From 46ab3ca27299144ea9c1f27257399c4a4f0cbb96 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Thu, 19 Oct 2023 12:53:36 +0100 Subject: Upgrade argh to 0.1.12 This project was upgraded with external_updater. Usage: tools/external_updater/updater.sh update rust/crates/argh For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md Bug: 306205011 Test: TreeHugger Change-Id: I64695aca633f1529087cbbd2de5b6e2bdf62acaa --- .cargo_vcs_info.json | 2 +- Android.bp | 27 +- Cargo.lock | 84 ++-- Cargo.toml | 6 +- Cargo.toml.orig | 6 +- METADATA | 10 +- src/lib.rs | 21 +- tests/args_info_tests.rs | 1029 ++++++++++++++++++++++++++++++++++++++++++++++ tests/lib.rs | 42 ++ 9 files changed, 1172 insertions(+), 55 deletions(-) create mode 100644 tests/args_info_tests.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index eed94c9..d4d81dd 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "3f3c29726a21c4b541bb2b9aa2c592461897ded0" + "sha1": "e8efc8285f632a4ebedfe4377e0f1d78276f8e19" }, "path_in_vcs": "argh" } \ No newline at end of file diff --git a/Android.bp b/Android.bp index 21682b8..8149825 100644 --- a/Android.bp +++ b/Android.bp @@ -23,7 +23,7 @@ rust_test { host_supported: true, crate_name: "argh", cargo_env_compat: true, - cargo_pkg_version: "0.1.10", + cargo_pkg_version: "0.1.12", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -38,12 +38,33 @@ rust_test { proc_macros: ["libargh_derive"], } +rust_test { + name: "argh_test_tests_args_info_tests", + host_supported: true, + crate_name: "args_info_tests", + cargo_env_compat: true, + cargo_pkg_version: "0.1.12", + srcs: ["tests/args_info_tests.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libargh", + "libargh_shared", + "libonce_cell", + ], + proc_macros: ["libargh_derive"], +} + rust_test { name: "argh_test_tests_lib", host_supported: true, crate_name: "lib", cargo_env_compat: true, - cargo_pkg_version: "0.1.10", + cargo_pkg_version: "0.1.12", srcs: ["tests/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -64,7 +85,7 @@ rust_library { host_supported: true, crate_name: "argh", cargo_env_compat: true, - cargo_pkg_version: "0.1.10", + cargo_pkg_version: "0.1.12", srcs: ["src/lib.rs"], edition: "2018", rustlibs: ["libargh_shared"], diff --git a/Cargo.lock b/Cargo.lock index 59f8c1a..d08061e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "argh" -version = "0.1.10" +version = "0.1.12" dependencies = [ "argh_derive", "argh_shared", @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "argh_derive" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" dependencies = [ "argh_shared", "proc-macro2", @@ -26,9 +26,21 @@ dependencies = [ [[package]] name = "argh_shared" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] + +[[package]] +name = "basic-toml" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" +dependencies = [ + "serde", +] [[package]] name = "glob" @@ -38,51 +50,54 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", @@ -91,9 +106,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -102,9 +117,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.107" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -113,42 +128,33 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "toml" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" -dependencies = [ - "serde", -] - [[package]] name = "trybuild" -version = "1.0.75" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1212c215a87a183687a7cc7065901b1a98da6b37277d51a1b5faedbb4efd4f3" +checksum = "a84e0202ea606ba5ebee8507ab2bfbe89b98551ed9b8f0be198109275cff284b" dependencies = [ + "basic-toml", "glob", "once_cell", "serde", "serde_derive", "serde_json", "termcolor", - "toml", ] [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "winapi" diff --git a/Cargo.toml b/Cargo.toml index 2867953..16a3e18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ [package] edition = "2018" name = "argh" -version = "0.1.10" +version = "0.1.12" authors = [ "Taylor Cramer ", "Benjamin Brittain ", @@ -30,10 +30,10 @@ license = "BSD-3-Clause" repository = "https://github.com/google/argh" [dependencies.argh_derive] -version = "0.1.10" +version = "0.1.12" [dependencies.argh_shared] -version = "0.1.10" +version = "0.1.12" [dev-dependencies.once_cell] version = "1.10.0" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 2f1b7f0..fc6daba 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "argh" -version = "0.1.10" +version = "0.1.12" authors = ["Taylor Cramer ", "Benjamin Brittain ", "Erick Tryzelaar "] edition = "2018" keywords = ["args", "arguments", "derive", "cli"] @@ -10,8 +10,8 @@ repository = "https://github.com/google/argh" readme = "README.md" [dependencies] -argh_shared = { version = "0.1.10", path = "../argh_shared" } -argh_derive = { version = "0.1.10", path = "../argh_derive" } +argh_shared = { version = "0.1.12", path = "../argh_shared" } +argh_derive = { version = "0.1.12", path = "../argh_derive" } [dev-dependencies] once_cell = "1.10.0" diff --git a/METADATA b/METADATA index 6a1602e..b5ae03f 100644 --- a/METADATA +++ b/METADATA @@ -1,6 +1,6 @@ # This project was upgraded with external_updater. # Usage: tools/external_updater/updater.sh update rust/crates/argh -# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md name: "argh" description: "Derive-based argument parser optimized for code size" @@ -11,13 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/argh/argh-0.1.10.crate" + value: "https://static.crates.io/crates/argh/argh-0.1.12.crate" } - version: "0.1.10" + version: "0.1.12" license_type: NOTICE last_upgrade_date { year: 2023 - month: 2 - day: 1 + month: 10 + day: 19 } } diff --git a/src/lib.rs b/src/lib.rs index 4fb6ff5..c419e6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -318,11 +318,30 @@ use std::str::FromStr; -pub use argh_derive::FromArgs; +pub use argh_derive::{ArgsInfo, FromArgs}; /// Information about a particular command used for output. pub type CommandInfo = argh_shared::CommandInfo<'static>; +/// Information about the command including the options and arguments. +pub type CommandInfoWithArgs = argh_shared::CommandInfoWithArgs<'static>; + +/// Information about a subcommand. +pub type SubCommandInfo = argh_shared::SubCommandInfo<'static>; + +pub use argh_shared::{ErrorCodeInfo, FlagInfo, FlagInfoKind, Optionality, PositionalInfo}; + +/// Structured information about the command line arguments. +pub trait ArgsInfo { + /// Returns the argument info. + fn get_args_info() -> CommandInfoWithArgs; + + /// Returns the list of subcommands + fn get_subcommands() -> Vec { + Self::get_args_info().commands + } +} + /// Types which can be constructed from a set of commandline arguments. pub trait FromArgs: Sized { /// Construct the type from an input set of arguments. diff --git a/tests/args_info_tests.rs b/tests/args_info_tests.rs new file mode 100644 index 0000000..1fc0730 --- /dev/null +++ b/tests/args_info_tests.rs @@ -0,0 +1,1029 @@ +#![cfg(test)] +// Copyright (c) 2023 Google LLC All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +use argh::{ + ArgsInfo, CommandInfoWithArgs, ErrorCodeInfo, FlagInfo, FlagInfoKind, FromArgs, Optionality, + PositionalInfo, SubCommandInfo, +}; + +fn assert_args_info(expected: &CommandInfoWithArgs) { + let actual_value = T::get_args_info(); + assert_eq!(expected, &actual_value) +} + +const HELP_FLAG: FlagInfo<'_> = FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--help", + short: None, + description: "display usage information", + hidden: false, +}; + +/// Tests that exercise the JSON output for help text. +#[test] +fn args_info_test_subcommand() { + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand)] + nested: MySubCommandEnum, + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let command_one = CommandInfoWithArgs { + name: "one", + description: "First subcommand.", + flags: &[ + HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "x" }, + optionality: Optionality::Required, + long: "--x", + short: None, + description: "how many x", + hidden: false, + }, + ], + ..Default::default() + }; + + assert_args_info::(&CommandInfoWithArgs { + name: "TopLevel", + description: "Top-level command.", + examples: &[], + flags: &[HELP_FLAG], + notes: &[], + positionals: &[], + error_codes: &[], + commands: vec![ + SubCommandInfo { name: "one", command: command_one.clone() }, + SubCommandInfo { + name: "two", + command: CommandInfoWithArgs { + name: "two", + description: "Second subcommand.", + flags: &[ + HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--fooey", + short: None, + description: "whether to fooey", + hidden: false, + }, + ], + ..Default::default() + }, + }, + ], + }); + + assert_args_info::(&command_one); +} + +#[test] +fn args_info_test_multiline_doc_comment() { + #[derive(FromArgs, ArgsInfo)] + /// Short description + struct Cmd { + #[argh(switch)] + /// a switch with a description + /// that is spread across + /// a number of + /// lines of comments. + _s: bool, + } + assert_args_info::( + &CommandInfoWithArgs { + name: "Cmd", + description: "Short description", + flags: &[HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--s", + short: None, + description: "a switch with a description that is spread across a number of lines of comments.", + hidden:false + } + ], + ..Default::default() + }); +} + +#[test] +fn args_info_test_basic_args() { + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + /// Basic command args demonstrating multiple types and cardinality. "With quotes" + struct Basic { + /// should the power be on. "Quoted value" should work too. + #[argh(switch)] + power: bool, + + /// option that is required because of no default and not Option<>. + #[argh(option, long = "required")] + required_flag: String, + + /// optional speed if not specified it is None. + #[argh(option, short = 's')] + speed: Option, + + /// repeatable option. + #[argh(option, arg_name = "url")] + link: Vec, + } + assert_args_info::(&CommandInfoWithArgs { + name: "Basic", + description: + "Basic command args demonstrating multiple types and cardinality. \"With quotes\"", + flags: &[ + FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--help", + short: None, + description: "display usage information", + hidden: false, + }, + FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--power", + short: None, + description: "should the power be on. \"Quoted value\" should work too.", + hidden: false, + }, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "required" }, + optionality: Optionality::Required, + long: "--required", + short: None, + description: "option that is required because of no default and not Option<>.", + hidden: false, + }, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "speed" }, + optionality: Optionality::Optional, + long: "--speed", + short: Some('s'), + description: "optional speed if not specified it is None.", + hidden: false, + }, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "url" }, + optionality: Optionality::Repeating, + long: "--link", + short: None, + description: "repeatable option.", + hidden: false, + }, + ], + ..Default::default() + }); +} + +#[test] +fn args_info_test_positional_args() { + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + /// Command with positional args demonstrating. "With quotes" + struct Positional { + /// the "root" position. + #[argh(positional, arg_name = "root")] + root_value: String, + + /// trunk value + #[argh(positional)] + trunk: String, + + /// leaves. There can be many leaves. + #[argh(positional)] + leaves: Vec, + } + assert_args_info::(&CommandInfoWithArgs { + name: "Positional", + description: "Command with positional args demonstrating. \"With quotes\"", + flags: &[HELP_FLAG], + positionals: &[ + PositionalInfo { + name: "root", + description: "the \"root\" position.", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "trunk", + description: "trunk value", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "leaves", + description: "leaves. There can be many leaves.", + optionality: Optionality::Repeating, + hidden: false, + }, + ], + + ..Default::default() + }); +} + +#[test] +fn args_info_test_optional_positional_args() { + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + /// Command with positional args demonstrating last value is optional + struct Positional { + /// the "root" position. + #[argh(positional, arg_name = "root")] + root_value: String, + + /// trunk value + #[argh(positional)] + trunk: String, + + /// leaves. There can be an optional leaves. + #[argh(positional)] + leaves: Option, + } + assert_args_info::(&CommandInfoWithArgs { + name: "Positional", + description: "Command with positional args demonstrating last value is optional", + flags: &[FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--help", + short: None, + description: "display usage information", + hidden: false, + }], + positionals: &[ + PositionalInfo { + name: "root", + description: "the \"root\" position.", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "trunk", + description: "trunk value", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "leaves", + description: "leaves. There can be an optional leaves.", + optionality: Optionality::Optional, + hidden: false, + }, + ], + + ..Default::default() + }); +} + +#[test] +fn args_info_test_default_positional_args() { + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + /// Command with positional args demonstrating last value is defaulted. + struct Positional { + /// the "root" position. + #[argh(positional, arg_name = "root")] + root_value: String, + + /// trunk value + #[argh(positional)] + trunk: String, + + /// leaves. There can be one leaf, defaults to hello. + #[argh(positional, default = "String::from(\"hello\")")] + leaves: String, + } + assert_args_info::(&CommandInfoWithArgs { + name: "Positional", + description: "Command with positional args demonstrating last value is defaulted.", + flags: &[HELP_FLAG], + positionals: &[ + PositionalInfo { + name: "root", + description: "the \"root\" position.", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "trunk", + description: "trunk value", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "leaves", + description: "leaves. There can be one leaf, defaults to hello.", + optionality: Optionality::Optional, + hidden: false, + }, + ], + + ..Default::default() + }); +} + +#[test] +fn args_info_test_notes_examples_errors() { + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + /// Command with Examples and usage Notes, including error codes. + #[argh( + note = r##" + These usage notes appear for {command_name} and how to best use it. + The formatting should be preserved. + one + two + three then a blank + + and one last line with "quoted text"."##, + example = r##" + Use the command with 1 file: + `{command_name} /path/to/file` + 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"), + error_code(2, "Some error with \"quotes\"") + )] + struct NotesExamplesErrors { + /// the "root" position. + #[argh(positional, arg_name = "files")] + fields: Vec, + } + assert_args_info::( + &CommandInfoWithArgs { + name: "NotesExamplesErrors", + 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\"."], + flags: &[HELP_FLAG + ], + positionals: &[ + PositionalInfo{ + name: "files", + description: "the \"root\" position.", + optionality: Optionality::Repeating, + 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\"."], + error_codes: & [ErrorCodeInfo { code: 0, description: "Success" }, ErrorCodeInfo { code: 1, description: "General Error" }, ErrorCodeInfo { code: 2, description: "Some error with \"quotes\"" }], + ..Default::default() + }); +} + +#[test] +fn args_info_test_subcommands() { + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + ///Top level command with "subcommands". + struct TopLevel { + /// show verbose output + #[argh(switch)] + verbose: bool, + + /// this doc comment does not appear anywhere. + #[argh(subcommand)] + cmd: SubcommandEnum, + } + + #[derive(FromArgs, ArgsInfo)] + #[argh(subcommand)] + /// Doc comments for subcommand enums does not appear in the help text. + enum SubcommandEnum { + Command1(Command1Args), + Command2(Command2Args), + Command3(Command3Args), + } + + /// Command1 args are used for Command1. + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + #[argh(subcommand, name = "one")] + struct Command1Args { + /// the "root" position. + #[argh(positional, arg_name = "root")] + root_value: String, + + /// trunk value + #[argh(positional)] + trunk: String, + + /// leaves. There can be zero leaves, defaults to hello. + #[argh(positional, default = "String::from(\"hello\")")] + leaves: String, + } + /// Command2 args are used for Command2. + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + #[argh(subcommand, name = "two")] + struct Command2Args { + /// should the power be on. "Quoted value" should work too. + #[argh(switch)] + power: bool, + + /// option that is required because of no default and not Option<>. + #[argh(option, long = "required")] + required_flag: String, + + /// optional speed if not specified it is None. + #[argh(option, short = 's')] + speed: Option, + + /// repeatable option. + #[argh(option, arg_name = "url")] + link: Vec, + } + /// Command3 args are used for Command3 which has no options or arguments. + #[derive(FromArgs, ArgsInfo)] + #[argh(subcommand, name = "three")] + struct Command3Args {} + + assert_args_info::(&CommandInfoWithArgs { + name: "TopLevel", + description: "Top level command with \"subcommands\".", + flags: &[ + HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--verbose", + short: None, + description: "show verbose output", + hidden: false, + }, + ], + positionals: &[], + commands: vec![ + SubCommandInfo { + name: "one", + command: CommandInfoWithArgs { + name: "one", + description: "Command1 args are used for Command1.", + flags: &[HELP_FLAG], + positionals: &[ + PositionalInfo { + name: "root", + description: "the \"root\" position.", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "trunk", + description: "trunk value", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "leaves", + description: "leaves. There can be zero leaves, defaults to hello.", + optionality: Optionality::Optional, + hidden: false, + }, + ], + ..Default::default() + }, + }, + SubCommandInfo { + name: "two", + command: CommandInfoWithArgs { + name: "two", + description: "Command2 args are used for Command2.", + flags: &[ + HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--power", + short: None, + description: + "should the power be on. \"Quoted value\" should work too.", + hidden: false, + }, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "required" }, + optionality: Optionality::Required, + long: "--required", + short: None, + description: + "option that is required because of no default and not Option<>.", + hidden: false, + }, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "speed" }, + optionality: Optionality::Optional, + long: "--speed", + short: Some('s'), + description: "optional speed if not specified it is None.", + hidden: false, + }, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "url" }, + optionality: Optionality::Repeating, + long: "--link", + short: None, + description: "repeatable option.", + hidden: false, + }, + ], + ..Default::default() + }, + }, + SubCommandInfo { + name: "three", + command: CommandInfoWithArgs { + name: "three", + description: + "Command3 args are used for Command3 which has no options or arguments.", + flags: &[HELP_FLAG], + positionals: &[], + ..Default::default() + }, + }, + ], + ..Default::default() + }); +} + +#[test] +fn args_info_test_subcommand_notes_examples() { + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + ///Top level command with "subcommands". + #[argh( + note = "Top level note", + example = "Top level example", + error_code(0, "Top level success") + )] + struct TopLevel { + /// this doc comment does not appear anywhere. + #[argh(subcommand)] + cmd: SubcommandEnum, + } + + #[derive(FromArgs, ArgsInfo)] + #[argh(subcommand)] + /// Doc comments for subcommand enums does not appear in the help text. + enum SubcommandEnum { + Command1(Command1Args), + } + + /// Command1 args are used for subcommand one. + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + #[argh( + subcommand, + name = "one", + note = "{command_name} is used as a subcommand of \"Top level\"", + example = "\"Typical\" usage is `{command_name}`.", + error_code(0, "one level success") + )] + struct Command1Args { + /// the "root" position. + #[argh(positional, arg_name = "root")] + root_value: String, + + /// trunk value + #[argh(positional)] + trunk: String, + + /// leaves. There can be many leaves. + #[argh(positional)] + leaves: Vec, + } + + let command_one = CommandInfoWithArgs { + name: "one", + description: "Command1 args are used for subcommand one.", + error_codes: &[ErrorCodeInfo { code: 0, description: "one level success" }], + examples: &["\"Typical\" usage is `{command_name}`."], + flags: &[HELP_FLAG], + notes: &["{command_name} is used as a subcommand of \"Top level\""], + positionals: &[ + PositionalInfo { + name: "root", + description: "the \"root\" position.", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "trunk", + description: "trunk value", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "leaves", + description: "leaves. There can be many leaves.", + optionality: Optionality::Repeating, + hidden: false, + }, + ], + ..Default::default() + }; + + assert_args_info::(&CommandInfoWithArgs { + name: "TopLevel", + description: "Top level command with \"subcommands\".", + error_codes: &[ErrorCodeInfo { code: 0, description: "Top level success" }], + examples: &["Top level example"], + flags: &[HELP_FLAG], + notes: &["Top level note"], + commands: vec![SubCommandInfo { name: "one", command: command_one.clone() }], + ..Default::default() + }); + + assert_args_info::(&command_one); +} + +#[test] +fn args_info_test_example() { + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + #[argh( + description = "Destroy the contents of with a specific \"method of destruction\".", + example = "Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp", + note = "Use `{command_name} help ` for details on [] for a subcommand.", + error_code(2, "The blade is too dull."), + error_code(3, "Out of fuel.") + )] + struct HelpExample { + /// force, ignore minor errors. This description is so long that it wraps to the next line. + #[argh(switch, short = 'f')] + force: bool, + + /// documentation + #[argh(switch)] + really_really_really_long_name_for_pat: bool, + + /// write repeatedly + #[argh(option, short = 's')] + scribble: String, + + /// say more. Defaults to $BLAST_VERBOSE. + #[argh(switch, short = 'v')] + verbose: bool, + + #[argh(subcommand)] + command: HelpExampleSubCommands, + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + #[argh(subcommand)] + enum HelpExampleSubCommands { + BlowUp(BlowUp), + Grind(GrindCommand), + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + #[argh(subcommand, name = "blow-up")] + /// explosively separate + struct BlowUp { + /// blow up bombs safely + #[argh(switch)] + safely: bool, + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + #[argh(subcommand, name = "grind", description = "make smaller by many small cuts")] + struct GrindCommand { + /// wear a visor while grinding + #[argh(switch)] + safely: bool, + } + + assert_args_info::( + &CommandInfoWithArgs { + name: "HelpExample", + description: "Destroy the contents of with a specific \"method of destruction\".", + examples: &["Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp"], + flags: &[HELP_FLAG, + FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--force", short: Some('f'), description: "force, ignore minor errors. This description is so long that it wraps to the next line.", + hidden:false }, + FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--really-really-really-long-name-for-pat", short: None, description: "documentation", + hidden:false }, + FlagInfo { kind: FlagInfoKind::Option { arg_name: "scribble"}, + optionality: Optionality::Required, long: "--scribble", short: Some('s'), description: "write repeatedly", + hidden:false }, + FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--verbose", short: Some('v'), description: "say more. Defaults to $BLAST_VERBOSE.", + hidden:false } + ], + notes: &["Use `{command_name} help ` for details on [] for a subcommand."], + commands: vec![ + SubCommandInfo { name: "blow-up", + command: CommandInfoWithArgs { name: "blow-up", + description: "explosively separate", + flags:& [HELP_FLAG, + FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--safely", short: None, description: "blow up bombs safely", + hidden:false } + ], + ..Default::default() + } }, + SubCommandInfo { + name: "grind", + command: CommandInfoWithArgs { + name: "grind", + description: "make smaller by many small cuts", + flags: &[HELP_FLAG, + FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--safely", short: None, description: "wear a visor while grinding" ,hidden:false}], + ..Default::default() + } + }], + error_codes: &[ErrorCodeInfo { code: 2, description: "The blade is too dull." }, ErrorCodeInfo { code: 3, description: "Out of fuel." }], + ..Default::default() + } + ); +} + +#[test] +fn positional_greedy() { + #[allow(dead_code)] + #[derive(FromArgs, ArgsInfo)] + /// Woot + struct LastRepeatingGreedy { + #[argh(positional)] + /// fooey + pub a: u32, + #[argh(switch)] + /// woo + pub b: bool, + #[argh(option)] + /// stuff + pub c: Option, + #[argh(positional, greedy)] + /// fooey + pub d: Vec, + } + assert_args_info::(&CommandInfoWithArgs { + name: "LastRepeatingGreedy", + description: "Woot", + flags: &[ + HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--b", + short: None, + description: "woo", + hidden: false, + }, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "c" }, + optionality: Optionality::Optional, + long: "--c", + short: None, + description: "stuff", + hidden: false, + }, + ], + positionals: &[ + PositionalInfo { + name: "a", + description: "fooey", + optionality: Optionality::Required, + hidden: false, + }, + PositionalInfo { + name: "d", + description: "fooey", + optionality: Optionality::Greedy, + hidden: false, + }, + ], + ..Default::default() + }); +} + +#[test] +fn hidden_help_attribute() { + #[derive(FromArgs, ArgsInfo)] + /// Short description + struct Cmd { + /// this one should be hidden + #[argh(positional, hidden_help)] + _one: String, + #[argh(positional)] + /// this one is real + _two: String, + /// this one should be hidden + #[argh(option, hidden_help)] + _three: String, + } + + assert_args_info::(&CommandInfoWithArgs { + name: "Cmd", + description: "Short description", + flags: &[ + HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "three" }, + optionality: Optionality::Required, + long: "--three", + short: None, + description: "this one should be hidden", + hidden: true, + }, + ], + positionals: &[ + PositionalInfo { + name: "one", + description: "this one should be hidden", + optionality: Optionality::Required, + hidden: true, + }, + PositionalInfo { + name: "two", + description: "this one is real", + optionality: Optionality::Required, + hidden: false, + }, + ], + ..Default::default() + }); +} + +#[test] +fn test_dynamic_subcommand() { + #[derive(PartialEq, Debug)] + struct DynamicSubCommandImpl { + got: String, + } + + impl argh::DynamicSubCommand for DynamicSubCommandImpl { + fn commands() -> &'static [&'static argh::CommandInfo] { + &[ + &argh::CommandInfo { name: "three", description: "Third command" }, + &argh::CommandInfo { name: "four", description: "Fourth command" }, + &argh::CommandInfo { name: "five", description: "Fifth command" }, + ] + } + + fn try_redact_arg_values( + _command_name: &[&str], + _args: &[&str], + ) -> Option, argh::EarlyExit>> { + Some(Err(argh::EarlyExit::from("Test should not redact".to_owned()))) + } + + fn try_from_args( + command_name: &[&str], + args: &[&str], + ) -> Option> { + let command_name = match command_name.last() { + Some(x) => *x, + None => return Some(Err(argh::EarlyExit::from("No command".to_owned()))), + }; + let description = Self::commands().iter().find(|x| x.name == command_name)?.description; + if args.len() > 1 { + Some(Err(argh::EarlyExit::from("Too many arguments".to_owned()))) + } else if let Some(arg) = args.first() { + Some(Ok(DynamicSubCommandImpl { got: format!("{} got {:?}", description, arg) })) + } else { + Some(Err(argh::EarlyExit::from("Not enough arguments".to_owned()))) + } + } + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand)] + nested: MySubCommandEnum, + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + #[argh(dynamic)] + ThreeFourFive(DynamicSubCommandImpl), + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, + } + + #[derive(FromArgs, ArgsInfo, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + assert_args_info::(&CommandInfoWithArgs { + name: "TopLevel", + description: "Top-level command.", + flags: &[HELP_FLAG], + commands: vec![ + SubCommandInfo { + name: "one", + command: CommandInfoWithArgs { + name: "one", + description: "First subcommand.", + flags: &[ + HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Option { arg_name: "x" }, + optionality: Optionality::Required, + long: "--x", + short: None, + description: "how many x", + hidden: false, + }, + ], + ..Default::default() + }, + }, + SubCommandInfo { + name: "two", + command: CommandInfoWithArgs { + name: "two", + description: "Second subcommand.", + flags: &[ + HELP_FLAG, + FlagInfo { + kind: FlagInfoKind::Switch, + optionality: Optionality::Optional, + long: "--fooey", + short: None, + description: "whether to fooey", + hidden: false, + }, + ], + ..Default::default() + }, + }, + SubCommandInfo { + name: "three", + command: CommandInfoWithArgs { + name: "three", + description: "Third command", + ..Default::default() + }, + }, + SubCommandInfo { + name: "four", + command: CommandInfoWithArgs { + name: "four", + description: "Fourth command", + ..Default::default() + }, + }, + SubCommandInfo { + name: "five", + command: CommandInfoWithArgs { + name: "five", + description: "Fifth command", + ..Default::default() + }, + }, + ], + ..Default::default() + }) +} diff --git a/tests/lib.rs b/tests/lib.rs index 713c73d..dbd3b48 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -81,6 +81,48 @@ fn custom_from_str_example() { assert_eq!(f.five, 5); } +#[test] +fn nested_from_str_example() { + #[derive(FromArgs)] + /// Goofy thing. + struct FiveStruct { + /// always five + #[argh(option, from_str_fn(nested::always_five))] + five: usize, + } + + pub mod nested { + pub fn always_five(_value: &str) -> Result { + Ok(5) + } + } + + let f = FiveStruct::from_args(&["cmdname"], &["--five", "woot"]).expect("failed to five"); + assert_eq!(f.five, 5); +} + +#[test] +fn method_from_str_example() { + #[derive(FromArgs)] + /// Goofy thing. + struct FiveStruct { + /// always five + #[argh(option, from_str_fn(AlwaysFive::::always_five))] + five: usize, + } + + struct AlwaysFive(T); + + impl AlwaysFive { + fn always_five(_value: &str) -> Result { + Ok(5) + } + } + + let f = FiveStruct::from_args(&["cmdname"], &["--five", "woot"]).expect("failed to five"); + assert_eq!(f.five, 5); +} + #[test] fn subcommand_example() { #[derive(FromArgs, PartialEq, Debug)] -- cgit v1.2.3