getopt3 2.6.1

Zero dependency command line argument parser
Documentation
/*
 * Copyright (c) Radim Kolar 2024.
 * SPDX-License-Identifier: MIT
 *
 * getopt3 library is licensed under MIT license:
 *   https://spdx.org/licenses/MIT.html
*/

/**
   Posix option parsing mode. First non option stops option parsing.
*/
#[cfg(feature = "posix")]
pub fn posix(arg: impl IntoIterator<Item=impl AsRef<str>>, optstring: impl AsRef<str>) -> Result<getopt> {
   let options_map = build_options_map(validate_optstring(optstring.as_ref())?);

   let mut opts = HashMap::<char,String>::new();
   let mut args = Vec::<String>::new();
   let mut next_opt: Option<char> = None;
   let mut stop_parsing = false;

   for element in arg {
      let element = element.as_ref();
      if stop_parsing {
         // we do not parse options anymore all what's left
         // are arguments
         args.push(element.to_string());
      } else
      {
         // check if we need to process option from previous loop
         if next_opt.is_some() {
            opts.insert(next_opt.unwrap(), element.to_string());
            next_opt = None;
         } else {
            // check if argument is a option in form -X otherwise
            // it is an argument and we stop parsing
            if element.starts_with('-') && element.len() == 2 &&
               validate_optchar(element.chars().nth(1).unwrap()).is_ok()
            {
               let opt = element.chars().nth(1).unwrap();
               if let Some(&value) = options_map.get(&opt) {
                  if value == true {
                     // option will follow in next loop iteration
                     next_opt = Some(opt);
                  } else {
                     // option is without an argument insert it now
                     opts.insert(opt,"".to_string());
                  }
               } else {
                  // unknown option treat is as option without an argument
                  opts.insert(opt,"".to_string());
               }
            } else {
               // not an option. store argument and stop parsing
               args.push(element.to_string());
               stop_parsing = true;
            }
         }
      }
   }
   // if there is non finished option. finish it
   if let Some(o) = next_opt {
      opts.insert(o, String::new());
   }

   Ok ( getopt {options:opts, arguments:args, option_has_arg:options_map} )
}

/**
  Checks if optchar is valid option character.

  Allowed characters are a-zA-Z0-9
*/
#[cfg(feature = "posix")]
fn validate_optchar(optchar: char) -> Result<char> {
   match optchar {
      'a'..='z' => Ok(optchar),
      'A'..='Z' => Ok(optchar),
      '0'..='9' => Ok(optchar),
      '?'       => Ok(optchar),
       _        => Err(Error::new(ErrorKind::InvalidInput, "unsupported option character"))
   }
}

#[cfg(test)]
#[cfg(feature = "posix")]
#[path = "getopt_posix_tests.rs"]
mod posix;

#[cfg(test)]
#[cfg(feature = "posix")]
#[path = "getopt_posix_helpers_tests.rs"]
mod posix_helpers;