diff --git a/derive/src/argument.rs b/derive/src/argument.rs index 37baafd..f8c0035 100644 --- a/derive/src/argument.rs +++ b/derive/src/argument.rs @@ -38,10 +38,16 @@ pub(crate) fn parse_arguments_attr(attrs: &[Attribute]) -> ArgumentsAttr { ArgumentsAttr::default() } -pub(crate) fn parse_argument(v: Variant) -> Option { +pub(crate) fn parse_argument(v: Variant) -> Vec { let ident = v.ident; let name = ident.to_string(); - let attribute = get_arg_attribute(&v.attrs)?; + let attributes = get_arg_attributes(&v.attrs); + + // Return early because we don't need to check the fields if it's not used. + if attributes.is_empty() { + return Vec::new(); + } + let help = collect_help(&v.attrs); let field = match v.fields { @@ -59,34 +65,43 @@ pub(crate) fn parse_argument(v: Variant) -> Option { } }; - let arg_type = match attribute { - ArgAttr::Option(opt) => { - let default_expr = match opt.default { - Some(expr) => quote!(#expr), - None => quote!(Default::default()), + attributes + .into_iter() + .map(|attribute| { + // We might override the help with the help given in the attribute + let mut arg_help = help.clone(); + let arg_type = match attribute { + ArgAttr::Option(opt) => { + let default_expr = match opt.default { + Some(expr) => quote!(#expr), + None => quote!(Default::default()), + }; + if let Some(help) = opt.help { + arg_help = help; + } + ArgType::Option { + flags: opt.flags, + takes_value: field.is_some(), + default: default_expr, + hidden: opt.hidden, + } + } + ArgAttr::Positional(pos) => { + assert!(field.is_some(), "Positional arguments must have a field"); + ArgType::Positional { + num_args: pos.num_args, + last: pos.last, + } + } }; - ArgType::Option { - flags: opt.flags, - takes_value: field.is_some(), - default: default_expr, - hidden: opt.hidden, + Argument { + ident: ident.clone(), + name: name.clone(), + arg_type, + help: arg_help, } - } - ArgAttr::Positional(pos) => { - assert!(field.is_some(), "Positional arguments must have a field"); - ArgType::Positional { - num_args: pos.num_args, - last: pos.last, - } - } - }; - - Some(Argument { - ident, - name, - arg_type, - help, - }) + }) + .collect() } fn collect_help(attrs: &[Attribute]) -> String { @@ -103,16 +118,12 @@ fn collect_help(attrs: &[Attribute]) -> String { help.join("\n") } -fn get_arg_attribute(attrs: &[Attribute]) -> Option { - let attrs: Vec<_> = attrs +fn get_arg_attributes(attrs: &[Attribute]) -> Vec { + attrs .iter() .filter(|a| a.path.is_ident("option") || a.path.is_ident("positional")) - .collect(); - match attrs[..] { - [] => None, - [attr] => Some(parse_argument_attribute(attr)), - _ => panic!("Can only specify one #[option] or #[positional] per argument variant"), - } + .map(parse_argument_attribute) + .collect() } pub(crate) fn short_handling(args: &[Argument]) -> TokenStream { diff --git a/derive/src/attributes.rs b/derive/src/attributes.rs index 48cf431..1fe7066 100644 --- a/derive/src/attributes.rs +++ b/derive/src/attributes.rs @@ -32,8 +32,9 @@ enum AttributeArguments { File(String), Env(String), ExitCode(i32), - Help(Vec), - Version(Vec), + Help(String), + HelpFlags(Vec), + VersionFlags(Vec), Last, Hidden, } @@ -69,10 +70,10 @@ impl ArgumentsAttr { let mut arguments_attr = Self::default(); for arg in AttributeArguments::parse_all(attr) { match arg { - AttributeArguments::Help(flags) => { + AttributeArguments::HelpFlags(flags) => { arguments_attr.help_flags = Flags::new(flags); } - AttributeArguments::Version(flags) => { + AttributeArguments::VersionFlags(flags) => { arguments_attr.version_flags = Flags::new(flags); } AttributeArguments::File(s) => arguments_attr.file = Some(s), @@ -91,6 +92,7 @@ pub(crate) struct OptionAttr { pub(crate) parser: Option, pub(crate) default: Option, pub(crate) hidden: bool, + pub(crate) help: Option, } impl OptionAttr { @@ -103,6 +105,7 @@ impl OptionAttr { AttributeArguments::Parser(e) => option_attr.parser = Some(e), AttributeArguments::Default(e) => option_attr.default = Some(e), AttributeArguments::Hidden => option_attr.hidden = true, + AttributeArguments::Help(h) => option_attr.help = Some(h), _ => panic!("Invalid argument"), }; } @@ -258,8 +261,9 @@ impl Parse for AttributeArguments { "value" => return Ok(Self::Value(input.parse::()?)), "file" => return Ok(Self::File(input.parse::()?.value())), "env" => return Ok(Self::Env(input.parse::()?.value())), + "help" => return Ok(Self::Help(input.parse::()?.value())), "exit_code" => return Ok(Self::ExitCode(input.parse::()?.base10_parse()?)), - "help" => { + "help_flags" => { let expr = input.parse::()?; let arr = match expr { syn::Expr::Array(arr) => arr, @@ -277,9 +281,9 @@ impl Parse for AttributeArguments { }; strings.push(val); } - return Ok(Self::Help(strings)); + return Ok(Self::HelpFlags(strings)); } - "version" => { + "version_flags" => { let expr = input.parse::()?; let arr = match expr { syn::Expr::Array(arr) => arr, @@ -299,7 +303,7 @@ impl Parse for AttributeArguments { }; strings.push(val); } - return Ok(Self::Version(strings)); + return Ok(Self::VersionFlags(strings)); } _ => panic!("Unrecognized argument {} for option attribute", name), }; diff --git a/derive/src/help.rs b/derive/src/help.rs index c8b2786..1dc1b8e 100644 --- a/derive/src/help.rs +++ b/derive/src/help.rs @@ -93,7 +93,10 @@ pub(crate) fn help_string( if flags.len() <= #width { let line = match help_lines.next() { Some(line) => line, - None => return s, + None => { + s.push('\n'); + continue; + }, }; let help_indent = " ".repeat(#width-flags.len()+2); s.push_str(&help_indent); diff --git a/tests/coreutils/ls.rs b/tests/coreutils/ls.rs index 7494ad0..b2b823b 100644 --- a/tests/coreutils/ls.rs +++ b/tests/coreutils/ls.rs @@ -143,32 +143,20 @@ enum Arg { #[option("--author")] Author, - // === Time === - #[option("-c")] - ChangeTime, - - #[option("-u")] - AccessTime, - #[option("--time=WORD")] + #[option("-c", default = Time::Change)] + #[option("-u", default = Time::Access)] Time(Time), - // === Sorting === + // === Sorting == + /// Sort by WORD #[option("--sort=WORD")] + #[option("-t", default = Sort::Time, help = "Sort by time")] + #[option("-U", default = Sort::None, help = "Do not sort")] + #[option("-v", default = Sort::Version, help = "Sort by version")] + #[option("-X", default = Sort::Extension, help = "Sort by extension")] Sort(Sort), - #[option("-t")] - SortTime, - - #[option("-U")] - SortNone, - - #[option("-v")] - SortVersion, - - #[option("-X")] - SortExtension, - // === Miscellaneous === #[option("-Z", "--context")] SecurityContext, @@ -208,21 +196,13 @@ enum Arg { NoGroup, // === Format === - /// Set long format - #[option("-l", "--long")] - Long, - - /// Set columns format - #[option("-C")] - Columns, - - /// Set across format - #[option("-x")] - Across, - - /// Set comma format - #[option("-m")] - Commas, + /// Set format + #[option("--format=FORMAT")] + #[option("-l", "--long", default = Format::Long, help = "Use long format")] + #[option("-C", default = Format::Columns, help = "Use columns format")] + #[option("-x", default = Format::Across, help = "Use across format")] + #[option("-m", default = Format::Commas, help = "Use comma format")] + Format(Format), /// Show single column #[option("-1")] @@ -237,20 +217,13 @@ enum Arg { #[option("-n", "--numeric-uid-gid")] LongNumericUidGid, - /// Set format - #[option("--format=FORMAT")] - Format(Format), - // === Indicator style === #[option("--indicator-style=STYLE")] + #[option("-p", default = IndicatorStyle::Slash, help = "Append slash to directories")] + #[option("--file-type", default = IndicatorStyle::FileType, help = "Add indicators for file types")] IndicatorStyle(IndicatorStyle), - #[option("-p")] - IndicatorStyleSlash, - - #[option("--file-type")] - IndicatorStyleFileType, - + /// Classify items #[option("-F", "--classify[=WHEN]", default = When::Always)] IndicatorStyleClassify(When), @@ -279,17 +252,11 @@ enum Arg { // === Quoting style === #[option("--quoting-style=STYLE")] + #[option("-N", "--literal", default = QuotingStyle::Literal)] + #[option("-h", "--escape", default = QuotingStyle::Escape)] + #[option("-Q", "--quote-name", default = todo!())] QuotingStyle(QuotingStyle), - #[option("-N", "--literal")] - Literal, - - #[option("-h", "--escape")] - Escape, - - #[option("-Q", "--quote-name")] - QuoteName, - /// Set the color #[option("--color[=WHEN]", default = When::Always)] Color(When), @@ -374,14 +341,8 @@ impl Options for Settings { Arg::All => self.which_files = Files::All, Arg::AlmostAll => self.which_files = Files::AlmostAll, Arg::Author => self.long_author = true, - Arg::ChangeTime => self.time = Time::Change, - Arg::AccessTime => self.time = Time::Access, Arg::Time(t) => self.time = t, Arg::Sort(s) => self.sort = s, - Arg::SortTime => self.sort = Sort::Time, - Arg::SortNone => self.sort = Sort::None, - Arg::SortVersion => self.sort = Sort::Version, - Arg::SortExtension => self.sort = Sort::Extension, Arg::SecurityContext => self.context = true, Arg::IgnoreBackups => self.ignore_backups = true, Arg::Directory => self.directory = true, @@ -394,10 +355,6 @@ impl Options for Settings { Arg::Width(w) => self.width = w, Arg::AllocationSize => todo!(), Arg::NoGroup => self.long_no_group = true, - Arg::Long => self.format = Format::Long, - Arg::Columns => self.format = Format::Columns, - Arg::Across => self.format = Format::Across, - Arg::Commas => self.format = Format::Commas, Arg::SingleColumn => self.format = Format::SingleColumn, Arg::LongNoGroup => { self.format = Format::Long; @@ -413,8 +370,6 @@ impl Options for Settings { } Arg::Format(f) => self.format = f, Arg::IndicatorStyle(style) => self.indicator_style = style, - Arg::IndicatorStyleSlash => self.indicator_style = IndicatorStyle::Slash, - Arg::IndicatorStyleFileType => self.indicator_style = IndicatorStyle::FileType, Arg::IndicatorStyleClassify(when) => { self.indicator_style = if when.to_bool() { IndicatorStyle::Classify @@ -429,9 +384,6 @@ impl Options for Settings { Arg::Kibibytes => todo!(), Arg::Si => todo!(), Arg::QuotingStyle(style) => self.quoting_style = style, - Arg::Literal => self.quoting_style = QuotingStyle::Literal, - Arg::Escape => self.quoting_style = QuotingStyle::Escape, - Arg::QuoteName => todo!(), Arg::Color(when) => self.color = when.to_bool(), Arg::HideControlChars => self.hide_control_chars = true, Arg::ShowControlChars => self.hide_control_chars = false, @@ -559,3 +511,12 @@ fn classify() { let s = Settings::parse(["ls", "-F"]); assert_eq!(s.indicator_style, IndicatorStyle::Classify); } + +#[test] +fn sort() { + let s = Settings::parse(["ls", "--sort=time"]); + assert_eq!(s.sort, Sort::Time); + + let s = Settings::parse(["ls", "-X"]); + assert_eq!(s.sort, Sort::Extension); +}