diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 01:03:04 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 01:03:04 +0000 |
commit | 53f44b1476cb8f9ec04a8abb2d405d3afa1455b7 (patch) | |
tree | 0f46a2e4d16f3027c8b32675fcfe393e5884c1ca | |
parent | b038a32db1b3071979638e16622e044a6dd2966a (diff) | |
parent | 60fb801071894a38fa0096784daeaa260094bb6a (diff) | |
download | argh-aml_tet_341511010.tar.gz |
Snap for 10447354 from 60fb801071894a38fa0096784daeaa260094bb6a to mainline-tethering-releaseaml_tet_341712060aml_tet_341610020aml_tet_341511010aml_tet_341411060aml_tet_341310230aml_tet_341112070aml_tet_341010040aml_tet_340913030android14-mainline-tethering-release
Change-Id: I99283465c23b203057febdec0279a7c3b374f5e2
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | Android.bp | 14 | ||||
-rw-r--r-- | Cargo.lock | 182 | ||||
-rw-r--r-- | Cargo.toml | 26 | ||||
-rw-r--r-- | Cargo.toml.orig | 10 | ||||
-rw-r--r-- | METADATA | 14 | ||||
-rw-r--r-- | README.md | 13 | ||||
-rw-r--r-- | TEST_MAPPING | 5 | ||||
-rw-r--r-- | cargo2android.json | 10 | ||||
-rw-r--r-- | examples/simple_example.rs | 42 | ||||
-rw-r--r-- | src/lib.rs | 267 | ||||
-rw-r--r-- | tests/compiletest.rs | 5 | ||||
-rw-r--r-- | tests/lib.rs | 399 | ||||
-rw-r--r-- | tests/ui/bad-long-names/bad-long-names.rs | 15 | ||||
-rw-r--r-- | tests/ui/bad-long-names/bad-long-names.stderr | 17 | ||||
-rw-r--r-- | tests/ui/conflicting-tails/positional-and-greedy.rs | 13 | ||||
-rw-r--r-- | tests/ui/conflicting-tails/positional-and-greedy.stderr | 11 | ||||
-rw-r--r-- | tests/ui/duplicate-name/duplicate-long-name.rs | 21 | ||||
-rw-r--r-- | tests/ui/duplicate-name/duplicate-long-name.stderr | 31 | ||||
-rw-r--r-- | tests/ui/duplicate-name/duplicate-short-name.rs | 21 | ||||
-rw-r--r-- | tests/ui/duplicate-name/duplicate-short-name.stderr | 31 |
21 files changed, 1093 insertions, 56 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 23c489b..eed94c9 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "f1f85d2d89cbe09314dc1b59e581b8a43531cf3e" + "sha1": "3f3c29726a21c4b541bb2b9aa2c592461897ded0" }, "path_in_vcs": "argh" }
\ No newline at end of file @@ -1,8 +1,6 @@ // This file is generated by cargo2android.py --config cargo2android.json. // Do not modify this file as changes will be overridden on upgrade. - - package { default_applicable_licenses: ["external_rust_crates_argh_license"], } @@ -25,7 +23,7 @@ rust_test { host_supported: true, crate_name: "argh", cargo_env_compat: true, - cargo_pkg_version: "0.1.7", + cargo_pkg_version: "0.1.10", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -35,6 +33,7 @@ rust_test { edition: "2018", rustlibs: [ "libargh_shared", + "libonce_cell", ], proc_macros: ["libargh_derive"], } @@ -42,9 +41,9 @@ rust_test { rust_test { name: "argh_test_tests_lib", host_supported: true, - crate_name: "lib", + crate_name: "argh", cargo_env_compat: true, - cargo_pkg_version: "0.1.7", + cargo_pkg_version: "0.1.10", srcs: ["tests/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -55,6 +54,7 @@ rust_test { rustlibs: [ "libargh", "libargh_shared", + "libonce_cell", ], proc_macros: ["libargh_derive"], } @@ -64,7 +64,7 @@ rust_library { host_supported: true, crate_name: "argh", cargo_env_compat: true, - cargo_pkg_version: "0.1.7", + cargo_pkg_version: "0.1.10", srcs: ["src/lib.rs"], edition: "2018", rustlibs: [ @@ -75,4 +75,6 @@ rust_library { "//apex_available:platform", "com.android.virt", ], + product_available: true, + vendor_available: true, } diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..59f8c1a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,182 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "argh" +version = "0.1.10" +dependencies = [ + "argh_derive", + "argh_shared", + "once_cell", + "trybuild", +] + +[[package]] +name = "argh_derive" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1212c215a87a183687a7cc7065901b1a98da6b37277d51a1b5faedbb4efd4f3" +dependencies = [ + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", + "toml", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" @@ -12,15 +12,31 @@ [package] edition = "2018" name = "argh" -version = "0.1.7" -authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"] +version = "0.1.10" +authors = [ + "Taylor Cramer <cramertj@google.com>", + "Benjamin Brittain <bwb@google.com>", + "Erick Tryzelaar <etryzelaar@google.com>", +] description = "Derive-based argument parser optimized for code size" readme = "README.md" -keywords = ["args", "arguments", "derive", "cli"] +keywords = [ + "args", + "arguments", + "derive", + "cli", +] license = "BSD-3-Clause" repository = "https://github.com/google/argh" + [dependencies.argh_derive] -version = "0.1.7" +version = "0.1.10" [dependencies.argh_shared] -version = "0.1.7" +version = "0.1.10" + +[dev-dependencies.once_cell] +version = "1.10.0" + +[dev-dependencies.trybuild] +version = "1.0.63" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index ec92149..2f1b7f0 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "argh" -version = "0.1.7" +version = "0.1.10" authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"] edition = "2018" keywords = ["args", "arguments", "derive", "cli"] @@ -10,5 +10,9 @@ repository = "https://github.com/google/argh" readme = "README.md" [dependencies] -argh_shared = { version = "0.1.7", path = "../argh_shared" } -argh_derive = { version = "0.1.7", path = "../argh_derive" } +argh_shared = { version = "0.1.10", path = "../argh_shared" } +argh_derive = { version = "0.1.10", path = "../argh_derive" } + +[dev-dependencies] +once_cell = "1.10.0" +trybuild = "1.0.63" @@ -1,3 +1,7 @@ +# 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 + name: "argh" description: "Derive-based argument parser optimized for code size" third_party { @@ -7,13 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/argh/argh-0.1.7.crate" + value: "https://static.crates.io/crates/argh/argh-0.1.10.crate" } - version: "0.1.7" + version: "0.1.10" license_type: NOTICE last_upgrade_date { - year: 2022 - month: 1 - day: 13 + year: 2023 + month: 2 + day: 1 } } @@ -175,3 +175,16 @@ struct SubCommandTwo { ``` NOTE: This is not an officially supported Google product. + + +## How to debug the expanded derive macro for `argh` + +The `argh::FromArgs` derive macro can be debugged with the [cargo-expand](https://crates.io/crates/cargo-expand) crate. + +### Expand the derive macro in `examples/simple_example.rs` + +See [argh/examples/simple_example.rs](./argh/examples/simple_example.rs) for the example struct we wish to expand. + +First, install `cargo-expand` by running `cargo install cargo-expand`. Note this requires the nightly build of Rust. + +Once installed, run `cargo expand` with in the `argh` package and you can see the expanded code. diff --git a/TEST_MAPPING b/TEST_MAPPING index 080451d..c04829d 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,5 +1,10 @@ // Generated by update_crate_tests.py for tests that depend on this crate. { + "imports": [ + { + "path": "packages/modules/Virtualization/virtualizationmanager" + } + ], "presubmit": [ { "name": "argh_test_src_lib" diff --git a/cargo2android.json b/cargo2android.json index 6e516e0..1dd19e7 100644 --- a/cargo2android.json +++ b/cargo2android.json @@ -5,5 +5,11 @@ ], "device": true, "run": true, - "tests": true -}
\ No newline at end of file + "tests": true, + "dependency-blocklist": [ + "trybuild" + ], + "test-blocklist": [ + "tests/compiletest.rs" + ] +} diff --git a/examples/simple_example.rs b/examples/simple_example.rs new file mode 100644 index 0000000..d977495 --- /dev/null +++ b/examples/simple_example.rs @@ -0,0 +1,42 @@ +// Copyright (c) 2022 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::FromArgs, std::fmt::Debug}; + +#[derive(FromArgs, PartialEq, Debug)] +/// Top-level command. +struct TopLevel { + #[argh(subcommand)] + nested: MySubCommandEnum, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), +} + +#[derive(FromArgs, PartialEq, Debug)] +/// First subcommand. +#[argh(subcommand, name = "one")] +struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Second subcommand. +#[argh(subcommand, name = "two")] +struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, +} + +fn main() { + let toplevel: TopLevel = argh::from_env(); + println!("{:#?}", toplevel); +} @@ -31,9 +31,7 @@ //! pilot_nickname: Option<String>, //! } //! -//! fn main() { -//! let up: GoUp = argh::from_env(); -//! } +//! let up: GoUp = argh::from_env(); //! ``` //! //! `./some_bin --help` will then output the following: @@ -129,6 +127,34 @@ //! The last positional argument may include a default, or be wrapped in //! `Option` or `Vec` to indicate an optional or repeating positional argument. //! +//! If your final positional argument has the `greedy` option on it, it will consume +//! any arguments after it as if a `--` were placed before the first argument to +//! match the greedy positional: +//! +//! ```rust +//! use argh::FromArgs; +//! #[derive(FromArgs, PartialEq, Debug)] +//! /// A command with a greedy positional argument at the end. +//! struct WithGreedyPositional { +//! /// some stuff +//! #[argh(option)] +//! stuff: Option<String>, +//! #[argh(positional, greedy)] +//! all_the_rest: Vec<String>, +//! } +//! ``` +//! +//! Now if you pass `--stuff Something` after a positional argument, it will +//! be consumed by `all_the_rest` instead of setting the `stuff` field. +//! +//! Note that `all_the_rest` won't be listed as a positional argument in the +//! long text part of help output (and it will be listed at the end of the usage +//! line as `[all_the_rest...]`), and it's up to the caller to append any +//! extra help output for the meaning of the captured arguments. This is to +//! enable situations where some amount of argument processing needs to happen +//! before the rest of the arguments can be interpreted, and shouldn't be used +//! for regular use as it might be confusing. +//! //! Subcommands are also supported. To use a subcommand, declare a separate //! `FromArgs` type for each subcommand as well as an enum that cases //! over each command: @@ -168,6 +194,125 @@ //! fooey: bool, //! } //! ``` +//! +//! 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 +//! dynamic variant should implement `DynamicSubCommand`. +//! +//! ```rust +//! # use argh::CommandInfo; +//! # use argh::DynamicSubCommand; +//! # use argh::EarlyExit; +//! # use argh::FromArgs; +//! # use once_cell::sync::OnceCell; +//! +//! #[derive(FromArgs, PartialEq, Debug)] +//! /// Top-level command. +//! struct TopLevel { +//! #[argh(subcommand)] +//! nested: MySubCommandEnum, +//! } +//! +//! #[derive(FromArgs, PartialEq, Debug)] +//! #[argh(subcommand)] +//! enum MySubCommandEnum { +//! Normal(NormalSubCommand), +//! #[argh(dynamic)] +//! Dynamic(Dynamic), +//! } +//! +//! #[derive(FromArgs, PartialEq, Debug)] +//! /// Normal subcommand. +//! #[argh(subcommand, name = "normal")] +//! struct NormalSubCommand { +//! #[argh(option)] +//! /// how many x +//! x: usize, +//! } +//! +//! /// Dynamic subcommand. +//! #[derive(PartialEq, Debug)] +//! struct Dynamic { +//! name: String +//! } +//! +//! impl DynamicSubCommand for Dynamic { +//! fn commands() -> &'static [&'static CommandInfo] { +//! static RET: OnceCell<Vec<&'static CommandInfo>> = OnceCell::new(); +//! RET.get_or_init(|| { +//! let mut commands = Vec::new(); +//! +//! // argh needs the `CommandInfo` structs we generate to be valid +//! // for the static lifetime. We can allocate the structures on +//! // the heap with `Box::new` and use `Box::leak` to get a static +//! // reference to them. We could also just use a constant +//! // reference, but only because this is a synthetic example; the +//! // point of using dynamic commands is to have commands you +//! // don't know about until runtime! +//! commands.push(&*Box::leak(Box::new(CommandInfo { +//! name: "dynamic_command", +//! description: "A dynamic command", +//! }))); +//! +//! commands +//! }) +//! } +//! +//! fn try_redact_arg_values( +//! command_name: &[&str], +//! args: &[&str], +//! ) -> Option<Result<Vec<String>, EarlyExit>> { +//! for command in Self::commands() { +//! if command_name.last() == Some(&command.name) { +//! // Process arguments and redact values here. +//! if !args.is_empty() { +//! return Some(Err("Our example dynamic command never takes arguments!" +//! .to_string().into())); +//! } +//! return Some(Ok(Vec::new())) +//! } +//! } +//! None +//! } +//! +//! fn try_from_args(command_name: &[&str], args: &[&str]) -> Option<Result<Self, EarlyExit>> { +//! for command in Self::commands() { +//! if command_name.last() == Some(&command.name) { +//! if !args.is_empty() { +//! return Some(Err("Our example dynamic command never takes arguments!" +//! .to_string().into())); +//! } +//! return Some(Ok(Dynamic { name: command.name.to_string() })) +//! } +//! } +//! None +//! } +//! } +//! ``` +//! +//! Programs that are run from an environment such as cargo may find it +//! useful to have positional arguments present in the structure but +//! omitted from the usage output. This can be accomplished by adding +//! the `hidden_help` attribute to that argument: +//! +//! ```rust +//! # use argh::FromArgs; +//! +//! #[derive(FromArgs)] +//! /// Cargo arguments +//! struct CargoArgs { +//! // Cargo puts the command name invoked into the first argument, +//! // so we don't want this argument to show up in the usage text. +//! #[argh(positional, hidden_help)] +//! command: String, +//! /// an option used for internal debugging +//! #[argh(option, hidden_help)] +//! internal_debugging: String, +//! #[argh(positional)] +//! real_first_arg: String, +//! } +//! ``` #![deny(missing_docs)] @@ -445,6 +590,11 @@ pub trait TopLevelCommand: FromArgs {} pub trait SubCommands: FromArgs { /// Info for the commands. const COMMANDS: &'static [&'static CommandInfo]; + + /// Get a list of commands that are discovered at runtime. + fn dynamic_commands() -> &'static [&'static CommandInfo] { + &[] + } } /// A `FromArgs` implementation that represents a single subcommand. @@ -457,6 +607,34 @@ impl<T: SubCommand> SubCommands for T { const COMMANDS: &'static [&'static CommandInfo] = &[T::COMMAND]; } +/// Trait implemented by values returned from a dynamic subcommand handler. +pub trait DynamicSubCommand: Sized { + /// Info about supported subcommands. + fn commands() -> &'static [&'static CommandInfo]; + + /// Perform the function of `FromArgs::redact_arg_values` for this dynamic + /// command. + /// + /// The full list of subcommands, ending with the subcommand that should be + /// dynamically recognized, is passed in `command_name`. If the command + /// passed is not recognized, this function should return `None`. Otherwise + /// it should return `Some`, and the value within the `Some` has the same + /// semantics as the return of `FromArgs::redact_arg_values`. + fn try_redact_arg_values( + command_name: &[&str], + args: &[&str], + ) -> Option<Result<Vec<String>, EarlyExit>>; + + /// Perform the function of `FromArgs::from_args` for this dynamic command. + /// + /// The full list of subcommands, ending with the subcommand that should be + /// dynamically recognized, is passed in `command_name`. If the command + /// passed is not recognized, this function should return `None`. Otherwise + /// it should return `Some`, and the value within the `Some` has the same + /// semantics as the return of `FromArgs::from_args`. + fn try_from_args(command_name: &[&str], args: &[&str]) -> Option<Result<Self, EarlyExit>>; +} + /// Information to display to the user about why a `FromArgs` construction exited early. /// /// This can occur due to either failed parsing or a flag like `--help`. @@ -481,8 +659,8 @@ impl From<String> for EarlyExit { } /// Extract the base cmd from a path -fn cmd<'a>(default: &'a String, path: &'a String) -> &'a str { - std::path::Path::new(path).file_name().map(|s| s.to_str()).flatten().unwrap_or(default.as_str()) +fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str { + std::path::Path::new(path).file_name().and_then(|s| s.to_str()).unwrap_or(default) } /// Create a `FromArgs` type from the current process's `env::args`. @@ -491,7 +669,19 @@ fn cmd<'a>(default: &'a String, path: &'a String) -> &'a str { /// was unsuccessful or if information like `--help` was requested. Error messages will be printed /// to stderr, and `--help` output to stdout. pub fn from_env<T: TopLevelCommand>() -> T { - let strings: Vec<String> = std::env::args().collect(); + let strings: Vec<String> = std::env::args_os() + .map(|s| s.into_string()) + .collect::<Result<Vec<_>, _>>() + .unwrap_or_else(|arg| { + eprintln!("Invalid utf8: {}", arg.to_string_lossy()); + std::process::exit(1) + }); + + if strings.is_empty() { + eprintln!("No program name, argv is empty"); + std::process::exit(1) + } + let cmd = cmd(&strings[0], &strings[0]); let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect(); T::from_args(&[cmd], &strs[1..]).unwrap_or_else(|early_exit| { @@ -501,7 +691,7 @@ pub fn from_env<T: TopLevelCommand>() -> T { 0 } Err(()) => { - eprintln!("{}", early_exit.output); + eprintln!("{}\nRun {} --help for more information.", early_exit.output, cmd); 1 } }) @@ -527,7 +717,7 @@ pub fn cargo_from_env<T: TopLevelCommand>() -> T { 0 } Err(()) => { - eprintln!("{}", early_exit.output); + eprintln!("{}\nRun --help for more information.", early_exit.output); 1 } }) @@ -644,6 +834,16 @@ impl Flag for bool { } } +impl Flag for Option<bool> { + fn default() -> Self { + None + } + + fn set_flag(&mut self) { + *self = Some(true); + } +} + macro_rules! impl_flag_for_integers { ($($ty:ty,)*) => { $( @@ -683,14 +883,14 @@ pub fn parse_struct_args( let mut positional_index = 0; let mut options_ended = false; - 'parse_args: while let Some(&next_arg) = remaining_args.get(0) { + 'parse_args: while let Some(&next_arg) = remaining_args.first() { remaining_args = &remaining_args[1..]; if (next_arg == "--help" || next_arg == "help") && !options_ended { help = true; continue; } - if next_arg.starts_with("-") && !options_ended { + if next_arg.starts_with('-') && !options_ended { if next_arg == "--" { options_ended = true; continue; @@ -712,7 +912,7 @@ pub fn parse_struct_args( } } - parse_positionals.parse(&mut positional_index, next_arg)?; + options_ended |= parse_positionals.parse(&mut positional_index, next_arg)?; } if help { @@ -751,7 +951,7 @@ impl<'a> ParseStructOptions<'a> { ParseStructOption::Flag(ref mut b) => b.set_flag(arg), ParseStructOption::Value(ref mut pvs) => { let value = remaining_args - .get(0) + .first() .ok_or_else(|| ["No value provided for option '", arg, "'.\n"].concat())?; *remaining_args = &remaining_args[1..]; pvs.fill_slot(arg, value).map_err(|s| { @@ -783,25 +983,31 @@ pub enum ParseStructOption<'a> { pub struct ParseStructPositionals<'a> { pub positionals: &'a mut [ParseStructPositional<'a>], pub last_is_repeating: bool, + pub last_is_greedy: bool, } impl<'a> ParseStructPositionals<'a> { /// Parse the next positional argument. /// /// `arg`: the argument supplied by the user. - fn parse(&mut self, index: &mut usize, arg: &str) -> Result<(), EarlyExit> { + /// + /// Returns true if non-positional argument parsing should stop + /// after this one. + fn parse(&mut self, index: &mut usize, arg: &str) -> Result<bool, EarlyExit> { if *index < self.positionals.len() { self.positionals[*index].parse(arg)?; - // Don't increment position if we're at the last arg - // *and* the last arg is repeating. - let skip_increment = self.last_is_repeating && *index == self.positionals.len() - 1; - - if !skip_increment { + if self.last_is_repeating && *index == self.positionals.len() - 1 { + // Don't increment position if we're at the last arg + // *and* the last arg is repeating. If it's also remainder, + // halt non-option processing after this. + Ok(self.last_is_greedy) + } else { + // If it is repeating, though, increment the index and continue + // processing options. *index += 1; + Ok(false) } - - Ok(()) } else { Err(EarlyExit { output: unrecognized_arg(arg), status: Err(()) }) } @@ -847,7 +1053,10 @@ pub struct ParseStructSubCommand<'a> { // The subcommand commands pub subcommands: &'static [&'static CommandInfo], + pub dynamic_subcommands: &'a [&'static CommandInfo], + // The function to parse the subcommand arguments. + #[allow(clippy::type_complexity)] pub parse_func: &'a mut dyn FnMut(&[&str], &[&str]) -> Result<(), EarlyExit>, } @@ -859,7 +1068,7 @@ impl<'a> ParseStructSubCommand<'a> { arg: &str, remaining_args: &[&str], ) -> Result<bool, EarlyExit> { - for subcommand in self.subcommands { + for subcommand in self.subcommands.iter().chain(self.dynamic_subcommands.iter()) { if subcommand.name == arg { let mut command = cmd_name.to_owned(); command.push(subcommand.name); @@ -877,7 +1086,7 @@ impl<'a> ParseStructSubCommand<'a> { } } - return Ok(false); + Ok(false) } } @@ -888,7 +1097,7 @@ fn prepend_help<'a>(args: &[&'a str]) -> Vec<&'a str> { } #[doc(hidden)] -pub fn print_subcommands(commands: &[&CommandInfo]) -> String { +pub fn print_subcommands<'a>(commands: impl Iterator<Item = &'a CommandInfo>) -> String { let mut out = String::new(); for cmd in commands { argh_shared::write_description(&mut out, cmd); @@ -905,7 +1114,7 @@ fn unrecognized_arg(arg: &str) -> String { #[derive(Default)] pub struct MissingRequirements { options: Vec<&'static str>, - subcommands: Option<&'static [&'static CommandInfo]>, + subcommands: Option<Vec<&'static CommandInfo>>, positional_args: Vec<&'static str>, } @@ -920,8 +1129,8 @@ impl MissingRequirements { // Add a missing required subcommand. #[doc(hidden)] - pub fn missing_subcommands(&mut self, commands: &'static [&'static CommandInfo]) { - self.subcommands = Some(commands); + pub fn missing_subcommands(&mut self, commands: impl Iterator<Item = &'static CommandInfo>) { + self.subcommands = Some(commands.collect()); } // Add a missing positional argument. @@ -951,7 +1160,7 @@ impl MissingRequirements { if !self.options.is_empty() { if !self.positional_args.is_empty() { - output.push_str("\n"); + output.push('\n'); } output.push_str("Required options not provided:"); for option in &self.options { @@ -960,9 +1169,9 @@ impl MissingRequirements { } } - if let Some(missing_subcommands) = self.subcommands { + if let Some(missing_subcommands) = &self.subcommands { if !self.options.is_empty() { - output.push_str("\n"); + output.push('\n'); } output.push_str("One of the following subcommands must be present:"); output.push_str(NEWLINE_INDENT); diff --git a/tests/compiletest.rs b/tests/compiletest.rs new file mode 100644 index 0000000..bd72ecd --- /dev/null +++ b/tests/compiletest.rs @@ -0,0 +1,5 @@ +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/**/*.rs"); +} diff --git a/tests/lib.rs b/tests/lib.rs index fe8c858..713c73d 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,8 +1,16 @@ -#![cfg(test)] // Copyright (c) 2020 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. +// Deny a bunch of uncommon clippy lints to make sure the generated code won't trigger a warning. +#![deny( + clippy::indexing_slicing, + clippy::panic_in_result_fn, + clippy::str_to_string, + clippy::unreachable, + clippy::unwrap_in_result +)] + use {argh::FromArgs, std::fmt::Debug}; #[test] @@ -28,6 +36,34 @@ fn basic_example() { } #[test] +fn generic_example() { + use std::fmt::Display; + use std::str::FromStr; + + #[derive(FromArgs, PartialEq, Debug)] + /// Reach new heights. + struct GoUp<S: FromStr> + where + <S as FromStr>::Err: Display, + { + /// whether or not to jump + #[argh(switch, short = 'j')] + jump: bool, + + /// how high to go + #[argh(option)] + height: usize, + + /// an optional nickname for the pilot + #[argh(option)] + pilot_nickname: Option<S>, + } + + let up = GoUp::<String>::from_args(&["cmdname"], &["--height", "5"]).expect("failed go_up"); + assert_eq!(up, GoUp::<String> { jump: false, height: 5, pilot_nickname: None }); +} + +#[test] fn custom_from_str_example() { #[derive(FromArgs)] /// Goofy thing. @@ -87,6 +123,119 @@ fn subcommand_example() { } #[test] +fn dynamic_subcommand_example() { + #[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<Result<Vec<String>, argh::EarlyExit>> { + Some(Err(argh::EarlyExit::from("Test should not redact".to_owned()))) + } + + fn try_from_args( + command_name: &[&str], + args: &[&str], + ) -> Option<Result<DynamicSubCommandImpl, argh::EarlyExit>> { + 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, PartialEq, Debug)] + /// Top-level command. + struct TopLevel { + #[argh(subcommand)] + nested: MySubCommandEnum, + } + + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum MySubCommandEnum { + One(SubCommandOne), + Two(SubCommandTwo), + #[argh(dynamic)] + ThreeFourFive(DynamicSubCommandImpl), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommandOne { + #[argh(option)] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + let one = TopLevel::from_args(&["cmdname"], &["one", "--x", "2"]).expect("sc 1"); + assert_eq!(one, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) },); + + let two = TopLevel::from_args(&["cmdname"], &["two", "--fooey"]).expect("sc 2"); + assert_eq!(two, TopLevel { nested: MySubCommandEnum::Two(SubCommandTwo { fooey: true }) },); + + let three = TopLevel::from_args(&["cmdname"], &["three", "beans"]).expect("sc 3"); + assert_eq!( + three, + TopLevel { + nested: MySubCommandEnum::ThreeFourFive(DynamicSubCommandImpl { + got: "Third command got \"beans\"".to_owned() + }) + }, + ); + + let four = TopLevel::from_args(&["cmdname"], &["four", "boulders"]).expect("sc 4"); + assert_eq!( + four, + TopLevel { + nested: MySubCommandEnum::ThreeFourFive(DynamicSubCommandImpl { + got: "Fourth command got \"boulders\"".to_owned() + }) + }, + ); + + let five = TopLevel::from_args(&["cmdname"], &["five", "gold rings"]).expect("sc 5"); + assert_eq!( + five, + TopLevel { + nested: MySubCommandEnum::ThreeFourFive(DynamicSubCommandImpl { + got: "Fifth command got \"gold rings\"".to_owned() + }) + }, + ); +} + +#[test] fn multiline_doc_comment_description() { #[derive(FromArgs)] /// Short description @@ -154,7 +303,7 @@ fn default_number() { fn default_function() { const MSG: &str = "hey I just met you"; fn call_me_maybe() -> String { - MSG.to_string() + MSG.to_owned() } #[derive(FromArgs)] @@ -311,6 +460,90 @@ Options: #[derive(FromArgs, Debug, PartialEq)] /// Woot + struct LastRepeatingGreedy { + #[argh(positional)] + /// fooey + a: u32, + #[argh(switch)] + /// woo + b: bool, + #[argh(option)] + /// stuff + c: Option<String>, + #[argh(positional, greedy)] + /// fooey + d: Vec<String>, + } + + #[test] + fn positional_greedy() { + assert_output(&["5"], LastRepeatingGreedy { a: 5, b: false, c: None, d: vec![] }); + assert_output( + &["5", "foo"], + LastRepeatingGreedy { a: 5, b: false, c: None, d: vec!["foo".into()] }, + ); + assert_output( + &["5", "foo", "bar"], + LastRepeatingGreedy { a: 5, b: false, c: None, d: vec!["foo".into(), "bar".into()] }, + ); + assert_output( + &["5", "--b", "foo", "bar"], + LastRepeatingGreedy { a: 5, b: true, c: None, d: vec!["foo".into(), "bar".into()] }, + ); + assert_output( + &["5", "foo", "bar", "--b"], + LastRepeatingGreedy { + a: 5, + b: false, + c: None, + d: vec!["foo".into(), "bar".into(), "--b".into()], + }, + ); + assert_output( + &["5", "--c", "hi", "foo", "bar"], + LastRepeatingGreedy { + a: 5, + b: false, + c: Some("hi".into()), + d: vec!["foo".into(), "bar".into()], + }, + ); + assert_output( + &["5", "foo", "bar", "--c", "hi"], + LastRepeatingGreedy { + a: 5, + b: false, + c: None, + d: vec!["foo".into(), "bar".into(), "--c".into(), "hi".into()], + }, + ); + assert_output( + &["5", "foo", "bar", "--", "hi"], + LastRepeatingGreedy { + a: 5, + b: false, + c: None, + d: vec!["foo".into(), "bar".into(), "--".into(), "hi".into()], + }, + ); + assert_help_string::<LastRepeatingGreedy>( + r###"Usage: test_arg_0 <a> [--b] [--c <c>] [d...] + +Woot + +Positional Arguments: + a fooey + +Options: + --b woo + --c stuff + --help display usage information +"###, + ); + } + + #[derive(FromArgs, Debug, PartialEq)] + /// Woot struct LastOptional { #[argh(positional)] /// fooey @@ -470,6 +703,26 @@ Required options not provided: }, ); } + + #[derive(FromArgs, Debug, PartialEq)] + /// Woot + struct Underscores { + #[argh(positional)] + /// fooey + a_: String, + } + + #[test] + fn positional_name_with_underscores() { + assert_output(&["first"], Underscores { a_: "first".into() }); + + assert_error::<Underscores>( + &[], + r###"Required positional arguments not provided: + a +"###, + ); + } } /// Tests derived from @@ -790,6 +1043,8 @@ Options: enum HelpExampleSubCommands { BlowUp(BlowUp), Grind(GrindCommand), + #[argh(dynamic)] + Plugin(HelpExamplePlugin), } #[derive(FromArgs, PartialEq, Debug)] @@ -809,6 +1064,39 @@ Options: safely: bool, } + #[derive(PartialEq, Debug)] + struct HelpExamplePlugin { + got: String, + } + + impl argh::DynamicSubCommand for HelpExamplePlugin { + fn commands() -> &'static [&'static argh::CommandInfo] { + &[&argh::CommandInfo { name: "plugin", description: "Example dynamic command" }] + } + + fn try_redact_arg_values( + _command_name: &[&str], + _args: &[&str], + ) -> Option<Result<Vec<String>, argh::EarlyExit>> { + Some(Err(argh::EarlyExit::from("Test should not redact".to_owned()))) + } + + fn try_from_args( + command_name: &[&str], + args: &[&str], + ) -> Option<Result<HelpExamplePlugin, argh::EarlyExit>> { + if command_name.last() != Some(&"plugin") { + None + } else if args.len() > 1 { + Some(Err(argh::EarlyExit::from("Too many arguments".to_owned()))) + } else if let Some(arg) = args.first() { + Some(Ok(HelpExamplePlugin { got: format!("plugin got {:?}", arg) })) + } else { + Some(Ok(HelpExamplePlugin { got: "plugin got no argument".to_owned() })) + } + } + } + #[test] fn example_parses_correctly() { let help_example = HelpExample::from_args( @@ -821,7 +1109,7 @@ Options: help_example, HelpExample { force: true, - scribble: "fooey".to_string(), + scribble: "fooey".to_owned(), really_really_really_long_name_for_pat: false, verbose: false, command: HelpExampleSubCommands::BlowUp(BlowUp { safely: true }), @@ -842,6 +1130,7 @@ Options: " help\n", " blow-up\n", " grind\n", + " plugin\n", ), ); } @@ -865,6 +1154,7 @@ Options: Commands: blow-up explosively separate grind make smaller by many small cuts + plugin Example dynamic command Examples: Scribble 'abc' and then run |grind|. @@ -903,6 +1193,36 @@ Options: "###, ); } + + #[test] + fn hidden_help_attribute() { + #[derive(FromArgs)] + /// 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_help_string::<Cmd>( + r###"Usage: test_arg_0 <two> + +Short description + +Positional Arguments: + two this one is real + +Options: + --help display usage information +"###, + ); + } } #[test] @@ -1264,7 +1584,7 @@ Options: -n, --n fooey --help display usage information "### - .to_string(), + .to_owned(), status: Ok(()), }), ); @@ -1283,7 +1603,7 @@ fn redact_arg_values_produces_errors_with_bad_arguments() { assert_eq!( Cmd::redact_arg_values(&["program-name"], &["--n"]), Err(argh::EarlyExit { - output: "No value provided for option '--n'.\n".to_string(), + output: "No value provided for option '--n'.\n".to_owned(), status: Err(()), }), ); @@ -1306,3 +1626,72 @@ fn redact_arg_values_does_not_warn_if_used() { let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap(); assert_eq!(actual, &["program-name", "speed"]); } + +#[test] +fn subcommand_does_not_panic() { + #[derive(FromArgs, PartialEq, Debug)] + #[argh(subcommand)] + enum SubCommandEnum { + Cmd(SubCommand), + } + + #[derive(FromArgs, PartialEq, Debug)] + /// First subcommand. + #[argh(subcommand, name = "one")] + struct SubCommand { + #[argh(positional)] + /// how many x + x: usize, + } + + #[derive(FromArgs, PartialEq, Debug)] + /// Second subcommand. + #[argh(subcommand, name = "two")] + struct SubCommandTwo { + #[argh(switch)] + /// whether to fooey + fooey: bool, + } + + // Passing no subcommand name to an emum + assert_eq!( + SubCommandEnum::from_args(&[], &["5"]).unwrap_err(), + argh::EarlyExit { output: "no subcommand name".into(), status: Err(()) }, + ); + + assert_eq!( + SubCommandEnum::redact_arg_values(&[], &["5"]).unwrap_err(), + argh::EarlyExit { output: "no subcommand name".into(), status: Err(()) }, + ); + + // Passing unknown subcommand name to an emum + assert_eq!( + SubCommandEnum::from_args(&["fooey"], &["5"]).unwrap_err(), + argh::EarlyExit { output: "no subcommand matched".into(), status: Err(()) }, + ); + + assert_eq!( + SubCommandEnum::redact_arg_values(&["fooey"], &["5"]).unwrap_err(), + argh::EarlyExit { output: "no subcommand matched".into(), status: Err(()) }, + ); + + // Passing unknown subcommand name to a struct + assert_eq!( + SubCommand::redact_arg_values(&[], &["5"]).unwrap_err(), + argh::EarlyExit { output: "no subcommand name".into(), status: Err(()) }, + ); +} + +#[test] +fn long_alphanumeric() { + #[derive(FromArgs)] + /// Short description + struct Cmd { + #[argh(option, long = "ac97")] + /// fooey + ac97: String, + } + + let cmd = Cmd::from_args(&["cmdname"], &["--ac97", "bar"]).unwrap(); + assert_eq!(cmd.ac97, "bar"); +} diff --git a/tests/ui/bad-long-names/bad-long-names.rs b/tests/ui/bad-long-names/bad-long-names.rs new file mode 100644 index 0000000..61a9696 --- /dev/null +++ b/tests/ui/bad-long-names/bad-long-names.rs @@ -0,0 +1,15 @@ +/// Command +#[derive(argh::FromArgs)] +struct Cmd { + #[argh(switch)] + /// non-ascii + привет: bool, + #[argh(switch)] + /// uppercase + XMLHTTPRequest: bool, + #[argh(switch, long = "not really")] + /// bad attr + ok: bool, +} + +fn main() {} diff --git a/tests/ui/bad-long-names/bad-long-names.stderr b/tests/ui/bad-long-names/bad-long-names.stderr new file mode 100644 index 0000000..3396a5a --- /dev/null +++ b/tests/ui/bad-long-names/bad-long-names.stderr @@ -0,0 +1,17 @@ +error: Long names must be ASCII + --> tests/ui/bad-long-names/bad-long-names.rs:6:5 + | +6 | привет: bool, + | ^^^^^^ + +error: Long names must be lowercase + --> tests/ui/bad-long-names/bad-long-names.rs:9:5 + | +9 | XMLHTTPRequest: bool, + | ^^^^^^^^^^^^^^ + +error: Long names must be lowercase + --> tests/ui/bad-long-names/bad-long-names.rs:10:27 + | +10 | #[argh(switch, long = "not really")] + | ^^^^^^^^^^^^ diff --git a/tests/ui/conflicting-tails/positional-and-greedy.rs b/tests/ui/conflicting-tails/positional-and-greedy.rs new file mode 100644 index 0000000..18c039a --- /dev/null +++ b/tests/ui/conflicting-tails/positional-and-greedy.rs @@ -0,0 +1,13 @@ +/// Command +#[derive(argh::FromArgs)] +struct Cmd { + #[argh(positional)] + /// positional + positional: Vec<String>, + + #[argh(positional, greedy)] + /// remainder + remainder: Vec<String>, +} + +fn main() {} diff --git a/tests/ui/conflicting-tails/positional-and-greedy.stderr b/tests/ui/conflicting-tails/positional-and-greedy.stderr new file mode 100644 index 0000000..f6a6399 --- /dev/null +++ b/tests/ui/conflicting-tails/positional-and-greedy.stderr @@ -0,0 +1,11 @@ +error: Only the last positional argument may be `Option`, `Vec`, or defaulted. + --> tests/ui/conflicting-tails/positional-and-greedy.rs:4:5 + | +4 | #[argh(positional)] + | ^ + +error: Later positional argument declared here. + --> tests/ui/conflicting-tails/positional-and-greedy.rs:8:5 + | +8 | #[argh(positional, greedy)] + | ^ diff --git a/tests/ui/duplicate-name/duplicate-long-name.rs b/tests/ui/duplicate-name/duplicate-long-name.rs new file mode 100644 index 0000000..550a7aa --- /dev/null +++ b/tests/ui/duplicate-name/duplicate-long-name.rs @@ -0,0 +1,21 @@ +/// Command +#[derive(argh::FromArgs)] +struct Cmd { + /// foo1 + #[argh(option, long = "foo")] + foo1: u32, + + /// foo2 + #[argh(option, long = "foo")] + foo2: u32, + + /// bar1 + #[argh(option, long = "bar")] + bar1: u32, + + /// bar2 + #[argh(option, long = "bar")] + bar2: u32, +} + +fn main() {} diff --git a/tests/ui/duplicate-name/duplicate-long-name.stderr b/tests/ui/duplicate-name/duplicate-long-name.stderr new file mode 100644 index 0000000..697f36b --- /dev/null +++ b/tests/ui/duplicate-name/duplicate-long-name.stderr @@ -0,0 +1,31 @@ +error: The long name of "--foo" was already used here. + --> tests/ui/duplicate-name/duplicate-long-name.rs:4:5 + | +4 | / /// foo1 +5 | | #[argh(option, long = "foo")] +6 | | foo1: u32, + | |_____________^ + +error: Later usage here. + --> tests/ui/duplicate-name/duplicate-long-name.rs:8:5 + | +8 | / /// foo2 +9 | | #[argh(option, long = "foo")] +10 | | foo2: u32, + | |_____________^ + +error: The long name of "--bar" was already used here. + --> tests/ui/duplicate-name/duplicate-long-name.rs:12:5 + | +12 | / /// bar1 +13 | | #[argh(option, long = "bar")] +14 | | bar1: u32, + | |_____________^ + +error: Later usage here. + --> tests/ui/duplicate-name/duplicate-long-name.rs:16:5 + | +16 | / /// bar2 +17 | | #[argh(option, long = "bar")] +18 | | bar2: u32, + | |_____________^ diff --git a/tests/ui/duplicate-name/duplicate-short-name.rs b/tests/ui/duplicate-name/duplicate-short-name.rs new file mode 100644 index 0000000..a52bddb --- /dev/null +++ b/tests/ui/duplicate-name/duplicate-short-name.rs @@ -0,0 +1,21 @@ +/// Command +#[derive(argh::FromArgs)] +struct Cmd { + /// foo1 + #[argh(option, short = 'f')] + foo1: u32, + + /// foo2 + #[argh(option, short = 'f')] + foo2: u32, + + /// bar1 + #[argh(option, short = 'b')] + bar1: u32, + + /// bar2 + #[argh(option, short = 'b')] + bar2: u32, +} + +fn main() {} diff --git a/tests/ui/duplicate-name/duplicate-short-name.stderr b/tests/ui/duplicate-name/duplicate-short-name.stderr new file mode 100644 index 0000000..9262a7d --- /dev/null +++ b/tests/ui/duplicate-name/duplicate-short-name.stderr @@ -0,0 +1,31 @@ +error: The short name of "-f" was already used here. + --> tests/ui/duplicate-name/duplicate-short-name.rs:4:5 + | +4 | / /// foo1 +5 | | #[argh(option, short = 'f')] +6 | | foo1: u32, + | |_____________^ + +error: Later usage here. + --> tests/ui/duplicate-name/duplicate-short-name.rs:8:5 + | +8 | / /// foo2 +9 | | #[argh(option, short = 'f')] +10 | | foo2: u32, + | |_____________^ + +error: The short name of "-b" was already used here. + --> tests/ui/duplicate-name/duplicate-short-name.rs:12:5 + | +12 | / /// bar1 +13 | | #[argh(option, short = 'b')] +14 | | bar1: u32, + | |_____________^ + +error: Later usage here. + --> tests/ui/duplicate-name/duplicate-short-name.rs:16:5 + | +16 | / /// bar2 +17 | | #[argh(option, short = 'b')] +18 | | bar2: u32, + | |_____________^ |