use std::{env, fmt, process}; #[derive(Debug, Default, PartialEq, Eq)] struct Args { verbose: bool, output: Option, inputs: Vec, } #[derive(Debug, PartialEq, Eq)] enum ParseError { MissingValue(&'static str), 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::UnknownOption(option) => write!(f, "unknown option: {option}"), } } } // Parses arguments after the executable name. fn parse_args(arguments: I) -> Result where I: IntoIterator, S: Into, { let mut arguments = arguments.into_iter().map(Into::into); let mut parsed = Args::default(); let mut options_enabled = true; while let Some(argument) = arguments.next() { if options_enabled { match argument.as_str() { "--" => { options_enabled = false; continue; } "-v" | "--verbose" => { parsed.verbose = true; continue; } "-o" | "--output" => { parsed.output = Some( arguments .next() .ok_or(ParseError::MissingValue("--output"))?, ); continue; } _ if argument.starts_with("--output=") => { parsed.output = Some(argument["--output=".len()..].to_owned()); continue; } _ if argument.starts_with('-') && argument != "-" => { return Err(ParseError::UnknownOption(argument)); } _ => {} } } parsed.inputs.push(argument); } Ok(parsed) } fn print_usage(program: &str) { println!("Usage: {program} [OPTIONS] [--] [INPUT ...]"); println!(); println!("Options:"); println!(" -v, --verbose Enable verbose output"); println!(" -o, --output Set the output path"); println!(" -h, --help Show this help"); } fn main() { let mut raw = env::args(); let program = raw.next().unwrap_or_else(|| "app".to_owned()); let arguments: Vec = raw.collect(); if arguments.iter().any(|arg| arg == "-h" || arg == "--help") { print_usage(&program); return; } match parse_args(arguments) { Ok(args) => { if args.verbose { eprintln!("parsed arguments: {args:?}"); } println!("output: {}", args.output.as_deref().unwrap_or("")); for input in args.inputs { println!("input: {input}"); } } 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(["-v", "--output=result.txt", "one", "two"]).unwrap(); assert_eq!( args, Args { verbose: true, output: Some("result.txt".to_owned()), inputs: vec!["one".to_owned(), "two".to_owned()], } ); } #[test] fn double_dash_stops_option_parsing() { let args = parse_args(["--", "--verbose", "-o"]).unwrap(); assert!(!args.verbose); assert_eq!(args.inputs, ["--verbose", "-o"]); } #[test] fn reports_missing_option_value() { assert_eq!( parse_args(["--output"]), Err(ParseError::MissingValue("--output")) ); } }