use std::{env, fmt, process}; #[derive(Debug, Default, PartialEq)] struct Args { verbose: bool, output: Option, count: u32, inputs: Vec, } #[derive(Debug, PartialEq)] enum ParseError { MissingValue(&'static str), InvalidCount(String), UnknownOption(String), } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::MissingValue(option) => write!(f, "missing value for {option}"), Self::InvalidCount(value) => write!(f, "invalid count: {value}"), Self::UnknownOption(option) => write!(f, "unknown option: {option}"), } } } fn parse_args(args: I) -> Result where I: IntoIterator, S: Into, { let mut result = Args { count: 1, ..Args::default() }; let mut args = args.into_iter().map(Into::into); while let Some(arg) = args.next() { match arg.as_str() { "-v" | "--verbose" => result.verbose = true, "-o" | "--output" => { result.output = Some( args.next() .ok_or(ParseError::MissingValue("--output"))?, ); } "-n" | "--count" => { let value = args .next() .ok_or(ParseError::MissingValue("--count"))?; result.count = value .parse() .map_err(|_| ParseError::InvalidCount(value))?; } "--" => { result.inputs.extend(args); break; } _ if arg.starts_with('-') => return Err(ParseError::UnknownOption(arg)), _ => result.inputs.push(arg), } } Ok(result) } fn print_help(program: &str) { println!( "\ Usage: {program} [OPTIONS] [INPUTS...] Options: -v, --verbose Enable verbose output -o, --output FILE Write output to FILE -n, --count NUMBER Number of iterations (default: 1) -h, --help Show this help" ); } fn main() { let mut raw = env::args(); let program = raw.next().unwrap_or_else(|| "example".into()); let args: Vec = raw.collect(); if args.iter().any(|arg| arg == "-h" || arg == "--help") { print_help(&program); return; } match parse_args(args) { Ok(args) => println!("{args:#?}"), Err(error) => { eprintln!("{program}: {error}"); eprintln!("Try '{program} --help' for more information."); process::exit(2); } } } #[cfg(test)] mod tests { use super::*; #[test] fn parses_options_and_inputs() { let args = parse_args([ "--verbose", "--output", "result.txt", "-n", "3", "one", "two", ]) .unwrap(); assert_eq!( args, Args { verbose: true, output: Some("result.txt".into()), count: 3, inputs: vec!["one".into(), "two".into()], } ); } #[test] fn double_dash_ends_option_parsing() { let args = parse_args(["--", "--verbose"]).unwrap(); assert!(!args.verbose); assert_eq!(args.inputs, ["--verbose"]); } #[test] fn rejects_unknown_options() { assert_eq!( parse_args(["--unknown"]), Err(ParseError::UnknownOption("--unknown".into())) ); } }