Address comments

* Renames some functions
* Flesh out docstrings for `from_args` and `redact_arg_values`
* Rename `<<<arg0>>>` to `program-name`
This commit is contained in:
Erick Tryzelaar 2021-07-07 17:00:16 -07:00 committed by Erick Tryzelaar
parent 8b4745dcd8
commit d98b6be239
3 changed files with 148 additions and 13 deletions

View File

@ -378,8 +378,8 @@ fn impl_from_args_struct_redact_arg_values<'a>(
type_attrs: &TypeAttrs,
fields: &'a [StructField<'a>],
) -> TokenStream {
let init_fields = declare_local_storage_for_redact_arg_values_fields(&fields);
let unwrap_fields = unwrap_redact_arg_values_fields(&fields);
let init_fields = declare_local_storage_for_redacted_fields(&fields);
let unwrap_fields = unwrap_redacted_fields(&fields);
let positional_fields: Vec<&StructField<'_>> =
fields.iter().filter(|field| field.kind == FieldKind::Positional).collect();
@ -613,7 +613,7 @@ fn unwrap_from_args_fields<'a>(fields: &'a [StructField<'a>]) -> impl Iterator<I
/// Most fields are stored in `Option<FieldType>` locals.
/// `argh(option)` fields are stored in a `ParseValueSlotTy` along with a
/// function that knows how to decode the appropriate value.
fn declare_local_storage_for_redact_arg_values_fields<'a>(
fn declare_local_storage_for_redacted_fields<'a>(
fields: &'a [StructField<'a>],
) -> impl Iterator<Item = TokenStream> + 'a {
fields.iter().map(|field| {
@ -664,7 +664,7 @@ fn declare_local_storage_for_redact_arg_values_fields<'a>(
}
/// Unwrap non-optional fields and take options out of their tuple slots.
fn unwrap_redact_arg_values_fields<'a>(fields: &'a [StructField<'a>]) -> impl Iterator<Item = TokenStream> + 'a {
fn unwrap_redacted_fields<'a>(fields: &'a [StructField<'a>]) -> impl Iterator<Item = TokenStream> + 'a {
fields.iter().map(|field| {
let field_name = field.name;

View File

@ -182,19 +182,135 @@ pub type CommandInfo = argh_shared::CommandInfo<'static>;
pub trait FromArgs: Sized {
/// Construct the type from an input set of arguments.
///
/// The first argument `command_name` is the identifier for the current command, treating each
/// segment as a separate item. This is used by in the output of `--help` in order to print out
/// how to run the command or a nested subcommand.
/// The first argument `command_name` is the identifier for the current command. In most cases,
/// users should only pass in a single item for the command name, which typically comes from
/// the first item from `std::env::args()`. Implementations however should append the
/// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This
/// allows `argh` to generate correct subcommand help strings.
///
/// The second argument `args` is the rest of the command line arguments.
///
/// # Examples
///
/// ```rust
/// # use argh::FromArgs;
///
/// /// Command to manage a classroom.
/// #[derive(Debug, PartialEq, FromArgs)]
/// struct ClassroomCmd {
/// #[argh(subcommand)]
/// subcommands: Subcommands,
/// }
///
/// #[derive(Debug, PartialEq, FromArgs)]
/// #[argh(subcommand)]
/// enum Subcommands {
/// List(ListCmd),
/// Add(AddCmd),
/// }
///
/// /// list all the classes.
/// #[derive(Debug, PartialEq, FromArgs)]
/// #[argh(subcommand, name = "list")]
/// struct ListCmd {
/// /// list classes for only this teacher.
/// #[argh(option)]
/// teacher_name: Option<String>,
/// }
///
/// /// add students to a class.
/// #[derive(Debug, PartialEq, FromArgs)]
/// #[argh(subcommand, name = "add")]
/// struct AddCmd {
/// /// the name of the class's teacher.
/// #[argh(option)]
/// teacher_name: String,
///
/// /// the name of the class.
/// #[argh(positional)]
/// class_name: String,
/// }
///
/// let args = ClassroomCmd::from_args(
/// &["classroom"],
/// &["list", "--teacher-name", "Smith"],
/// ).unwrap();
/// assert_eq!(
/// args,
/// ClassroomCmd {
/// subcommands: Subcommands::List(ListCmd {
/// teacher_name: Some("Smith".to_string()),
/// })
/// },
/// );
///
/// // Help returns an error, but internally returns an `Ok` status.
/// let early_exit = ClassroomCmd::from_args(
/// &["classroom"],
/// &["help"],
/// ).unwrap_err();
/// assert_eq!(
/// early_exit,
/// argh::EarlyExit {
/// output: r#"Usage: classroom <command> [<args>]
///
/// Command to manage a classroom.
///
/// Options:
/// --help display usage information
///
/// Commands:
/// list list all the classes.
/// add add students to a class.
/// "#.to_string(),
/// status: Ok(()),
/// },
/// );
///
/// // Help works with subcommands.
/// let early_exit = ClassroomCmd::from_args(
/// &["classroom"],
/// &["list", "help"],
/// ).unwrap_err();
/// assert_eq!(
/// early_exit,
/// argh::EarlyExit {
/// output: r#"Usage: classroom list [--teacher-name <teacher-name>]
///
/// list all the classes.
///
/// Options:
/// --teacher-name list classes for only this teacher.
/// --help display usage information
/// "#.to_string(),
/// status: Ok(()),
/// },
/// );
///
/// // Incorrect arguments will error out.
/// let err = ClassroomCmd::from_args(
/// &["classroom"],
/// &["lisp"],
/// ).unwrap_err();
/// assert_eq!(
/// err,
/// argh::EarlyExit {
/// output: "Unrecognized argument: lisp\n".to_string(),
/// status: Err(()),
/// },
/// );
/// ```
fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit>;
/// 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
/// anonymous usage statistics without revealing the content entered by the end user.
///
/// The first argument `command_name` is the identifier for the current command, treating each
/// segment as a separate item. This is used in the error string if the arguments fail to parse.
/// The first argument `command_name` is the identifier for the current command. In most cases,
/// users should only pass in a single item for the command name, which typically comes from
/// the first item from `std::env::args()`. Implementations however should append the
/// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This
/// allows `argh` to generate correct subcommand help strings.
///
/// The second argument `args` is the rest of the command line arguments.
///
@ -248,8 +364,8 @@ pub trait FromArgs: Sized {
/// }
///
/// let args = ClassroomCmd::redact_arg_values(
/// &["classroom"],
/// &["list"],
/// &["classroom"],
/// &["list"],
/// ).unwrap();
/// assert_eq!(
/// args,
@ -297,6 +413,25 @@ pub trait FromArgs: Sized {
/// status: Err(()),
/// }),
/// );
///
/// // `ClassroomCmd::redact_arg_values` will generate help messages.
/// assert_eq!(
/// ClassroomCmd::redact_arg_values(&["classroom"], &["help"]),
/// Err(argh::EarlyExit {
/// output: r#"Usage: classroom <command> [<args>]
///
/// Command to manage a classroom.
///
/// Options:
/// --help display usage information
///
/// Commands:
/// list list all the classes.
/// add add students to a class.
/// "#.to_string(),
/// status: Ok(()),
/// }),
/// );
/// ```
fn redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result<Vec<String>, EarlyExit> {
Ok(vec!["<<REDACTED>>".into()])

View File

@ -808,7 +808,7 @@ Options:
#[test]
fn example_parses_correctly() {
let help_example = HelpExample::from_args(
&["<<<arg0>>>"],
&["program-name"],
&["-f", "--scribble", "fooey", "blow-up", "--safely"],
)
.unwrap();
@ -827,7 +827,7 @@ Options:
#[test]
fn example_errors_on_missing_required_option_and_missing_required_subcommand() {
let exit = HelpExample::from_args(&["<<<arg0>>>"], &[]).unwrap_err();
let exit = HelpExample::from_args(&["program-name"], &[]).unwrap_err();
exit.status.unwrap_err();
assert_eq!(
exit.output,