diff --git a/README.md b/README.md index 4e949e4..fc55fdd 100644 --- a/README.md +++ b/README.md @@ -174,4 +174,44 @@ struct SubCommandTwo { } ``` +It is possible to specify a default subcommand, just like specifying +a default for an optional argument. + +```rust +use argh::FromArgs; + +#[derive(FromArgs, PartialEq, Debug)] +/// Top-level command. +struct TopLevel { + #[argh(subcommand, default = "SubCommandOne")] + nested: Option, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), +} + +#[derive(FromArgs, PartialEq, Debug)] +/// First subcommand. +#[argh(subcommand, name = "one")] +struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Second subcommand. +#[argh(subcommand, name = "two")] +struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, +} +``` + + NOTE: This is not an officially supported Google product. diff --git a/argh/src/lib.rs b/argh/src/lib.rs index 5afc162..352c7e7 100644 --- a/argh/src/lib.rs +++ b/argh/src/lib.rs @@ -298,7 +298,18 @@ pub trait FromArgs: Sized { /// }, /// ); /// ``` - fn from_args(command_name: &[&str], args: &[&str]) -> Result; + fn from_args(command_name: &[&str], args: &[&str]) -> Result { + Self::from_args_show_command_usage(command_name, args, true) + } + + /// Just like `from_args`, but with an additional parameter to specify whether or not + /// to print the first line of command_usage. + /// Internal use only. + fn from_args_show_command_usage( + command_name: &[&str], + args: &[&str], + show_command_usage: bool, + ) -> Result; /// Get a String with just the argument names, e.g., options, flags, subcommands, etc, but /// without the values of the options and arguments. This can be useful as a means to capture @@ -432,6 +443,17 @@ pub trait FromArgs: Sized { /// ); /// ``` fn redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result, EarlyExit> { + Self::redact_arg_values_show_command_usage(_command_name, _args, true) + } + + /// Just like `redact_arg_values`, but with an additional parameter to specify whether or not + /// to print the first line of command_usage. + /// Internal use only. + fn redact_arg_values_show_command_usage( + _command_name: &[&str], + _args: &[&str], + _show_command_usage: bool, + ) -> Result, EarlyExit> { Ok(vec!["<>".into()]) } } @@ -689,19 +711,25 @@ impl_flag_for_integers![u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,]; /// `parse_positionals`: Helper to parse positional arguments. /// `parse_subcommand`: Helper to parse a subcommand. /// `help_func`: Generate a help message. +/// `show_command_usage`: The bool value `help_func` is called with. #[doc(hidden)] -pub fn parse_struct_args( +pub fn parse_struct_args_show_command_usage( cmd_name: &[&str], args: &[&str], mut parse_options: ParseStructOptions<'_>, mut parse_positionals: ParseStructPositionals<'_>, mut parse_subcommand: Option>, - help_func: &dyn Fn() -> String, + help_func: &dyn Fn(bool) -> String, + show_command_usage: bool, ) -> Result<(), EarlyExit> { let mut help = false; let mut remaining_args = args; let mut positional_index = 0; let mut options_ended = false; + let (has_default_subcommand_parse, subcmd_str) = match parse_subcommand { + Some(ParseStructSubCommand { default: Some(subcmd_str), .. }) => (true, subcmd_str), + _ => (false, ""), + }; 'parse_args: while let Some(&next_arg) = remaining_args.get(0) { remaining_args = &remaining_args[1..]; @@ -720,28 +748,103 @@ pub fn parse_struct_args( return Err("Trailing arguments are not allowed after `help`.".to_string().into()); } - parse_options.parse(next_arg, &mut remaining_args)?; - continue; + // Give a chance to default subcommand reparsing. + let parse_result = parse_options.parse(next_arg, &mut remaining_args); + if parse_result.is_err() { + if has_default_subcommand_parse { + break 'parse_args; + } + parse_result? + } else { + continue; + } } if let Some(ref mut parse_subcommand) = parse_subcommand { - if parse_subcommand.parse(help, cmd_name, next_arg, remaining_args)? { + if parse_subcommand.parse(help, cmd_name, next_arg, remaining_args, true)? { // Unset `help`, since we handled it in the subcommand help = false; break 'parse_args; } } - parse_positionals.parse(&mut positional_index, next_arg)?; + let parse_result = parse_positionals.parse(&mut positional_index, next_arg); + if parse_result.is_err() { + if has_default_subcommand_parse { + break 'parse_args; + } + parse_result? + } else { + continue; + } } if help { - Err(EarlyExit { output: help_func(), status: Ok(()) }) + // If there is also a default subcommand, we'd like to print the help for that subcommand as well. + if has_default_subcommand_parse { + let mut unwrapped_parse_subcommand = parse_subcommand.unwrap(); + // Do not print usage again for the subcommand. + let sub_parse = (&mut unwrapped_parse_subcommand.parse_func)( + &[cmd_name, &[subcmd_str]].concat(), + args, + false, + ) + .expect_err("bad parse func"); + let default_subcommand_help_msg = format!("When no subcommand is given, the command `{}` is used as the default. Allowing the additional:", unwrapped_parse_subcommand.default.unwrap()); + Err(EarlyExit { + output: format!( + "{}\n{}\n\n{}", + help_func(show_command_usage), + default_subcommand_help_msg, + &sub_parse.output + ), + status: Ok(()), + }) + } else { + Err(EarlyExit { output: help_func(show_command_usage), status: Ok(()) }) + } + } else if let Some(ref mut parse_subcommand) = parse_subcommand { + // If we have a potential subcommand and it is not parsed. + if let Some(default_subcommand) = parse_subcommand.default { + // Pass the args again to the default args. + // TODO(qhua948, #130): make this better, pass args without re-parsing. + parse_subcommand.parse(help, cmd_name, default_subcommand, args, true).map(|_| ()) + } else { + Ok(()) + } } else { Ok(()) } } +/// This function implements argument parsing for structs. +/// +/// `cmd_name`: The identifier for the current command. +/// `args`: The command line arguments. +/// `parse_options`: Helper to parse optional arguments. +/// `parse_positionals`: Helper to parse positional arguments. +/// `parse_subcommand`: Helper to parse a subcommand. +/// `help_func`: Generate a help message. +#[doc(hidden)] +pub fn parse_struct_args( + cmd_name: &[&str], + args: &[&str], + parse_options: ParseStructOptions<'_>, + parse_positionals: ParseStructPositionals<'_>, + parse_subcommand: Option>, + help_func: &dyn Fn(bool) -> String, +) -> Result<(), EarlyExit> { + parse_struct_args_show_command_usage( + cmd_name, + args, + parse_options, + parse_positionals, + parse_subcommand, + help_func, + true, + ) +} + #[doc(hidden)] pub struct ParseStructOptions<'a> { /// A mapping from option string literals to the entry @@ -765,7 +868,7 @@ impl<'a> ParseStructOptions<'a> { .arg_to_slot .iter() .find_map(|&(name, pos)| if name == arg { Some(pos) } else { None }) - .ok_or_else(|| unrecognized_argument(arg))?; + .ok_or_else(|| unrecognized_arg(arg))?; match self.slots[pos] { ParseStructOption::Flag(ref mut b) => b.set_flag(arg), @@ -785,10 +888,6 @@ impl<'a> ParseStructOptions<'a> { } } -fn unrecognized_argument(x: &str) -> String { - ["Unrecognized argument: ", x, "\n"].concat() -} - // `--` or `-` options, including a mutable reference to their value. #[doc(hidden)] pub enum ParseStructOption<'a> { @@ -870,7 +969,10 @@ pub struct ParseStructSubCommand<'a> { pub dynamic_subcommands: &'a [&'static CommandInfo], // The function to parse the subcommand arguments. - pub parse_func: &'a mut dyn FnMut(&[&str], &[&str]) -> Result<(), EarlyExit>, + pub parse_func: &'a mut dyn FnMut(&[&str], &[&str], bool) -> Result<(), EarlyExit>, + + // The default subcommand as a literal string, which should match one of the subcommand's "name" attribute. + pub default: Option<&'static str>, } impl<'a> ParseStructSubCommand<'a> { @@ -880,6 +982,7 @@ impl<'a> ParseStructSubCommand<'a> { cmd_name: &[&str], arg: &str, remaining_args: &[&str], + show_command_usage: bool, ) -> Result { for subcommand in self.subcommands.iter().chain(self.dynamic_subcommands.iter()) { if subcommand.name == arg { @@ -893,7 +996,7 @@ impl<'a> ParseStructSubCommand<'a> { remaining_args }; - (self.parse_func)(&command, remaining_args)?; + (self.parse_func)(&command, remaining_args, show_command_usage)?; return Ok(true); } diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs index c4e58a9..b1a2272 100644 --- a/argh/tests/lib.rs +++ b/argh/tests/lib.rs @@ -207,6 +207,416 @@ fn dynamic_subcommand_example() { ); } +#[test] +fn option_subcommand_ok() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand)] + nested: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let one = TopLevel::from_args(&["cmdname"], &["one", "--x", "2"]).expect("sc 1"); + assert_eq!(one, TopLevel { nested: Some(MySubCommandEnum::One(SubCommandOne { x: 2 })) },); +} + +#[test] +fn optional_subcommand_empty_show_help_no_default() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand)] + nested: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let help_str = r###"Usage: cmdname [] [] + +Top-level command. + +Options: + --help display usage information + +Commands: + one First subcommand. + two Second subcommand. +"###; + + match TopLevel::from_args(&["cmdname"], &["--help"]) { + Ok(_) => panic!("help was parsed as args"), + Err(e) => { + assert_eq!(help_str, e.output); + e.status.expect("help returned an error"); + } + } +} + +#[test] +fn optional_subcommand_empty_show_help_for_both() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand, default = "SubCommandOne")] + nested: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let help_str = r###"Usage: cmdname [] [] + +Top-level command. + +Options: + --help display usage information + +Commands: + one First subcommand. + two Second subcommand. + +When no subcommand is given, the command `one` is used as the default. Allowing the additional: + +First subcommand. + +Options: + --x how many x + --help display usage information +"###; + + match TopLevel::from_args(&["cmdname"], &["--help"]) { + Ok(_) => panic!("help was parsed as args"), + Err(e) => { + assert_eq!(help_str, e.output); + e.status.expect("help returned an error"); + } + } +} + +#[test] +fn option_subcommand_empty() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand)] + nested: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let one = TopLevel::from_args(&["cmdname"], &[]).expect("sc 1"); + assert_eq!(one, TopLevel { nested: None },); +} + +#[test] +fn option_subcommand_with_default() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand, default = "SubCommandOne")] + nested: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option, default = "1")] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let one = TopLevel::from_args(&["cmdname"], &[]).expect("sc 1"); + assert_eq!(one, TopLevel { nested: Some(MySubCommandEnum::One(SubCommandOne { x: 1 })) },); +} + +#[test] +fn option_subcommand_nested_multiple() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand, default = "MySubCommandStruct")] + nested: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + // Nested command. + #[argh(subcommand, name = "my", description = "")] + struct MySubCommandStruct { + #[argh(subcommand, default = "SubCommandOne")] + /// nested command. + nested_again: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option, default = "1")] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let one = TopLevel::from_args(&["cmdname"], &[]).expect("sc 1"); + assert_eq!( + one, + TopLevel { + nested: Some(MySubCommandStruct { + nested_again: Some(MySubCommandEnum::One(SubCommandOne { x: 1 })) + }) + } + ); +} + +#[test] +fn option_subcommand_nested_multiple_show_help_both() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand, default = "MySubCommandStruct")] + nested: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + // Nested command. + #[argh(subcommand, name = "my", description = "My Subcmd")] + struct MySubCommandStruct { + #[argh(subcommand, default = "SubCommandOne")] + /// nested command. + nested_again: Option, + + #[argh(option, default = "69", description = "delta")] + /// the delta + delta: i32, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option, default = "1")] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + let help_str = r###"Usage: cmdname [] [] + +Top-level command. + +Options: + --help display usage information + +Commands: + my My Subcmd + +When no subcommand is given, the command `my` is used as the default. Allowing the additional: + +My Subcmd + +Options: + --delta delta + --help display usage information + +Commands: + one First subcommand. + two Second subcommand. + +When no subcommand is given, the command `one` is used as the default. Allowing the additional: + +First subcommand. + +Options: + --x how many x + --help display usage information +"###; + + match TopLevel::from_args(&["cmdname"], &["--help"]) { + Ok(_) => panic!("help was parsed as args"), + Err(e) => { + assert_eq!(help_str, e.output); + e.status.expect("help returned an error"); + } + } +} + +#[test] +fn option_subcommand_with_default_and_args() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand, default = "SubCommandOne")] + nested: Option, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option, default = "1")] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let one = TopLevel::from_args(&["cmdname"], &["--x", "69"]).expect("sc 1"); + assert_eq!(one, TopLevel { nested: Some(MySubCommandEnum::One(SubCommandOne { x: 69 })) },); +} + #[test] fn multiline_doc_comment_description() { #[derive(FromArgs)] @@ -877,6 +1287,7 @@ Options: e.status.expect_err("should be an error"); } + // TODO(qhua948): undocumented feature of format! templating for {command_name} #[derive(FromArgs, PartialEq, Debug)] #[argh( description = "Destroy the contents of .", diff --git a/argh_derive/src/help.rs b/argh_derive/src/help.rs index 917c781..2ac4e22 100644 --- a/argh_derive/src/help.rs +++ b/argh_derive/src/help.rs @@ -26,35 +26,36 @@ pub(crate) fn help( fields: &[StructField<'_>], subcommand: Option<&StructField<'_>>, ) -> TokenStream { - let mut format_lit = "Usage: {command_name}".to_string(); + let mut usage_section = "Usage: {command_name}".to_string(); let positional = fields.iter().filter(|f| f.kind == FieldKind::Positional); let mut has_positional = false; for arg in positional.clone() { has_positional = true; - format_lit.push(' '); - positional_usage(&mut format_lit, arg); + usage_section.push(' '); + positional_usage(&mut usage_section, arg); } let options = fields.iter().filter(|f| f.long_name.is_some()); for option in options.clone() { - format_lit.push(' '); - option_usage(&mut format_lit, option); + usage_section.push(' '); + option_usage(&mut usage_section, option); } if let Some(subcommand) = subcommand { - format_lit.push(' '); + usage_section.push(' '); if !subcommand.optionality.is_required() { - format_lit.push('['); + usage_section.push('['); } - format_lit.push_str(""); + usage_section.push_str(""); if !subcommand.optionality.is_required() { - format_lit.push(']'); + usage_section.push(']'); } - format_lit.push_str(" []"); + usage_section.push_str(" []"); } - format_lit.push_str(SECTION_SEPARATOR); + usage_section.push_str(SECTION_SEPARATOR); + let mut format_lit = "".to_string(); let description = require_description(errors, Span::call_site(), &ty_attrs.description, "type"); format_lit.push_str(&description); @@ -114,10 +115,26 @@ pub(crate) fn help( format_lit.push('\n'); - quote! { { - #subcommand_calculation - format!(#format_lit, command_name = #cmd_name_str_array_ident.join(" "), #subcommand_format_arg) - } } + let has_format_template_command_name = format_lit.contains("{command_name}"); + let command_name_format_arg = quote! { command_name = #cmd_name_str_array_ident.join(" ") }; + let together = format!("{}{}", usage_section, format_lit); + + // We also allow user to template a `{command_name}` in their Notes/Examples. + let second_section_format_tokens = if has_format_template_command_name { + quote! { format!(#format_lit, #command_name_format_arg, #subcommand_format_arg) } + } else { + quote! { format!(#format_lit, #subcommand_format_arg) } + }; + + quote! { & |show_command_usage: bool| { + #subcommand_calculation + if show_command_usage{ + format!(#together, #command_name_format_arg, #subcommand_format_arg) + } else { + #second_section_format_tokens + } + } + } } /// A section composed of exactly just the literals provided to the program. diff --git a/argh_derive/src/lib.rs b/argh_derive/src/lib.rs index c35a269..566edcb 100644 --- a/argh_derive/src/lib.rs +++ b/argh_derive/src/lib.rs @@ -170,6 +170,7 @@ impl<'a> StructField<'a> { } FieldKind::SubCommand => { let inner = ty_inner(&["Option"], &field.ty); + // Whether or not the subcommand is optional. optionality = if inner.is_some() { Optionality::Optional } else { Optionality::None }; ty_without_wrapper = inner.unwrap_or(&field.ty); @@ -303,14 +304,21 @@ fn impl_from_args_struct_from_args<'a>( let parse_subcommands = if let Some(subcommand) = subcommand { let name = subcommand.name; let ty = subcommand.ty_without_wrapper; + let default_opt = if let Some(ref default) = subcommand.attrs.default { + let idt = default.parse_with(syn::Path::parse_mod_style).unwrap(); + quote! { Some ( <#idt as argh::SubCommand>::COMMAND.name ) } + } else { + quote! { None } + }; quote_spanned! { impl_span => Some(argh::ParseStructSubCommand { subcommands: <#ty as argh::SubCommands>::COMMANDS, dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(), - parse_func: &mut |__command, __remaining_args| { - #name = Some(<#ty as argh::FromArgs>::from_args(__command, __remaining_args)?); + parse_func: &mut |__command, __remaining_args, __show_command_usage| { + #name = Some(<#ty as argh::FromArgs>::from_args_show_command_usage(__command, __remaining_args, __show_command_usage)?); Ok(()) }, + default: #default_opt, }) } } else { @@ -322,14 +330,14 @@ fn impl_from_args_struct_from_args<'a>( let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand); let method_impl = quote_spanned! { impl_span => - fn from_args(__cmd_name: &[&str], __args: &[&str]) + fn from_args_show_command_usage(__cmd_name: &[&str], __args: &[&str], __show_command_usage: bool) -> std::result::Result { #![allow(clippy::unwrap_in_result)] #( #init_fields )* - argh::parse_struct_args( + argh::parse_struct_args_show_command_usage( __cmd_name, __args, argh::ParseStructOptions { @@ -348,7 +356,8 @@ fn impl_from_args_struct_from_args<'a>( last_is_repeating: #last_positional_is_repeating, }, #parse_subcommands, - &|| #help, + #help, + __show_command_usage, )?; let mut #missing_requirements_ident = argh::MissingRequirements::default(); @@ -412,14 +421,21 @@ fn impl_from_args_struct_redact_arg_values<'a>( let redact_subcommands = if let Some(subcommand) = subcommand { let name = subcommand.name; let ty = subcommand.ty_without_wrapper; + let default_opt = if let Some(ref default) = subcommand.attrs.default { + let idt = default.parse_with(syn::Path::parse_mod_style).unwrap(); + quote! { Some ( <#idt as argh::SubCommand>::COMMAND.name ) } + } else { + quote! { None } + }; quote_spanned! { impl_span => Some(argh::ParseStructSubCommand { subcommands: <#ty as argh::SubCommands>::COMMANDS, dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(), - parse_func: &mut |__command, __remaining_args| { - #name = Some(<#ty as argh::FromArgs>::redact_arg_values(__command, __remaining_args)?); + parse_func: &mut |__command, __remaining_args, __show_command_usage| { + #name = Some(<#ty as argh::FromArgs>::redact_arg_values_show_command_usage(__command, __remaining_args, __show_command_usage)?); Ok(()) }, + default: #default_opt, }) } } else { @@ -437,10 +453,10 @@ fn impl_from_args_struct_redact_arg_values<'a>( let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand); let method_impl = quote_spanned! { impl_span => - fn redact_arg_values(__cmd_name: &[&str], __args: &[&str]) -> std::result::Result, argh::EarlyExit> { + fn redact_arg_values_show_command_usage(__cmd_name: &[&str], __args: &[&str], __show_command_usage: bool) -> std::result::Result, argh::EarlyExit> { #( #init_fields )* - argh::parse_struct_args( + argh::parse_struct_args_show_command_usage( __cmd_name, __args, argh::ParseStructOptions { @@ -459,7 +475,8 @@ fn impl_from_args_struct_redact_arg_values<'a>( last_is_repeating: #last_positional_is_repeating, }, #redact_subcommands, - &|| #help, + #help, + __show_command_usage, )?; let mut #missing_requirements_ident = argh::MissingRequirements::default(); @@ -904,7 +921,7 @@ fn impl_from_args_enum( quote! { impl argh::FromArgs for #name { - fn from_args(command_name: &[&str], args: &[&str]) + fn from_args_show_command_usage(command_name: &[&str], args: &[&str], show_command_usage: bool) -> std::result::Result { let subcommand_name = if let Some(subcommand_name) = command_name.last() { @@ -916,7 +933,7 @@ fn impl_from_args_enum( #( if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name { return Ok(#name_repeating::#variant_names( - <#variant_ty as argh::FromArgs>::from_args(command_name, args)? + <#variant_ty as argh::FromArgs>::from_args_show_command_usage(command_name, args, show_command_usage)? )); } )* @@ -926,7 +943,7 @@ fn impl_from_args_enum( Err(argh::EarlyExit::from("no subcommand matched".to_owned())) } - fn redact_arg_values(command_name: &[&str], args: &[&str]) -> std::result::Result, argh::EarlyExit> { + fn redact_arg_values_show_command_usage(command_name: &[&str], args: &[&str], show_command_usage: bool) -> std::result::Result, argh::EarlyExit> { let subcommand_name = if let Some(subcommand_name) = command_name.last() { *subcommand_name } else { @@ -935,7 +952,7 @@ fn impl_from_args_enum( #( if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name { - return <#variant_ty as argh::FromArgs>::redact_arg_values(command_name, args); + return <#variant_ty as argh::FromArgs>::redact_arg_values_show_command_usage(command_name, args, show_command_usage); } )* diff --git a/argh_derive/src/parse_attrs.rs b/argh_derive/src/parse_attrs.rs index f229862..05d1e4b 100644 --- a/argh_derive/src/parse_attrs.rs +++ b/argh_derive/src/parse_attrs.rs @@ -135,11 +135,11 @@ impl FieldAttrs { if let (Some(default), Some(field_type)) = (&this.default, &this.field_type) { match field_type.kind { - FieldKind::Option | FieldKind::Positional => {} - FieldKind::SubCommand | FieldKind::Switch => errors.err( + FieldKind::Option | FieldKind::Positional | FieldKind::SubCommand => {} + FieldKind::Switch => errors.err( default, - "`default` may only be specified on `#[argh(option)]` \ - or `#[argh(positional)]` fields", + "`default` may only be specified on `#[argh(option)],` \ + `#[argh(positional)]` or `#[argh(subcommand)]` fields", ), } }