From 00b73fbb09f989dc1986ee2c27f2260619a02cb3 Mon Sep 17 00:00:00 2001 From: Ian McKellar Date: Tue, 23 Dec 2025 14:28:51 -0800 Subject: [PATCH 1/2] Add support for synonyms in subcommands and options This commit introduces the `synonyms` attribute to `#[argh()]`, allowing users to specify alternative names for subcommands, options, and switches. Synonyms are hidden from the help output but are accepted during parsing. Key changes: - Added `synonyms = ["..."]` attribute support in argh_derive. - Extended `SubCommand` trait with `SYNONYMS` constant. - Extended `SubCommands` trait with `COMMAND_SYNONYMS` constant. - Updated `ParseStructSubCommand::parse` to check synonyms. - Updated documentation with examples. This implementation avoids breaking changes to the public `CommandInfo` API by storing synonym information in the `SubCommand` trait instead. --- README.md | 35 +++++++++++++ argh/src/lib.rs | 90 +++++++++++++++++++++++++++++----- argh/tests/args_info_tests.rs | 10 ++-- argh/tests/synonyms.rs | 77 +++++++++++++++++++++++++++++ argh_derive/Cargo.toml | 2 +- argh_derive/src/lib.rs | 55 +++++++++++++++++---- argh_derive/src/parse_attrs.rs | 51 +++++++++++++++++-- 7 files changed, 288 insertions(+), 32 deletions(-) create mode 100644 argh/tests/synonyms.rs diff --git a/README.md b/README.md index 8a03f94..7d6bbd6 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,41 @@ struct SubCommandTwo { } ``` +## Synonyms + +Synonyms can be specified for subcommands, options, and switches using the `synonyms` attribute. +These synonyms will be hidden from the help output but will be accepted during parsing. + +```rust +use argh::FromArgs; + +#[derive(FromArgs)] +/// A command with synonyms. +struct Cmd { + /// an option with synonyms + #[argh(option, synonyms = ["bar", "baz"])] + foo: String, + + #[argh(subcommand)] + nested: MySubCommandEnum, +} + +#[derive(FromArgs)] +#[argh(subcommand)] +enum MySubCommandEnum { + One(SubCommandOne), +} + +#[derive(FromArgs)] +/// First subcommand. +#[argh(subcommand, name = "one", synonyms = ["uno", "eins"])] +struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, +} +``` + NOTE: This is not an officially supported Google product. diff --git a/argh/src/lib.rs b/argh/src/lib.rs index 7decc64..89d1ccb 100644 --- a/argh/src/lib.rs +++ b/argh/src/lib.rs @@ -220,6 +220,41 @@ //! } //! ``` //! +//! ## Synonyms +//! +//! Synonyms can be specified for subcommands, options, and switches using the `synonyms` attribute. +//! These synonyms will be hidden from the help output but will be accepted during parsing. +//! +//! ```rust +//! use argh::FromArgs; +//! +//! #[derive(FromArgs)] +//! /// A command with synonyms. +//! struct Cmd { +//! /// an option with synonyms +//! #[argh(option, synonyms = ["bar", "baz"])] +//! foo: String, +//! +//! #[argh(subcommand)] +//! nested: MySubCommandEnum, +//! } +//! +//! #[derive(FromArgs)] +//! #[argh(subcommand)] +//! enum MySubCommandEnum { +//! One(SubCommandOne), +//! } +//! +//! #[derive(FromArgs)] +//! /// First subcommand. +//! #[argh(subcommand, name = "one", synonyms = ["uno", "eins"])] +//! struct SubCommandOne { +//! #[argh(option)] +//! /// how many x +//! x: usize, +//! } +//! ``` +//! //! You can also discover subcommands dynamically at runtime. To do this, //! declare subcommands as usual and add a variant to the enum with the //! `dynamic` attribute. Instead of deriving `FromArgs`, the value inside the @@ -649,6 +684,9 @@ pub trait SubCommands: FromArgs { /// Info for the commands. const COMMANDS: &'static [&'static CommandInfo]; + /// Synonyms for the commands. + const COMMAND_SYNONYMS: &'static [(&'static str, &'static [&'static str])] = &[]; + /// Get a list of commands that are discovered at runtime. fn dynamic_commands() -> &'static [&'static CommandInfo] { &[] @@ -657,16 +695,23 @@ pub trait SubCommands: FromArgs { impl SubCommands for T { const COMMANDS: &'static [&'static CommandInfo] = &[T::COMMAND]; + const COMMAND_SYNONYMS: &'static [(&'static str, &'static [&'static str])] = &[ + (T::COMMAND.name, T::SYNONYMS), + ]; } /// A `FromArgs` implementation that represents a single subcommand. pub trait SubCommand: FromArgs { /// Information about the subcommand. const COMMAND: &'static CommandInfo; + + /// Synonyms for the subcommand. + const SYNONYMS: &'static [&'static str] = &[]; } impl SubCommand for Box { const COMMAND: &'static CommandInfo = T::COMMAND; + const SYNONYMS: &'static [&'static str] = T::SYNONYMS; } /// Trait implemented by values returned from a dynamic subcommand handler. @@ -1151,6 +1196,9 @@ pub struct ParseStructSubCommand<'a> { // The subcommand commands pub subcommands: &'static [&'static CommandInfo], + // The subcommand synonyms + pub command_synonyms: &'static [(&'static str, &'static [&'static str])], + pub dynamic_subcommands: &'a [&'static CommandInfo], // The function to parse the subcommand arguments. @@ -1170,24 +1218,40 @@ impl ParseStructSubCommand<'_> { if subcommand.name == arg || arg.chars().count() == 1 && arg.chars().next().unwrap() == *subcommand.short { - let mut command = cmd_name.to_owned(); - command.push(subcommand.name); - let prepended_help; - let remaining_args = if help { - prepended_help = prepend_help(remaining_args); - &prepended_help - } else { - remaining_args - }; - - (self.parse_func)(&command, remaining_args)?; - - return Ok(true); + return self.parse_subcommand(help, cmd_name, subcommand.name, remaining_args); + } + } + + for (name, synonyms) in self.command_synonyms { + if synonyms.contains(&arg) { + return self.parse_subcommand(help, cmd_name, name, remaining_args); } } Ok(false) } + + fn parse_subcommand( + &mut self, + help: bool, + cmd_name: &[&str], + subcommand_name: &str, + remaining_args: &[&str], + ) -> Result { + let mut command = cmd_name.to_owned(); + command.push(subcommand_name); + let prepended_help; + let remaining_args = if help { + prepended_help = prepend_help(remaining_args); + &prepended_help + } else { + remaining_args + }; + + (self.parse_func)(&command, remaining_args)?; + + Ok(true) + } } // Prepend `help` to a list of arguments. diff --git a/argh/tests/args_info_tests.rs b/argh/tests/args_info_tests.rs index a1b458d..84da78b 100644 --- a/argh/tests/args_info_tests.rs +++ b/argh/tests/args_info_tests.rs @@ -379,7 +379,7 @@ fn args_info_test_notes_examples_errors() { one two three then a blank - + and one last line with "quoted text"."##, example = r##" Use the command with 1 file: @@ -387,7 +387,7 @@ fn args_info_test_notes_examples_errors() { Use it with a "wildcard": `{command_name} /path/to/*` a blank line - + and one last line with "quoted text"."##, error_code(0, "Success"), error_code(1, "General Error"), @@ -403,7 +403,7 @@ fn args_info_test_notes_examples_errors() { name: "NotesExamplesErrors", short: &'\0', description: "Command with Examples and usage Notes, including error codes.", - examples: &["\n Use the command with 1 file:\n `{command_name} /path/to/file`\n Use it with a \"wildcard\":\n `{command_name} /path/to/*`\n a blank line\n \n and one last line with \"quoted text\"."], + examples: &["\n Use the command with 1 file:\n `{command_name} /path/to/file`\n Use it with a \"wildcard\":\n `{command_name} /path/to/*`\n a blank line\n\n and one last line with \"quoted text\"."], flags: &[HELP_FLAG ], positionals: &[ @@ -414,7 +414,7 @@ fn args_info_test_notes_examples_errors() { hidden:false } ], - notes: &["\n These usage notes appear for {command_name} and how to best use it.\n The formatting should be preserved.\n one\n two\n three then a blank\n \n and one last line with \"quoted text\"."], + notes: &["\n These usage notes appear for {command_name} and how to best use it.\n The formatting should be preserved.\n one\n two\n three then a blank\n\n and one last line with \"quoted text\"."], error_codes: & [ErrorCodeInfo { code: 0, description: "Success" }, ErrorCodeInfo { code: 1, description: "General Error" }, ErrorCodeInfo { code: 2, description: "Some error with \"quotes\"" }], ..Default::default() }); @@ -769,7 +769,7 @@ fn args_info_test_example() { SubCommandInfo { name: "blow-up", command: CommandInfoWithArgs { name: "blow-up", short: &'\0', - description: "explosively separate", + description: "explosively separate", flags:& [HELP_FLAG, FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--safely", short: None, description: "blow up bombs safely", hidden:false } diff --git a/argh/tests/synonyms.rs b/argh/tests/synonyms.rs new file mode 100644 index 0000000..80e2551 --- /dev/null +++ b/argh/tests/synonyms.rs @@ -0,0 +1,77 @@ +#![cfg(feature = "help")] + +use argh::FromArgs; + +#[test] +fn test_subcommand_synonyms() { + #[derive(FromArgs, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand)] + nested: MySubCommandEnum, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one", synonyms = ["uno", "eins"])] + struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, + } + + let one = TopLevel::from_args(&["cmdname"], &["one", "--x", "2"]).expect("sc 1"); + assert_eq!(one, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) }); + + let uno = TopLevel::from_args(&["cmdname"], &["uno", "--x", "2"]).expect("sc uno"); + assert_eq!(uno, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) }); + + let eins = TopLevel::from_args(&["cmdname"], &["eins", "--x", "2"]).expect("sc eins"); + assert_eq!(eins, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) }); +} + +#[test] +fn test_option_synonyms() { + #[derive(FromArgs, PartialEq, Debug)] + /// Command with option synonyms. + struct Cmd { + #[argh(option, long = "foo", synonyms = ["bar", "baz"])] + /// foo option + foo: String, + } + + let a = Cmd::from_args(&["cmd"], &["--foo", "value"]).unwrap(); + assert_eq!(a.foo, "value"); + + let b = Cmd::from_args(&["cmd"], &["--bar", "value"]).unwrap(); + assert_eq!(b.foo, "value"); + + let c = Cmd::from_args(&["cmd"], &["--baz", "value"]).unwrap(); + assert_eq!(c.foo, "value"); +} + +#[test] +fn test_switch_synonyms() { + #[derive(FromArgs, PartialEq, Debug)] + /// Command with switch synonyms. + struct Cmd { + #[argh(switch, long = "foo", synonyms = ["bar", "baz"])] + /// foo switch + foo: bool, + } + + let a = Cmd::from_args(&["cmd"], &["--foo"]).unwrap(); + assert!(a.foo); + + let b = Cmd::from_args(&["cmd"], &["--bar"]).unwrap(); + assert!(b.foo); + + let c = Cmd::from_args(&["cmd"], &["--baz"]).unwrap(); + assert!(c.foo); +} diff --git a/argh_derive/Cargo.toml b/argh_derive/Cargo.toml index 023d9a7..be71f56 100644 --- a/argh_derive/Cargo.toml +++ b/argh_derive/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = "2.0" +syn = { version = "2.0", features = ["full"] } argh_shared.workspace = true [features] diff --git a/argh_derive/src/lib.rs b/argh_derive/src/lib.rs index ff439d0..b4ba96b 100644 --- a/argh_derive/src/lib.rs +++ b/argh_derive/src/lib.rs @@ -389,6 +389,7 @@ fn impl_from_args_struct_from_args<'a>( quote_spanned! { impl_span => Some(argh::ParseStructSubCommand { subcommands: <#ty as argh::SubCommands>::COMMANDS, + command_synonyms: <#ty as argh::SubCommands>::COMMAND_SYNONYMS, 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)?); @@ -533,6 +534,7 @@ fn impl_from_args_struct_redact_arg_values<'a>( quote_spanned! { impl_span => Some(argh::ParseStructSubCommand { subcommands: <#ty as argh::SubCommands>::COMMANDS, + command_synonyms: <#ty as argh::SubCommands>::COMMAND_SYNONYMS, 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)?); @@ -633,33 +635,48 @@ fn ensure_only_last_positional_is_optional(errors: &Errors, fields: &[StructFiel /// Ensures that only one short or long name is used. fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) { - let mut seen_short_names = HashMap::new(); - let mut seen_long_names = HashMap::new(); + let mut seen_short_names: HashMap = HashMap::new(); + let mut seen_long_names: HashMap = HashMap::new(); for field in fields { if let Some(short_name) = &field.attrs.short { - let short_name = short_name.value(); + let short_name = short_name.value().to_string(); if let Some(first_use_field) = seen_short_names.get(&short_name) { errors.err_span_tokens( - first_use_field, + (*first_use_field).field, &format!("The short name of \"-{}\" was already used here.", short_name), ); errors.err_span_tokens(field.field, "Later usage here."); } - seen_short_names.insert(short_name, &field.field); + seen_short_names.insert(short_name, field); } if let Some(long_name) = &field.long_name { - if let Some(first_use_field) = seen_long_names.get(&long_name) { + if let Some(first_use_field) = seen_long_names.get(long_name) { errors.err_span_tokens( - *first_use_field, + (*first_use_field).field, &format!("The long name of \"{}\" was already used here.", long_name), ); errors.err_span_tokens(field.field, "Later usage here."); } - seen_long_names.insert(long_name, field.field); + seen_long_names.insert(long_name.clone(), field); + } + + for synonym in &field.attrs.synonyms { + let synonym_value = synonym.value(); + if let Some(first_use_field) = seen_long_names.get(&synonym_value) { + errors.err_span_tokens( + (*first_use_field).field, + &format!("The synonym \"{}\" was already used here.", synonym_value), + ); + errors.err_span_tokens(field.field, "Later usage here."); + } + // Synonyms are treated as long names, so we don't check against short names unless we want to support short synonyms (which we don't seem to support explicitly as short flags). + // Actually, if a synonym is "x", it becomes "--x". Short name is "-x". They don't conflict. + + seen_long_names.insert(synonym_value, field); } } } @@ -691,6 +708,7 @@ fn top_or_sub_cmd_impl( }); let short_name = type_attrs.short.as_ref().map(|c| quote! { &#c }).unwrap_or_else(|| quote! { &'\0' }); + let synonyms = &type_attrs.synonyms; quote! { #[automatically_derived] impl #impl_generics argh::SubCommand for #name #ty_generics #where_clause { @@ -699,6 +717,8 @@ fn top_or_sub_cmd_impl( short: #short_name, description: #description, }; + + const SYNONYMS: &'static [&'static str] = &[#( #synonyms ),*]; } } } @@ -922,6 +942,11 @@ fn flag_str_to_output_table_map_entries<'a>(fields: &'a [StructField<'a>]) -> Ve } flag_str_to_output_table_map.push(quote! { (#long_name, #i) }); + + for synonym in &field.attrs.synonyms { + let synonym = format!("--{}", synonym.value()); + flag_str_to_output_table_map.push(quote! { (#synonym, #i) }); + } } flag_str_to_output_table_map } @@ -1075,6 +1100,12 @@ fn impl_from_args_enum( let name_repeating = std::iter::repeat(name.clone()); let variant_ty = variants.iter().map(|x| x.ty).collect::>(); let variant_names = variants.iter().map(|x| x.name).collect::>(); + let variant_synonyms = variants.iter().map(|x| { + let ty = x.ty; + quote! { + || <#ty as argh::SubCommand>::SYNONYMS.contains(&subcommand_name) + } + }).collect::>(); let dynamic_from_args = dynamic_type_and_variant.as_ref().map(|(dynamic_type, dynamic_variant)| { quote! { @@ -1113,7 +1144,7 @@ fn impl_from_args_enum( }; #( - if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name + if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name #variant_synonyms || (*<#variant_ty as argh::SubCommand>::COMMAND.short != '\0' && subcommand_name.len() == 1 && subcommand_name.starts_with(*<#variant_ty as argh::SubCommand>::COMMAND.short)) @@ -1137,7 +1168,7 @@ fn impl_from_args_enum( }; #( - if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name + if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name #variant_synonyms || (*<#variant_ty as argh::SubCommand>::COMMAND.short != '\0' && subcommand_name.len() == 1 && subcommand_name.starts_with(*<#variant_ty as argh::SubCommand>::COMMAND.short)) @@ -1157,6 +1188,10 @@ fn impl_from_args_enum( <#variant_ty as argh::SubCommand>::COMMAND, )*]; + const COMMAND_SYNONYMS: &'static [(&'static str, &'static [&'static str])] = &[#( + (<#variant_ty as argh::SubCommand>::COMMAND.name, <#variant_ty as argh::SubCommand>::SYNONYMS), + )*]; + #dynamic_commands } } diff --git a/argh_derive/src/parse_attrs.rs b/argh_derive/src/parse_attrs.rs index ca7c1c0..7f99384 100644 --- a/argh_derive/src/parse_attrs.rs +++ b/argh_derive/src/parse_attrs.rs @@ -23,6 +23,7 @@ pub struct FieldAttrs { pub greedy: Option, pub hidden_help: bool, pub usage: bool, + pub synonyms: Vec, } /// The purpose of a particular field on a `#![derive(FromArgs)]` struct. @@ -129,13 +130,17 @@ impl FieldAttrs { this.hidden_help = true; } else if name.is_ident("usage") { this.usage = true; + } else if name.is_ident("synonyms") { + if let Some(m) = errors.expect_meta_name_value(&meta) { + this.parse_attr_synonyms(errors, m); + } } else { errors.err( &meta, concat!( "Invalid field-level `argh` attribute\n", "Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, ", - "`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`, `usage`", + "`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`, `usage`, `synonyms`", ), ); } @@ -199,6 +204,10 @@ impl FieldAttrs { } } } + + fn parse_attr_synonyms(&mut self, errors: &Errors, m: &syn::MetaNameValue) { + parse_attr_list_string(errors, m, &mut self.synonyms); + } } pub(crate) fn check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str) { @@ -285,6 +294,7 @@ pub struct TypeAttrs { /// Arguments that trigger printing of the help message pub help_triggers: Option>, pub usage: Option, + pub synonyms: Vec, } impl TypeAttrs { @@ -343,13 +353,17 @@ impl TypeAttrs { if let Some(m) = errors.expect_meta_name_value(&meta) { this.parse_attr_usage(errors, m); } + } else if name.is_ident("synonyms") { + if let Some(m) = errors.expect_meta_name_value(&meta) { + this.parse_attr_synonyms(errors, m); + } } else { errors.err( &meta, concat!( "Invalid type-level `argh` attribute\n", "Expected one of: `description`, `error_code`, `example`, `name`, ", - "`note`, `short`, `subcommand`, `usage`", + "`note`, `short`, `subcommand`, `synonyms`, `usage`", ), ); } @@ -464,12 +478,17 @@ impl TypeAttrs { fn parse_attr_usage(&mut self, errors: &Errors, m: &syn::MetaNameValue) { parse_attr_single_string(errors, m, "usage", &mut self.usage) } + + fn parse_attr_synonyms(&mut self, errors: &Errors, m: &syn::MetaNameValue) { + parse_attr_list_string(errors, m, &mut self.synonyms); + } } /// Represents a `FromArgs` enum variant's attributes. #[derive(Default)] pub struct VariantAttrs { pub is_dynamic: Option, + pub synonyms: Vec, } impl VariantAttrs { @@ -506,11 +525,15 @@ impl VariantAttrs { } else { this.is_dynamic = errors.expect_meta_word(&meta).cloned(); } + } else if name.is_ident("synonyms") { + if let Some(m) = errors.expect_meta_name_value(&meta) { + this.parse_attr_synonyms(errors, m); + } } else { errors.err( &meta, "Invalid variant-level `argh` attribute\n\ - Subcommand variants can only have the #[argh(dynamic)] attribute.", + Subcommand variants can only have the #[argh(dynamic)] or #[argh(synonyms)] attribute.", ); } } @@ -518,6 +541,10 @@ impl VariantAttrs { this } + + fn parse_attr_synonyms(&mut self, errors: &Errors, m: &syn::MetaNameValue) { + parse_attr_list_string(errors, m, &mut self.synonyms); + } } /// Represents the attributes of a variant in a choice enum (an enum with `#[derive(FromArgValue)]`). @@ -631,6 +658,20 @@ fn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut V } } +fn parse_attr_list_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec) { + if let syn::Expr::Array(array) = &m.value { + for elem in &array.elems { + if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) = elem { + list.push(lit_str.clone()); + } else { + errors.err(elem, "Expected string literal in synonyms array"); + } + } + } else { + errors.err(&m.value, "Expected array literal for synonyms, e.g. synonyms = [\"a\", \"b\"]"); + } +} + fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option) { let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) { nv @@ -714,6 +755,7 @@ pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: error_codes, help_triggers, usage, + synonyms, } = type_attrs; // Ensure that `#[argh(subcommand)]` is present. @@ -757,6 +799,9 @@ pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: if let Some(usage) = usage { err_unused_enum_attr(errors, usage); } + if let Some(synonym) = synonyms.first() { + err_unused_enum_attr(errors, synonym); + } } fn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) { From 5b57917467d0bdd893e42ab9a4370e8fe50a7df3 Mon Sep 17 00:00:00 2001 From: Ian McKellar Date: Tue, 23 Dec 2025 15:00:17 -0800 Subject: [PATCH 2/2] fixup --- argh/tests/args_info_tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/argh/tests/args_info_tests.rs b/argh/tests/args_info_tests.rs index 84da78b..890eaf1 100644 --- a/argh/tests/args_info_tests.rs +++ b/argh/tests/args_info_tests.rs @@ -379,7 +379,7 @@ fn args_info_test_notes_examples_errors() { one two three then a blank - + and one last line with "quoted text"."##, example = r##" Use the command with 1 file: @@ -387,7 +387,7 @@ fn args_info_test_notes_examples_errors() { Use it with a "wildcard": `{command_name} /path/to/*` a blank line - + and one last line with "quoted text"."##, error_code(0, "Success"), error_code(1, "General Error"), @@ -403,7 +403,7 @@ fn args_info_test_notes_examples_errors() { name: "NotesExamplesErrors", short: &'\0', description: "Command with Examples and usage Notes, including error codes.", - examples: &["\n Use the command with 1 file:\n `{command_name} /path/to/file`\n Use it with a \"wildcard\":\n `{command_name} /path/to/*`\n a blank line\n\n and one last line with \"quoted text\"."], + examples: &["\n Use the command with 1 file:\n `{command_name} /path/to/file`\n Use it with a \"wildcard\":\n `{command_name} /path/to/*`\n a blank line\n \n and one last line with \"quoted text\"."], flags: &[HELP_FLAG ], positionals: &[ @@ -414,7 +414,7 @@ fn args_info_test_notes_examples_errors() { hidden:false } ], - notes: &["\n These usage notes appear for {command_name} and how to best use it.\n The formatting should be preserved.\n one\n two\n three then a blank\n\n and one last line with \"quoted text\"."], + notes: &["\n These usage notes appear for {command_name} and how to best use it.\n The formatting should be preserved.\n one\n two\n three then a blank\n \n and one last line with \"quoted text\"."], error_codes: & [ErrorCodeInfo { code: 0, description: "Success" }, ErrorCodeInfo { code: 1, description: "General Error" }, ErrorCodeInfo { code: 2, description: "Some error with \"quotes\"" }], ..Default::default() });