From 74af32a4b9a3bae10a6eaa54530d59f11fa82cef Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 2 Dec 2022 13:49:12 +0100 Subject: [PATCH] options with optional values --- derive/src/lib.rs | 23 +++- tests/flags.rs | 269 +++++++--------------------------------------- tests/options.rs | 221 +++++++++++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+), 233 deletions(-) create mode 100644 tests/options.rs diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 3403291..1dc5325 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -157,6 +157,20 @@ pub fn arguments(input: TokenStream) -> TokenStream { Fields::Unnamed(f) => { let v: Vec<_> = f.unnamed.iter().collect(); assert_eq!(v.len(), 1, "Options can have only one field"); + let field_type = v[0].ty.clone(); + let field_is_option = { + if let syn::Type::Path(field_type_path) = field_type { + field_type_path + .path + .segments + .iter() + .map(|s| s.ident.to_string()) + .collect::>() + == vec!["Option"] + } else { + false + } + }; for attr in variant.attrs { let Some(attr) = parse_attr(attr) else { continue; }; let DeriveAttribute::Option(f) = attr else { @@ -166,7 +180,14 @@ pub fn arguments(input: TokenStream) -> TokenStream { short_flags.append(&mut shorts); long_flags.append(&mut longs); } - quote!(Self::#variant_ident (FromValue::from_value(parser.value()?)?)) + if field_is_option { + quote!(Self::#variant_ident (match parser.optional_value() { + Some(v) => Some(FromValue::from_value(v)?), + None => None, + })) + } else { + quote!(Self::#variant_ident (FromValue::from_value(parser.value()?)?)) + } } _ => panic!("unimplemented"), }; diff --git a/tests/flags.rs b/tests/flags.rs index 59cf7d6..6cbada8 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -1,4 +1,4 @@ -use uutils_args::{ArgumentIter, Arguments, FromValue, Options}; +use uutils_args::{ArgumentIter, Arguments, Options}; fn to_vec(mut args: ArgumentIter) -> Vec { let mut v = Vec::new(); @@ -19,7 +19,7 @@ fn one_flag() { #[derive(Options, Default)] #[arg_type(Arg)] struct Settings { - #[map(Arg::Foo => true)] + #[set_true(Arg::Foo)] foo: bool, } @@ -43,9 +43,9 @@ fn two_flags() { #[derive(Default, Options, PartialEq, Eq, Debug)] #[arg_type(Arg)] struct Settings { - #[map(Arg::A => true)] + #[set_true(Arg::A)] a: bool, - #[map(Arg::B => true)] + #[set_true(Arg::B)] b: bool, } @@ -78,7 +78,7 @@ fn long_and_short_flag() { #[derive(Default, Options, PartialEq, Eq, Debug)] #[arg_type(Arg)] struct Settings { - #[map(Arg::Foo => true)] + #[set_true(Arg::Foo)] foo: bool, } @@ -101,7 +101,7 @@ fn short_alias() { #[derive(Default, Options, PartialEq, Eq, Debug)] #[arg_type(Arg)] struct Settings { - #[map(Arg::Foo => true)] + #[set_true(Arg::Foo)] foo: bool, } @@ -119,7 +119,7 @@ fn long_alias() { #[derive(Default, Options, PartialEq, Eq, Debug)] #[arg_type(Arg)] struct Settings { - #[map(Arg::Foo => true)] + #[set_true(Arg::Foo)] foo: bool, } @@ -139,9 +139,9 @@ fn short_and_long_alias() { #[derive(Default, Options, PartialEq, Eq, Debug)] #[arg_type(Arg)] struct Settings { - #[map(Arg::Foo => true)] + #[set_true(Arg::Foo)] foo: bool, - #[map(Arg::Bar => true)] + #[set_true(Arg::Bar)] bar: bool, } @@ -176,11 +176,11 @@ fn xyz_map_to_abc() { #[derive(Default, Options, PartialEq, Eq, Debug)] #[arg_type(Arg)] struct Settings { - #[map(Arg::X | Arg::Z => true)] + #[set_true(Arg::X | Arg::Z)] a: bool, - #[map(Arg::X | Arg::Y | Arg::Z => true)] + #[set_true(Arg::X | Arg::Y | Arg::Z)] b: bool, - #[map(Arg::Y | Arg::Z => true)] + #[set_true(Arg::Y | Arg::Z)] c: bool, } @@ -234,9 +234,9 @@ fn non_rust_ident() { #[derive(Default, Options, PartialEq, Eq, Debug)] #[arg_type(Arg)] struct Settings { - #[map(Arg::FooBar => true)] + #[set_true(Arg::FooBar)] a: bool, - #[map(Arg::Super => true)] + #[set_true(Arg::Super)] b: bool, } @@ -256,7 +256,7 @@ fn number_flag() { #[derive(Default, Options, PartialEq, Eq, Debug)] #[arg_type(Arg)] struct Settings { - #[map(Arg::One => true)] + #[set_true(Arg::One)] one: bool, } @@ -278,7 +278,7 @@ fn false_bool() { struct Settings { #[map( Arg::A => true, - Arg::B => false + Arg::B => false, )] foo: bool, } @@ -295,6 +295,27 @@ fn false_bool() { Settings::parse(["-b", "-a"]).unwrap(), Settings { foo: true } ); + + #[derive(Default, Options, PartialEq, Eq, Debug)] + #[arg_type(Arg)] + struct Settings2 { + #[set_true(Arg::A)] + #[set_false(Arg::B)] + foo: bool, + } + + assert_eq!(Settings2::parse(["-a"]).unwrap(), Settings2 { foo: true }); + assert_eq!(Settings2::parse(["-b"]).unwrap(), Settings2 { foo: false }); + assert_eq!(Settings2::parse(["-ab"]).unwrap(), Settings2 { foo: false }); + assert_eq!(Settings2::parse(["-ba"]).unwrap(), Settings2 { foo: true }); + assert_eq!( + Settings2::parse(["-a", "-b"]).unwrap(), + Settings2 { foo: false } + ); + assert_eq!( + Settings2::parse(["-b", "-a"]).unwrap(), + Settings2 { foo: true } + ); } #[test] @@ -363,219 +384,3 @@ fn count() { assert_eq!(Settings::parse(["-vv"]).unwrap().verbosity, 2); assert_eq!(Settings::parse(["-vvv"]).unwrap().verbosity, 3); } - -#[test] -fn string_option() { - #[derive(Arguments)] - enum Arg { - #[option] - Message(String), - } - - #[derive(Default, Options)] - #[arg_type(Arg)] - struct Settings { - #[map(Arg::Message(s) => s)] - message: String, - } - - assert_eq!( - Settings::parse(["--message=hello"]).unwrap().message, - "hello" - ); -} - -#[test] -fn enum_option() { - #[derive(FromValue, Default, Debug, PartialEq, Eq, Clone)] - enum Format { - #[default] - #[value] - Foo, - #[value] - Bar, - #[value] - Baz, - } - - #[derive(Arguments)] - enum Arg { - #[option] - Format(Format), - } - - #[derive(Default, Options)] - #[arg_type(Arg)] - struct Settings { - #[map(Arg::Format(f) => f)] - format: Format, - } - - assert_eq!( - Settings::parse(["--format=bar"]).unwrap().format, - Format::Bar - ); - - assert_eq!( - Settings::parse(["--format", "baz"]).unwrap().format, - Format::Baz - ); -} - -#[test] -fn enum_option_with_fields() { - #[derive(FromValue, Default, Debug, PartialEq, Eq, Clone)] - enum Indent { - #[default] - Tabs, - #[value("thin", value = Self::Spaces(4))] - #[value("wide", value = Self::Spaces(8))] - Spaces(u8), - } - - #[derive(Arguments)] - enum Arg { - #[option] - Indent(Indent), - } - - #[derive(Default, Options)] - #[arg_type(Arg)] - struct Settings { - #[map(Arg::Indent(i) => i)] - indent: Indent, - } - - assert_eq!( - Settings::parse(["-i=thin"]).unwrap().indent, - Indent::Spaces(4) - ); - assert_eq!( - Settings::parse(["-i=wide"]).unwrap().indent, - Indent::Spaces(8) - ); -} - -#[test] -fn enum_with_complex_from_value() { - #[derive(Default, Debug, PartialEq, Eq, Clone)] - enum Indent { - #[default] - Tabs, - Spaces(u8), - } - - impl FromValue for Indent { - fn from_value(value: std::ffi::OsString) -> Result { - let value = value.into_string()?; - if value == "tabs" { - Ok(Self::Tabs) - } else if let Ok(n) = value.parse() { - Ok(Self::Spaces(n)) - } else { - Err(lexopt::Error::ParsingFailed { - value, - error: "Failure!".into(), - }) - } - } - } - - #[derive(Arguments)] - enum Arg { - #[option] - Indent(Indent), - } - - #[derive(Default, Options)] - #[arg_type(Arg)] - struct Settings { - #[map(Arg::Indent(i) => i)] - indent: Indent, - } - - assert_eq!(Settings::parse(["-i=tabs"]).unwrap().indent, Indent::Tabs); - assert_eq!(Settings::parse(["-i=4"]).unwrap().indent, Indent::Spaces(4)); -} - -#[test] -fn color() { - #[derive(Default, FromValue, Debug, PartialEq, Eq, Clone)] - enum Color { - #[value("yes", "always")] - Always, - #[default] - #[value("auto")] - Auto, - #[value("no", "never")] - Never, - } - - #[derive(Arguments)] - enum Arg { - #[option] - Color(Color), - } - - #[derive(Default, Options)] - #[arg_type(Arg)] - struct Settings { - #[map(Arg::Color(c) => c)] - color: Color, - } - - assert_eq!( - Settings::parse(["--color=yes"]).unwrap().color, - Color::Always - ); - assert_eq!( - Settings::parse(["--color=always"]).unwrap().color, - Color::Always - ); - assert_eq!(Settings::parse(["--color=no"]).unwrap().color, Color::Never); - assert_eq!( - Settings::parse(["--color=never"]).unwrap().color, - Color::Never - ); - assert_eq!( - Settings::parse(["--color=auto"]).unwrap().color, - Color::Auto - ); -} - -#[test] -fn actions() { - #[derive(Arguments)] - enum Arg { - #[option] - Message(String), - #[flag] - Send, - #[flag] - Receive, - } - - #[derive(Options, Default)] - #[arg_type(Arg)] - struct Settings { - #[map(Arg::Message(m) => m)] - message1: String, - - #[set(Arg::Message)] - message2: String, - - #[set_true(Arg::Send)] - #[set_false(Arg::Receive)] - send: bool, - - // Or map, true or false inside the collect - #[collect(set(Arg::Message))] - messages: Vec, - } - - let settings = Settings::parse(["-m=Hello", "-m=World", "--send"]).unwrap(); - assert_eq!(settings.messages, vec!["Hello", "World"]); - assert_eq!(settings.message1, "World"); - assert_eq!(settings.message2, "World"); - assert!(settings.send); -} diff --git a/tests/options.rs b/tests/options.rs new file mode 100644 index 0000000..1882208 --- /dev/null +++ b/tests/options.rs @@ -0,0 +1,221 @@ +use uutils_args::{Arguments, FromValue, Options}; + +#[test] +fn string_option() { + #[derive(Arguments)] + enum Arg { + #[option] + Message(String), + } + + #[derive(Default, Options)] + #[arg_type(Arg)] + struct Settings { + #[set(Arg::Message)] + message: String, + } + + assert_eq!( + Settings::parse(["--message=hello"]).unwrap().message, + "hello" + ); +} + +#[test] +fn enum_option() { + #[derive(FromValue, Default, Debug, PartialEq, Eq, Clone)] + enum Format { + #[default] + #[value] + Foo, + #[value] + Bar, + #[value] + Baz, + } + + #[derive(Arguments)] + enum Arg { + #[option] + Format(Format), + } + + #[derive(Default, Options)] + #[arg_type(Arg)] + struct Settings { + #[set(Arg::Format)] + format: Format, + } + + assert_eq!( + Settings::parse(["--format=bar"]).unwrap().format, + Format::Bar + ); + + assert_eq!( + Settings::parse(["--format", "baz"]).unwrap().format, + Format::Baz + ); +} + +#[test] +fn enum_option_with_fields() { + #[derive(FromValue, Default, Debug, PartialEq, Eq, Clone)] + enum Indent { + #[default] + Tabs, + #[value("thin", value = Self::Spaces(4))] + #[value("wide", value = Self::Spaces(8))] + Spaces(u8), + } + + #[derive(Arguments)] + enum Arg { + #[option] + Indent(Indent), + } + + #[derive(Default, Options)] + #[arg_type(Arg)] + struct Settings { + #[set(Arg::Indent)] + indent: Indent, + } + + assert_eq!( + Settings::parse(["-i=thin"]).unwrap().indent, + Indent::Spaces(4) + ); + assert_eq!( + Settings::parse(["-i=wide"]).unwrap().indent, + Indent::Spaces(8) + ); +} + +#[test] +fn enum_with_complex_from_value() { + #[derive(Default, Debug, PartialEq, Eq, Clone)] + enum Indent { + #[default] + Tabs, + Spaces(u8), + } + + impl FromValue for Indent { + fn from_value(value: std::ffi::OsString) -> Result { + let value = value.into_string()?; + if value == "tabs" { + Ok(Self::Tabs) + } else if let Ok(n) = value.parse() { + Ok(Self::Spaces(n)) + } else { + Err(lexopt::Error::ParsingFailed { + value, + error: "Failure!".into(), + }) + } + } + } + + #[derive(Arguments)] + enum Arg { + #[option] + Indent(Indent), + } + + #[derive(Default, Options)] + #[arg_type(Arg)] + struct Settings { + #[map(Arg::Indent(i) => i)] + indent: Indent, + } + + assert_eq!(Settings::parse(["-i=tabs"]).unwrap().indent, Indent::Tabs); + assert_eq!(Settings::parse(["-i=4"]).unwrap().indent, Indent::Spaces(4)); +} + +#[test] +fn color() { + #[derive(Default, FromValue, Debug, PartialEq, Eq, Clone)] + enum Color { + #[value("yes", "always")] + Always, + #[default] + #[value("auto")] + Auto, + #[value("no", "never")] + Never, + } + + #[derive(Arguments)] + enum Arg { + #[option] + Color(Option), + } + + #[derive(Default, Options)] + #[arg_type(Arg)] + struct Settings { + #[map( + Arg::Color(Some(c)) => c, + Arg::Color(None) => Color::Always, + )] + color: Color, + } + + assert_eq!( + Settings::parse(["--color=yes"]).unwrap().color, + Color::Always + ); + assert_eq!( + Settings::parse(["--color=always"]).unwrap().color, + Color::Always + ); + assert_eq!(Settings::parse(["--color=no"]).unwrap().color, Color::Never); + assert_eq!( + Settings::parse(["--color=never"]).unwrap().color, + Color::Never + ); + assert_eq!( + Settings::parse(["--color=auto"]).unwrap().color, + Color::Auto + ); + assert_eq!(Settings::parse(["--color"]).unwrap().color, Color::Always) +} + +#[test] +fn actions() { + #[derive(Arguments)] + enum Arg { + #[option] + Message(String), + #[flag] + Send, + #[flag] + Receive, + } + + #[derive(Options, Default)] + #[arg_type(Arg)] + struct Settings { + #[map(Arg::Message(m) => m)] + message1: String, + + #[set(Arg::Message)] + message2: String, + + #[set_true(Arg::Send)] + #[set_false(Arg::Receive)] + send: bool, + + // Or map, true or false inside the collect + #[collect(set(Arg::Message))] + messages: Vec, + } + + let settings = Settings::parse(["-m=Hello", "-m=World", "--send"]).unwrap(); + assert_eq!(settings.messages, vec!["Hello", "World"]); + assert_eq!(settings.message1, "World"); + assert_eq!(settings.message2, "World"); + assert!(settings.send); +}