diff --git a/argh/src/lib.rs b/argh/src/lib.rs index 322214a..40d2e1f 100644 --- a/argh/src/lib.rs +++ b/argh/src/lib.rs @@ -149,6 +149,9 @@ //! } //! ``` //! +//! Positional arguments accept values starting with a dash, such as negative numbers +//! (e.g., `-5`) or dash-prefixed filenames. To explicitly mark the end of options, +//! use `--` as a separator. //! The last positional argument may include a default, or be wrapped in //! `Option` or `Vec` to indicate an optional or repeating positional argument. //! @@ -1024,12 +1027,16 @@ pub fn parse_struct_args( continue; } - if help { - return Err("Trailing arguments are not allowed after `help`.".to_string().into()); - } + let is_valid_option = parse_options.arg_to_slot.iter().any(|&(name, _)| name == next_arg); + if is_valid_option { + if help { + return Err("Trailing arguments are not allowed after `help`.".to_string().into()); + } - parse_options.parse(next_arg, &mut remaining_args)?; - continue; + parse_options.parse(next_arg, &mut remaining_args)?; + continue; + } + // If it's not a valid option, fall through to parse as positional } if let Some(ref mut parse_subcommand) = parse_subcommand { diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs index 9ef1774..a0d718a 100644 --- a/argh/tests/lib.rs +++ b/argh/tests/lib.rs @@ -922,6 +922,118 @@ Required options not provided: "###, ); } + + #[derive(FromArgs, Debug, PartialEq)] + /// Woot + struct NegativeNumber { + #[argh(positional)] + /// a number + n: i32, + } + + #[test] + fn negative_number_positional() { + assert_output(&["-5"], NegativeNumber { n: -5 }); + assert_output(&["-42"], NegativeNumber { n: -42 }); + assert_output(&["5"], NegativeNumber { n: 5 }); + } + + #[derive(FromArgs, Debug, PartialEq)] + /// Woot + struct DashPrefixedString { + #[argh(positional)] + /// a string + s: String, + } + + #[test] + fn dash_prefixed_string_positional() { + assert_output(&["-filename"], DashPrefixedString { s: "-filename".into() }); + assert_output(&["--weird-name"], DashPrefixedString { s: "--weird-name".into() }); + assert_output(&["-"], DashPrefixedString { s: "-".into() }); + } + + #[derive(FromArgs, Debug, PartialEq)] + /// Woot + struct NegativeWithOption { + #[argh(positional)] + /// a number + n: i32, + #[argh(option)] + /// an option + opt: Option, + } + + #[test] + fn negative_number_with_option() { + assert_output(&["-5"], NegativeWithOption { n: -5, opt: None }); + assert_output(&["-5", "--opt", "value"], NegativeWithOption { n: -5, opt: Some("value".into()) }); + assert_output(&["--opt", "value", "-10"], NegativeWithOption { n: -10, opt: Some("value".into()) }); + } + + #[derive(FromArgs, Debug, PartialEq)] + /// Woot + struct NegativeFloatNumber { + #[argh(positional)] + /// a float number + f: f64, + } + + #[test] + fn negative_float_positional() { + assert_output(&["-5.5"], NegativeFloatNumber { f: -5.5 }); + assert_output(&["-42.1"], NegativeFloatNumber { f: -42.1 }); + assert_output(&["5.5"], NegativeFloatNumber { f: 5.5 }); + } + + #[derive(FromArgs, Debug, PartialEq)] + /// Woot + struct WithOptionAndPositional { + #[argh(option)] + /// an option + opt: Option, + #[argh(positional)] + /// a positional + pos: String, + } + + #[derive(FromArgs, Debug, PartialEq)] + /// Woot + struct OnlyOption { + #[argh(option)] + /// an option + opt: Option, + } + + #[test] + fn unrecognized_option_error() { + // When there are no positional arguments, unrecognized options should error + // Note: error message includes quotes when fuzzy_search is available + #[cfg(not(feature = "fuzzy_search"))] + { + assert_error::( + &["--unknown"], + r###"Unrecognized argument: --unknown +"###, + ); + } + #[cfg(feature = "fuzzy_search")] + { + assert_error::( + &["--unknown"], + r###"Unrecognized argument: "--unknown" +"###, + ); + } + } + + #[test] + fn negative_number_not_mistaken_for_option() { + assert_output(&["-5"], WithOptionAndPositional { opt: None, pos: "-5".into() }); + assert_output(&["-100", "--opt", "val"], WithOptionAndPositional { opt: Some("val".into()), pos: "-100".into() }); + // Dash-prefixed strings are also accepted as positional arguments + assert_output(&["--unknown"], WithOptionAndPositional { opt: None, pos: "--unknown".into() }); + } } /// Tests derived from