diff --git a/README.md b/README.md index 7368162..8a03f94 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Options: -j, --jump whether or not to jump --height how high to go --pilot-nickname an optional nickname for the pilot - --help display usage information + --help, help display usage information ``` The resulting program can then be used in any of these ways: diff --git a/argh/src/lib.rs b/argh/src/lib.rs index 3cbfa5f..46b1fc0 100644 --- a/argh/src/lib.rs +++ b/argh/src/lib.rs @@ -45,7 +45,7 @@ //! -j, --jump whether or not to jump //! --height how high to go //! --pilot-nickname an optional nickname for the pilot -//! --help display usage information +//! --help, help display usage information //! ``` //! //! The resulting program can then be used in any of these ways: @@ -70,6 +70,7 @@ //! //! #[derive(FromArgs)] //! /// Reach new heights. +//! #[argh(help_triggers("-h", "--help", "help"))] //! struct GoUp { //! /// an optional nickname for the pilot //! #[argh(option)] @@ -421,7 +422,7 @@ pub trait FromArgs: Sized { /// Command to manage a classroom. /// /// Options: - /// --help display usage information + /// --help, help display usage information /// /// Commands: /// list list all the classes. @@ -445,7 +446,7 @@ pub trait FromArgs: Sized { /// /// Options: /// --teacher-name list classes for only this teacher. - /// --help display usage information + /// --help, help display usage information /// "#.to_string(), /// status: Ok(()), /// }, @@ -587,7 +588,7 @@ pub trait FromArgs: Sized { /// Command to manage a classroom. /// /// Options: - /// --help display usage information + /// --help, help display usage information /// /// Commands: /// list list all the classes. @@ -912,7 +913,7 @@ pub fn parse_struct_args( 'parse_args: while let Some(&next_arg) = remaining_args.first() { remaining_args = &remaining_args[1..]; - if (next_arg == "--help" || next_arg == "help") && !options_ended { + if (parse_options.help_triggers.contains(&next_arg)) && !options_ended { help = true; continue; } @@ -959,6 +960,9 @@ pub struct ParseStructOptions<'a> { /// The storage for argument output data. pub slots: &'a mut [ParseStructOption<'a>], + + /// help triggers is a list of strings that trigger printing of help + pub help_triggers: &'a [&'a str], } impl<'a> ParseStructOptions<'a> { diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs index 0f3998a..d6a2fef 100644 --- a/argh/tests/lib.rs +++ b/argh/tests/lib.rs @@ -81,6 +81,29 @@ fn custom_from_str_example() { assert_eq!(f.five, 5); } +#[test] +fn help_trigger_example() { + /// Height options + #[derive(FromArgs)] + #[argh(help_triggers("-h", "--help", "help"))] + struct Height { + /// how high to go + #[argh(option)] + _height: usize, + } + + assert_help_string::( + r#"Usage: test_arg_0 --height + +Height options + +Options: + --height how high to go + -h, --help, help display usage information +"#, + ); +} + #[test] fn nested_from_str_example() { #[derive(FromArgs)] @@ -298,7 +321,7 @@ Short description Options: --s a switch with a description that is spread across a number of lines of comments. - --help display usage information + --help, help display usage information "###, ); } @@ -322,7 +345,7 @@ A \description: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~\ Options: --s a \description: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~\ - --help display usage information + --help, help display usage information "###, ); } @@ -459,7 +482,7 @@ Woot Options: -n, --n fooey - --help display usage information + --help, help display usage information "###, ); } @@ -481,7 +504,7 @@ Woot Options: --option-name fooey - --help display usage information + --help, help display usage information "###, ); } @@ -519,7 +542,7 @@ Positional Arguments: b fooey Options: - --help display usage information + --help, help display usage information "###, ); } @@ -603,7 +626,7 @@ Positional Arguments: Options: --b woo --c stuff - --help display usage information + --help, help display usage information "###, ); } @@ -1012,7 +1035,7 @@ mod fuchsia_commandline_tools_rubric { A type for testing `--help`/`help` Options: - --help display usage information + --help, help display usage information Commands: first First subcommmand for testing `help`. @@ -1023,7 +1046,7 @@ Commands: First subcommmand for testing `help`. Options: - --help display usage information + --help, help display usage information Commands: second Second subcommand for testing `help`. @@ -1034,7 +1057,7 @@ Commands: Second subcommand for testing `help`. Options: - --help display usage information + --help, help display usage information "###; #[test] @@ -1215,7 +1238,7 @@ Options: documentation -s, --scribble write repeatedly -v, --verbose say more. Defaults to $BLAST_VERBOSE. - --help display usage information + --help, help display usage information Commands: blow-up explosively separate @@ -1255,7 +1278,7 @@ Positional Arguments: name Options: - --help display usage information + --help, help display usage information "###, ); } @@ -1285,7 +1308,7 @@ Positional Arguments: two this one is real Options: - --help display usage information + --help, help display usage information "###, ); } @@ -1648,7 +1671,7 @@ Woot Options: -n, --n fooey - --help display usage information + --help, help display usage information "### .to_owned(), status: Ok(()), diff --git a/argh_derive/src/help.rs b/argh_derive/src/help.rs index 68a427a..3f52331 100644 --- a/argh_derive/src/help.rs +++ b/argh_derive/src/help.rs @@ -26,6 +26,7 @@ pub(crate) fn help( ty_attrs: &TypeAttrs, fields: &[StructField<'_>], subcommand: Option<&StructField<'_>>, + help_triggers: &[String], ) -> TokenStream { let mut format_lit = "Usage: {command_name}".to_string(); @@ -83,8 +84,12 @@ pub(crate) fn help( for option in options { option_description(errors, &mut format_lit, option); } - // Also include "help" - option_description_format(&mut format_lit, None, "--help", "display usage information"); + option_description_format( + &mut format_lit, + None, + &help_triggers.join(", "), + "display usage information", + ); let subcommand_calculation; let subcommand_format_arg; diff --git a/argh_derive/src/lib.rs b/argh_derive/src/lib.rs index 6eff785..4cbfcc3 100644 --- a/argh_derive/src/lib.rs +++ b/argh_derive/src/lib.rs @@ -370,10 +370,12 @@ fn impl_from_args_struct_from_args<'a>( quote_spanned! { impl_span => None } }; + let help_triggers = get_help_triggers(type_attrs); + let help = if cfg!(feature = "help") { // Identifier referring to a value containing the name of the current command as an `&[&str]`. let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span); - help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand) + help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand, &help_triggers) } else { quote! { String::new() } }; @@ -392,6 +394,7 @@ fn impl_from_args_struct_from_args<'a>( argh::ParseStructOptions { arg_to_slot: &[ #( #flag_str_to_output_table_map ,)* ], slots: &mut [ #( #flag_output_table, )* ], + help_triggers: &[ #( #help_triggers ),* ], }, argh::ParseStructPositionals { positionals: &mut [ @@ -424,6 +427,29 @@ fn impl_from_args_struct_from_args<'a>( method_impl } +/// get help triggers vector from type_attrs.help_triggers as a [`Vec`] +/// +/// Defaults to vec!["--help", "help"] if type_attrs.help_triggers is None +fn get_help_triggers(type_attrs: &TypeAttrs) -> Vec { + let help_triggers = type_attrs.help_triggers.as_ref().map_or_else( + || vec!["--help".to_owned(), "help".to_owned()], + |s| { + s.iter() + .filter_map(|s| { + let trigger = s.value(); + let trigger_trimmed = trigger.trim().to_owned(); + if trigger_trimmed.is_empty() { + None + } else { + Some(trigger_trimmed) + } + }) + .collect::>() + }, + ); + help_triggers +} + fn impl_from_args_struct_redact_arg_values<'a>( errors: &Errors, type_attrs: &TypeAttrs, @@ -494,10 +520,12 @@ fn impl_from_args_struct_redact_arg_values<'a>( quote! { "no subcommand name" } }; + let help_triggers = get_help_triggers(type_attrs); + let help = if cfg!(feature = "help") { // Identifier referring to a value containing the name of the current command as an `&[&str]`. let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span); - help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand) + help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand, &help_triggers) } else { quote! { String::new() } }; @@ -512,6 +540,7 @@ fn impl_from_args_struct_redact_arg_values<'a>( argh::ParseStructOptions { arg_to_slot: &[ #( #flag_str_to_output_table_map ,)* ], slots: &mut [ #( #flag_output_table, )* ], + help_triggers: &[ #( #help_triggers ),* ], }, argh::ParseStructPositionals { positionals: &mut [ diff --git a/argh_derive/src/parse_attrs.rs b/argh_derive/src/parse_attrs.rs index a83ce5e..39d2fba 100644 --- a/argh_derive/src/parse_attrs.rs +++ b/argh_derive/src/parse_attrs.rs @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +use syn::{parse::Parser, punctuated::Punctuated}; + use { crate::errors::Errors, proc_macro2::Span, @@ -271,6 +273,8 @@ pub struct TypeAttrs { pub examples: Vec, pub notes: Vec, pub error_codes: Vec<(syn::LitInt, syn::LitStr)>, + /// Arguments that trigger printing of the help message + pub help_triggers: Option>, } impl TypeAttrs { @@ -317,6 +321,10 @@ impl TypeAttrs { { this.parse_attr_subcommand(errors, ident); } + } else if name.is_ident("help_triggers") { + if let Some(m) = errors.expect_meta_list(&meta) { + Self::parse_help_triggers(m, errors, &mut this); + } } else { errors.err( &meta, @@ -405,6 +413,24 @@ impl TypeAttrs { self.is_subcommand = Some(ident.clone()); } } + + // get the list of arguments that trigger printing of the help message as a vector of strings (help_arguments("-h", "--help", "help")) + fn parse_help_triggers(m: &syn::MetaList, errors: &Errors, this: &mut TypeAttrs) { + let parser = Punctuated::::parse_terminated; + match parser.parse(m.tokens.clone().into()) { + Ok(args) => { + let mut triggers = Vec::new(); + for arg in args { + if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) = arg { + triggers.push(lit_str); + } + } + + this.help_triggers = Some(triggers); + } + Err(err) => errors.push(err), + } + } } /// Represents an enum variant's attributes. @@ -604,7 +630,8 @@ fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Op /// Checks that a `#![derive(FromArgs)]` enum has an `#[argh(subcommand)]` /// attribute and that it does not have any other type-level `#[argh(...)]` attributes. pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: &Span) { - let TypeAttrs { is_subcommand, name, description, examples, notes, error_codes } = type_attrs; + let TypeAttrs { is_subcommand, name, description, examples, notes, error_codes, help_triggers } = + type_attrs; // Ensure that `#[argh(subcommand)]` is present. if is_subcommand.is_none() { @@ -635,6 +662,11 @@ pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: if let Some(err_code) = error_codes.first() { err_unused_enum_attr(errors, &err_code.0); } + if let Some(triggers) = help_triggers { + if let Some(trigger) = triggers.first() { + err_unused_enum_attr(errors, trigger); + } + } } fn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) {