Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for custom help triggers #106

Merged
merged 1 commit into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 9 additions & 5 deletions argh/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)]
Expand Down Expand Up @@ -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.
Expand All @@ -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(()),
/// },
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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> {
Expand Down
49 changes: 36 additions & 13 deletions argh/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Height>(
r#"Usage: test_arg_0 --height <height>

Height options

Options:
--height how high to go
-h, --help, help display usage information
"#,
);
}

#[test]
fn nested_from_str_example() {
#[derive(FromArgs)]
Expand Down Expand Up @@ -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
"###,
);
}
Expand All @@ -322,7 +345,7 @@ A \description: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~\

Options:
--s a \description: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~\
--help display usage information
--help, help display usage information
"###,
);
}
Expand Down Expand Up @@ -459,7 +482,7 @@ Woot

Options:
-n, --n fooey
--help display usage information
--help, help display usage information
"###,
);
}
Expand All @@ -481,7 +504,7 @@ Woot

Options:
--option-name fooey
--help display usage information
--help, help display usage information
"###,
);
}
Expand Down Expand Up @@ -519,7 +542,7 @@ Positional Arguments:
b fooey

Options:
--help display usage information
--help, help display usage information
"###,
);
}
Expand Down Expand Up @@ -603,7 +626,7 @@ Positional Arguments:
Options:
--b woo
--c stuff
--help display usage information
--help, help display usage information
"###,
);
}
Expand Down Expand Up @@ -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`.
Expand All @@ -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`.
Expand All @@ -1034,7 +1057,7 @@ Commands:
Second subcommand for testing `help`.

Options:
--help display usage information
--help, help display usage information
"###;

#[test]
Expand Down Expand Up @@ -1215,7 +1238,7 @@ Options:
documentation
-s, --scribble write <scribble> repeatedly
-v, --verbose say more. Defaults to $BLAST_VERBOSE.
--help display usage information
--help, help display usage information

Commands:
blow-up explosively separate
Expand Down Expand Up @@ -1255,7 +1278,7 @@ Positional Arguments:
name

Options:
--help display usage information
--help, help display usage information
"###,
);
}
Expand Down Expand Up @@ -1285,7 +1308,7 @@ Positional Arguments:
two this one is real

Options:
--help display usage information
--help, help display usage information
"###,
);
}
Expand Down Expand Up @@ -1648,7 +1671,7 @@ Woot

Options:
-n, --n fooey
--help display usage information
--help, help display usage information
"###
.to_owned(),
status: Ok(()),
Expand Down
9 changes: 7 additions & 2 deletions argh_derive/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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;
Expand Down
33 changes: 31 additions & 2 deletions argh_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
};
Expand All @@ -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 [
Expand Down Expand Up @@ -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<String>`]
///
/// Defaults to vec!["--help", "help"] if type_attrs.help_triggers is None
fn get_help_triggers(type_attrs: &TypeAttrs) -> Vec<String> {
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::<Vec<_>>()
},
);
help_triggers
}

fn impl_from_args_struct_redact_arg_values<'a>(
errors: &Errors,
type_attrs: &TypeAttrs,
Expand Down Expand Up @@ -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() }
};
Expand All @@ -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 [
Expand Down
34 changes: 33 additions & 1 deletion argh_derive/src/parse_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -271,6 +273,8 @@ pub struct TypeAttrs {
pub examples: Vec<syn::LitStr>,
pub notes: Vec<syn::LitStr>,
pub error_codes: Vec<(syn::LitInt, syn::LitStr)>,
/// Arguments that trigger printing of the help message
pub help_triggers: Option<Vec<syn::LitStr>>,
}

impl TypeAttrs {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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::<syn::Expr, syn::Token![,]>::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.
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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) {
Expand Down
Loading