use std::{env, fmt, process}; #[derive(Debug, Default, PartialEq)] struct Args { verbose: bool, output: Option, input: Option, } #[derive(Debug, PartialEq)] enum ParseError { HelpRequested, MissingValue(&'static str), UnknownOption(String), UnexpectedArgument(String), } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::HelpRequested => Ok(()), Self::MissingValue(option) => write!(f, "missing value for {option}"), Self::UnknownOption(option) => write!(f, "unknown option: {option}"), Self::UnexpectedArgument(arg) => write!(f, "unexpected argument: {arg}"), } } } fn parse_args(arguments: I) -> Result where I: IntoIterator, S: Into, { let mut args = Args::default(); let mut iter = arguments.into_iter().map(Into::into); let mut options_enabled = true; while let Some(arg) = iter.next() { if options_enabled { match arg.as_str() { "-h" | "--help" => return Err(ParseError::HelpRequested), "-v" | "--verbose" => args.verbose = true, "-o" | "--output" => { args.output = Some( iter.next() .ok_or(ParseError::MissingValue("--output"))?, ); } "--" => options_enabled = false, _ if arg.starts_with('-') => { return Err(ParseError::UnknownOption(arg)); } _ => set_input(&mut args, arg)?, } } else { set_input(&mut args, arg)?; } } Ok(args) } fn set_input(args: &mut Args, value: String) -> Result<(), ParseError> { if args.input.is_some() { Err(ParseError::UnexpectedArgument(value)) } else { args.input = Some(value); Ok(()) } } fn print_usage(program: &str) { eprintln!( "Usage: {program} [OPTIONS] [INPUT]\n\ \n\ Options:\n\ \x20 -v, --verbose Enable verbose output\n\ \x20 -o, --output Write output to FILE\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) => { // Replace this with the application's real work. if args.verbose { eprintln!("arguments: {args:?}"); } println!( "input={}, output={}", args.input.as_deref().unwrap_or(""), args.output.as_deref().unwrap_or("") ); } Err(ParseError::HelpRequested) => print_usage(&program), Err(error) => { eprintln!("error: {error}"); print_usage(&program); process::exit(2); } } } #[cfg(test)] mod tests { use super::*; #[test] fn parses_options_and_input() { let result = parse_args(["-v", "--output", "result.txt", "input.txt"]); assert_eq!( result, Ok(Args { verbose: true, output: Some("result.txt".into()), input: Some("input.txt".into()), }) ); } #[test] fn double_dash_allows_option_like_input() { let result = parse_args(["--", "-input.txt"]); assert_eq!( result, Ok(Args { input: Some("-input.txt".into()), ..Args::default() }) ); } #[test] fn rejects_unknown_options() { let result = parse_args(["--wat"]); assert_eq!( result, Err(ParseError::UnknownOption("--wat".into())) ); } }