use std::{env, process}; #[derive(Debug, Default, PartialEq)] struct Args { verbose: bool, output: Option, count: u32, inputs: Vec, } fn parse_args(args: I) -> Result where I: IntoIterator, S: Into, { let mut parsed = 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" => parsed.verbose = true, "-o" | "--output" => { parsed.output = Some( args.next() .ok_or_else(|| format!("{arg} requires a value"))?, ); } "-n" | "--count" => { let value = args .next() .ok_or_else(|| format!("{arg} requires a value"))?; parsed.count = value .parse() .map_err(|_| format!("invalid count: {value}"))?; } "--" => { parsed.inputs.extend(args); break; } _ if arg.starts_with("--output=") => { parsed.output = Some(arg["--output=".len()..].to_owned()); } _ if arg.starts_with("--count=") => { let value = &arg["--count=".len()..]; parsed.count = value .parse() .map_err(|_| format!("invalid count: {value}"))?; } "-h" | "--help" => return Err(String::new()), _ if arg.starts_with('-') => return Err(format!("unknown option: {arg}")), _ => parsed.inputs.push(arg), } } if parsed.inputs.is_empty() { return Err("at least one input is required".to_owned()); } Ok(parsed) } fn usage(program: &str) { eprintln!( "Usage: {program} [OPTIONS] ...\n\ \n\ Options:\n\ \x20 -v, --verbose Enable verbose output\n\ \x20 -o, --output Set the output path\n\ \x20 -n, --count Set the repetition count (default: 1)\n\ \x20 -h, --help Show this help" ); } fn main() { let mut raw = env::args(); let program = raw.next().unwrap_or_else(|| "app".to_owned()); match parse_args(raw) { Ok(args) => { if args.verbose { eprintln!("arguments: {args:?}"); } for _ in 0..args.count { for input in &args.inputs { println!("{input}"); } } } Err(message) => { if !message.is_empty() { eprintln!("error: {message}\n"); } usage(&program); process::exit(if message.is_empty() { 0 } else { 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".to_owned()), count: 3, inputs: vec!["one".to_owned(), "two".to_owned()], } ); } #[test] fn double_dash_stops_option_parsing() { let args = parse_args(["--", "-literal"]).unwrap(); assert_eq!(args.inputs, vec!["-literal"]); assert_eq!(args.count, 1); } #[test] fn rejects_unknown_options() { assert_eq!( parse_args(["--unknown", "input"]).unwrap_err(), "unknown option: --unknown" ); } }