diff --git a/derive/src/argument.rs b/derive/src/argument.rs index dd39d45..b68c55e 100644 --- a/derive/src/argument.rs +++ b/derive/src/argument.rs @@ -24,6 +24,7 @@ pub(crate) enum ArgType { }, Positional { num_args: RangeInclusive, + last: bool, }, } @@ -64,6 +65,7 @@ pub(crate) fn parse_argument(v: Variant) -> Option { assert!(field.is_some(), "Positional arguments must have a field"); ArgType::Positional { num_args: pos.num_args, + last: pos.last, } } }; @@ -237,8 +239,8 @@ pub(crate) fn positional_handling(args: &[Argument]) -> (TokenStream, TokenStrea let mut missing_argument_checks = vec![]; for arg @ Argument { name, arg_type, .. } in args { - let num_args = match arg_type { - ArgType::Positional { num_args } => num_args, + let (num_args, last) = match arg_type { + ArgType::Positional { num_args, last } => (num_args, last), ArgType::Option { .. } => continue, }; @@ -251,7 +253,11 @@ pub(crate) fn positional_handling(args: &[Argument]) -> (TokenStream, TokenStrea last_index += num_args.end(); - let expr = argument_expression(arg); + let expr = if *last { + last_positional_expression(&arg.ident) + } else { + positional_expression(&arg.ident) + }; match_arms.push(quote!(0..=#last_index => { #expr })); } @@ -303,14 +309,19 @@ fn required_value_expression(ident: &Ident) -> TokenStream { quote!(Self::#ident(FromValue::from_value(parser.value()?)?)) } -fn argument_expression(arg: &Argument) -> TokenStream { - let Argument { - ident, arg_type, .. - } = arg; - match arg_type { - ArgType::Positional { .. } => quote!( - Self::#ident(FromValue::from_value(value)?) - ), - _ => panic!("WWWOWOWOWOW"), - } +fn positional_expression(ident: &Ident) -> TokenStream { + quote!( + Self::#ident(FromValue::from_value(value)?) + ) +} + +fn last_positional_expression(ident: &Ident) -> TokenStream { + quote!({ + let raw_args = parser.raw_args()?; + let collection = std::iter::once(value) + .chain(raw_args) + .map(FromValue::from_value) + .collect::>()?; + Self::#ident(collection) + }) } diff --git a/derive/src/attributes.rs b/derive/src/attributes.rs index 3ff1aaa..cbd69e5 100644 --- a/derive/src/attributes.rs +++ b/derive/src/attributes.rs @@ -30,6 +30,7 @@ enum AttributeArguments { Value(Expr), NumArgs(RangeInclusive), File(String), + Last, } impl AttributeArguments { @@ -93,11 +94,15 @@ impl ValueAttr { pub(crate) struct PositionalAttr { pub(crate) num_args: RangeInclusive, + pub(crate) last: bool, } impl Default for PositionalAttr { fn default() -> Self { - Self { num_args: 1..=1 } + Self { + num_args: 1..=1, + last: false, + } } } @@ -108,6 +113,7 @@ impl PositionalAttr { for arg in AttributeArguments::parse_all(attr) { match arg { AttributeArguments::NumArgs(k) => positional_attr.num_args = k, + AttributeArguments::Last => positional_attr.last = true, _ => panic!(), }; } @@ -207,7 +213,16 @@ impl Parse for AttributeArguments { if input.peek(Ident) { let name = input.parse::()?.to_string(); + + // Arguments that do not take values + match name.as_str() { + "last" => return Ok(Self::Last), + _ => {} + } + input.parse::()?; + + // Arguments that do take values match name.as_str() { "parser" => return Ok(Self::Parser(input.parse::()?)), "default" => return Ok(Self::Default(input.parse::()?)), diff --git a/tests/positionals.rs b/tests/positionals.rs index 3eb5f11..c9951f2 100644 --- a/tests/positionals.rs +++ b/tests/positionals.rs @@ -1,8 +1,5 @@ -use std::ffi::OsString; use uutils_args::{Arguments, Options}; -const EMPTY: [OsString; 0] = []; - #[test] fn one_positional() { #[derive(Arguments, Clone)] @@ -21,7 +18,7 @@ fn one_positional() { let settings = Settings::parse(["test", "foo"]).unwrap(); assert_eq!(settings.file1, "foo"); - assert!(Settings::parse(EMPTY).is_err()); + assert!(Settings::parse(["test"]).is_err()); } #[test] @@ -47,7 +44,7 @@ fn two_positionals() { assert_eq!(settings.foo, "a"); assert_eq!(settings.bar, "b"); - assert!(Settings::parse(EMPTY).is_err()); + assert!(Settings::parse(["test"]).is_err()); } #[test] @@ -65,7 +62,7 @@ fn optional_positional() { foo: Option, } - let settings = Settings::parse(EMPTY).unwrap(); + let settings = Settings::parse(["test"]).unwrap(); assert_eq!(settings.foo, None); let settings = Settings::parse(["test", "bar"]).unwrap(); assert_eq!(settings.foo.unwrap(), "bar"); @@ -88,6 +85,50 @@ fn collect_positional() { let settings = Settings::parse(["test", "a", "b", "c"]).unwrap(); assert_eq!(settings.foo, vec!["a", "b", "c"]); - let settings = Settings::parse(EMPTY).unwrap(); + let settings = Settings::parse(["test"]).unwrap(); assert_eq!(settings.foo, Vec::::new()); } + +#[test] +fn last1() { + #[derive(Arguments, Clone)] + enum Arg { + #[positional(last, ..)] + Foo(Vec), + } + + #[derive(Default, Options)] + #[arg_type(Arg)] + struct Settings { + #[set(Arg::Foo)] + foo: Vec, + } + + let settings = Settings::parse(["test", "a", "-b", "c"]).unwrap(); + assert_eq!(settings.foo, vec!["a", "-b", "c"]); +} + +#[test] +fn last2() { + #[derive(Arguments, Clone)] + enum Arg { + #[option("-a")] + A, + + #[positional(last, ..)] + Foo(Vec), + } + + #[derive(Default, Options)] + #[arg_type(Arg)] + struct Settings { + #[set(Arg::Foo)] + foo: Vec, + } + + let settings = Settings::parse(["test", "-a"]).unwrap(); + assert_eq!(settings.foo, Vec::::new()); + + let settings = Settings::parse(["test", "--", "-a"]).unwrap(); + assert_eq!(settings.foo, vec!["-a"]); +}