minmax-cli 0.1.0-beta.7

programs computing min and max argument values
/*
min + max cli programs

implementation by Radim Kolar <hsn@sendmail.cz> 2026
https://gitlab.com/hsn10/minmax-cli

This is free and unencumbered software released into the public domain.
SPDX-License-Identifier: Unlicense OR CC0-1.0

For more information, please refer to <http://unlicense.org/>
or <https://creativecommons.org/publicdomain/zero/1.0/>
*/

const VERSION: &str = env!("CARGO_PKG_VERSION");

const ARGS: &str = "v?aA";

fn do_help() {
   println!("{} [-v | -? ] | [ -a | -A ] <number> ...", NAME);
   println!("");
   println!("Find and print the {} value from a list of provided numbers.", ACTION);
   println!("");
   println!("Options");
   println!("  -v  Print program version and exit.");
   println!("  -?  Show this help message.");
   println!("  -a  Compare numbers by their absolute value instead of their raw value.");
   println!("  -A  Print absolute value of returned number. Implies -a.");
   println!("");
   println!("Exit Codes");
   println!("  0   Success");
   println!("  1   No numeric arguments provided");
   println!("  2   One or more numeric arguments failed to parse or were invalid");
   println!("");
   println!("This is free and unencumbered software released into the public domain.");
}

pub fn index_of_<F>(numbers: &[f64], mut cmp: F) -> usize
where
   F: FnMut(&f64, &f64) -> std::cmp::Ordering,
{
   numbers
      .iter()
      .enumerate()
      .filter(|(_, &v)| v.is_finite()) // removes NaN and ±∞
      .min_by(|(_, a), (_, b)| cmp(a, b))
      .expect("All values are NaN or Infinite")
      .0
}

#[cfg(test)]
mod indexmin_tests;

/**
  converts positional arguments into floats.

  ## Returns
  true - if all arguments were succesfully parsed
  false - one or more argument failed to parse, is NaN or Infinite.
*/
pub fn args_as_floats(g: &getopt2::getopt, numbers: &mut Vec<f64>) -> bool {
   numbers.clear();
   let mut parse_fail = false;
   /* parse command line arguments as f64 numbers */
   for n_str in g.iter() {
      debug_assert!({
         println!("parsing n_str='{}'", n_str);
         true
      });
      match n_str.parse::<f64>() {
         Ok(num) => {
            // Check if the parsed number is NaN or infinite
            if num.is_nan() {
               eprintln!("Error parsing '{}': NaN values are not allowed.", n_str);
               parse_fail = true;
            } else if num.is_infinite() {
               eprintln!("Error parsing '{}': Infinite values are not allowed.", n_str);
               parse_fail = true;
            } else {
               // If it's a valid finite number, push it
               numbers.push(num);
            }
         }
         Err(e) => {
            eprintln!("Error parsing '{}': {}", n_str, e);
            parse_fail = true
         }
      }
   }
   !parse_fail
}

fn to_comparable_numbers(numbers: &[f64], use_abs: bool) -> Vec<f64> {
   let mut comparable_numbers = Vec::<f64>::with_capacity(numbers.len());
   for &n in numbers.iter() {
      if use_abs {
         comparable_numbers.push(n.abs());
      } else {
         comparable_numbers.push(n);
      }
   }
   comparable_numbers
}

use getopt2::hideBin;
use std::process::ExitCode;

/**
  ExitCode:
  1. 0 - OK
  2. 1 - Numeric arguments are missing
  3. 2 - Arguments can't be parsed as numbers
*/
fn main() -> ExitCode {
   let g = getopt2::new(hideBin(std::env::args()), ARGS).unwrap();
   if g.has('v') {
      println!("{}", VERSION);
      ExitCode::SUCCESS
   } else if g.has('?') {
      do_help();
      ExitCode::SUCCESS
   } else if g.len() == 0 {
      // no numeric arguments found
      do_help();
      ExitCode::from(1)
   } else {
      // main program logic
      let use_abs = g.has('a') || g.has('A');
      let mut numbers: Vec<f64> = Vec::with_capacity(g.len());

      if !args_as_floats(&g, &mut numbers) {
         ExitCode::from(2)
      } else {
         /* convert numbers to comparable numbers, optionaly use abs value */
         let comparable_numbers: Vec<f64> = to_comparable_numbers(&numbers, use_abs);
         /* find lowest number */
         let i = index_of_(&comparable_numbers, num_cmp());
         /* output absolute value on -A */
         if g.has('A') {
            numbers.iter_mut().for_each(|x| *x = x.abs());
         }
         println!("{}", numbers[i]);

         ExitCode::SUCCESS
      }
   }
}