argh-0.1.9/.cargo_vcs_info.json0000644000000001420000000000100117610ustar { "git": { "sha1": "adc704cd29f710864b0fc1872bc86f857bebfdbf" }, "path_in_vcs": "argh" }argh-0.1.9/Cargo.lock0000644000000114370000000000100077450ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "argh" version = "0.1.9" dependencies = [ "argh_derive", "argh_shared", "once_cell", "trybuild", ] [[package]] name = "argh_derive" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa013479b80109a1bf01a039412b0f0013d716f36921226d86c6709032fb7a03" dependencies = [ "argh_shared", "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "argh_shared" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "149f75bbec1827618262e0855a68f0f9a7f2edc13faebf33c4f16d6725edb6a9" [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "heck" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "itoa" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "once_cell" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "proc-macro2" version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "serde" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" [[package]] name = "serde_derive" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" 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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] [[package]] name = "trybuild" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f408301c7480f9e6294eb779cfc907f54bd901a9660ef24d7f233ed5376485" dependencies = [ "glob", "once_cell", "serde", "serde_derive", "serde_json", "termcolor", "toml", ] [[package]] name = "unicode-ident" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[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" argh-0.1.9/Cargo.toml0000644000000021030000000000100077560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "argh" version = "0.1.9" authors = [ "Taylor Cramer ", "Benjamin Brittain ", "Erick Tryzelaar ", ] description = "Derive-based argument parser optimized for code size" readme = "README.md" keywords = [ "args", "arguments", "derive", "cli", ] license = "BSD-3-Clause" repository = "https://github.com/google/argh" [dependencies.argh_derive] version = "0.1.9" [dependencies.argh_shared] version = "0.1.9" [dev-dependencies.once_cell] version = "1.10.0" [dev-dependencies.trybuild] version = "1.0.63" argh-0.1.9/Cargo.toml.orig000064400000000000000000000011301046102023000134360ustar 00000000000000[package] name = "argh" version = "0.1.9" authors = ["Taylor Cramer ", "Benjamin Brittain ", "Erick Tryzelaar "] edition = "2018" keywords = ["args", "arguments", "derive", "cli"] license = "BSD-3-Clause" description = "Derive-based argument parser optimized for code size" repository = "https://github.com/google/argh" readme = "README.md" [dependencies] argh_shared = { version = "0.1.9", path = "../argh_shared" } argh_derive = { version = "0.1.9", path = "../argh_derive" } [dev-dependencies] once_cell = "1.10.0" trybuild = "1.0.63" argh-0.1.9/LICENSE000064400000000000000000000027101046102023000115610ustar 00000000000000Copyright 2019 The Fuchsia Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. argh-0.1.9/README.md000064400000000000000000000116311046102023000120350ustar 00000000000000# Argh **Argh is an opinionated Derive-based argument parser optimized for code size** [![crates.io](https://img.shields.io/crates/v/argh.svg)](https://crates.io/crates/argh) [![license](https://img.shields.io/badge/license-BSD3.0-blue.svg)](https://github.com/google/argh/LICENSE) [![docs.rs](https://docs.rs/argh/badge.svg)](https://docs.rs/crate/argh/) ![Argh](https://github.com/google/argh/workflows/Argh/badge.svg) Derive-based argument parsing optimized for code size and conformance to the Fuchsia commandline tools specification The public API of this library consists primarily of the `FromArgs` derive and the `from_env` function, which can be used to produce a top-level `FromArgs` type from the current program's commandline arguments. ## Basic Example ```rust,no_run use argh::FromArgs; #[derive(FromArgs)] /// Reach new heights. struct GoUp { /// 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, } fn main() { let up: GoUp = argh::from_env(); } ``` `./some_bin --help` will then output the following: ``` Usage: cmdname [-j] --height [--pilot-nickname ] Reach new heights. Options: -j, --jump whether or not to jump --height how high to go --pilot-nickname an optional nickname for the pilot --help display usage information ``` The resulting program can then be used in any of these ways: - `./some_bin --height 5` - `./some_bin -j --height 5` - `./some_bin --jump --height 5 --pilot-nickname Wes` Switches, like `jump`, are optional and will be set to true if provided. Options, like `height` and `pilot_nickname`, can be either required, optional, or repeating, depending on whether they are contained in an `Option` or a `Vec`. Default values can be provided using the `#[argh(default = "")]` attribute, and in this case an option is treated as optional. ```rust use argh::FromArgs; fn default_height() -> usize { 5 } #[derive(FromArgs)] /// Reach new heights. struct GoUp { /// an optional nickname for the pilot #[argh(option)] pilot_nickname: Option, /// an optional height #[argh(option, default = "default_height()")] height: usize, /// an optional direction which is "up" by default #[argh(option, default = "String::from(\"only up\")")] direction: String, } fn main() { let up: GoUp = argh::from_env(); } ``` Custom option types can be deserialized so long as they implement the `FromArgValue` trait (automatically implemented for all `FromStr` types). If more customized parsing is required, you can supply a custom `fn(&str) -> Result` using the `from_str_fn` attribute: ```rust use argh::FromArgs; #[derive(FromArgs)] /// Goofy thing. struct FiveStruct { /// always five #[argh(option, from_str_fn(always_five))] five: usize, } fn always_five(_value: &str) -> Result { Ok(5) } ``` Positional arguments can be declared using `#[argh(positional)]`. These arguments will be parsed in order of their declaration in the structure: ```rust use argh::FromArgs; #[derive(FromArgs, PartialEq, Debug)] /// A command with positional arguments. struct WithPositional { #[argh(positional)] first: String, } ``` The last positional argument may include a default, or be wrapped in `Option` or `Vec` to indicate an optional or repeating positional argument. 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: ```rust use argh::FromArgs; #[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, } ``` 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. argh-0.1.9/examples/simple_example.rs000064400000000000000000000016261046102023000157510ustar 00000000000000// 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); } argh-0.1.9/src/lib.rs000064400000000000000000001107011046102023000124570ustar 00000000000000// 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. //! Derive-based argument parsing optimized for code size and conformance //! to the Fuchsia commandline tools specification //! //! The public API of this library consists primarily of the `FromArgs` //! derive and the `from_env` function, which can be used to produce //! a top-level `FromArgs` type from the current program's commandline //! arguments. //! //! ## Basic Example //! //! ```rust,no_run //! use argh::FromArgs; //! //! #[derive(FromArgs)] //! /// Reach new heights. //! struct GoUp { //! /// 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, //! } //! //! let up: GoUp = argh::from_env(); //! ``` //! //! `./some_bin --help` will then output the following: //! //! ```bash //! Usage: cmdname [-j] --height [--pilot-nickname ] //! //! Reach new heights. //! //! Options: //! -j, --jump whether or not to jump //! --height how high to go //! --pilot-nickname an optional nickname for the pilot //! --help display usage information //! ``` //! //! The resulting program can then be used in any of these ways: //! - `./some_bin --height 5` //! - `./some_bin -j --height 5` //! - `./some_bin --jump --height 5 --pilot-nickname Wes` //! //! Switches, like `jump`, are optional and will be set to true if provided. //! //! Options, like `height` and `pilot_nickname`, can be either required, //! optional, or repeating, depending on whether they are contained in an //! `Option` or a `Vec`. Default values can be provided using the //! `#[argh(default = "")]` attribute, and in this case an //! option is treated as optional. //! //! ```rust //! use argh::FromArgs; //! //! fn default_height() -> usize { //! 5 //! } //! //! #[derive(FromArgs)] //! /// Reach new heights. //! struct GoUp { //! /// an optional nickname for the pilot //! #[argh(option)] //! pilot_nickname: Option, //! //! /// an optional height //! #[argh(option, default = "default_height()")] //! height: usize, //! //! /// an optional direction which is "up" by default //! #[argh(option, default = "String::from(\"only up\")")] //! direction: String, //! } //! //! fn main() { //! let up: GoUp = argh::from_env(); //! } //! ``` //! //! Custom option types can be deserialized so long as they implement the //! `FromArgValue` trait (automatically implemented for all `FromStr` types). //! If more customized parsing is required, you can supply a custom //! `fn(&str) -> Result` using the `from_str_fn` attribute: //! //! ``` //! # use argh::FromArgs; //! //! #[derive(FromArgs)] //! /// Goofy thing. //! struct FiveStruct { //! /// always five //! #[argh(option, from_str_fn(always_five))] //! five: usize, //! } //! //! fn always_five(_value: &str) -> Result { //! Ok(5) //! } //! ``` //! //! Positional arguments can be declared using `#[argh(positional)]`. //! These arguments will be parsed in order of their declaration in //! the structure: //! //! ```rust //! use argh::FromArgs; //! #[derive(FromArgs, PartialEq, Debug)] //! /// A command with positional arguments. //! struct WithPositional { //! #[argh(positional)] //! first: String, //! } //! ``` //! //! 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, //! #[argh(positional, greedy)] //! all_the_rest: Vec, //! } //! ``` //! //! 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: //! //! ```rust //! # use argh::FromArgs; //! //! #[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, //! } //! ``` //! //! 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> = 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, 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> { //! 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 //! } //! } //! ``` #![deny(missing_docs)] use std::str::FromStr; pub use argh_derive::FromArgs; /// Information about a particular command used for output. pub type CommandInfo = argh_shared::CommandInfo<'static>; /// Types which can be constructed from a set of commandline arguments. pub trait FromArgs: Sized { /// Construct the type from an input set of arguments. /// /// The first argument `command_name` is the identifier for the current command. In most cases, /// users should only pass in a single item for the command name, which typically comes from /// the first item from `std::env::args()`. Implementations however should append the /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This /// allows `argh` to generate correct subcommand help strings. /// /// The second argument `args` is the rest of the command line arguments. /// /// # Examples /// /// ```rust /// # use argh::FromArgs; /// /// /// Command to manage a classroom. /// #[derive(Debug, PartialEq, FromArgs)] /// struct ClassroomCmd { /// #[argh(subcommand)] /// subcommands: Subcommands, /// } /// /// #[derive(Debug, PartialEq, FromArgs)] /// #[argh(subcommand)] /// enum Subcommands { /// List(ListCmd), /// Add(AddCmd), /// } /// /// /// list all the classes. /// #[derive(Debug, PartialEq, FromArgs)] /// #[argh(subcommand, name = "list")] /// struct ListCmd { /// /// list classes for only this teacher. /// #[argh(option)] /// teacher_name: Option, /// } /// /// /// add students to a class. /// #[derive(Debug, PartialEq, FromArgs)] /// #[argh(subcommand, name = "add")] /// struct AddCmd { /// /// the name of the class's teacher. /// #[argh(option)] /// teacher_name: String, /// /// /// the name of the class. /// #[argh(positional)] /// class_name: String, /// } /// /// let args = ClassroomCmd::from_args( /// &["classroom"], /// &["list", "--teacher-name", "Smith"], /// ).unwrap(); /// assert_eq!( /// args, /// ClassroomCmd { /// subcommands: Subcommands::List(ListCmd { /// teacher_name: Some("Smith".to_string()), /// }) /// }, /// ); /// /// // Help returns an error, but internally returns an `Ok` status. /// let early_exit = ClassroomCmd::from_args( /// &["classroom"], /// &["help"], /// ).unwrap_err(); /// assert_eq!( /// early_exit, /// argh::EarlyExit { /// output: r#"Usage: classroom [] /// /// Command to manage a classroom. /// /// Options: /// --help display usage information /// /// Commands: /// list list all the classes. /// add add students to a class. /// "#.to_string(), /// status: Ok(()), /// }, /// ); /// /// // Help works with subcommands. /// let early_exit = ClassroomCmd::from_args( /// &["classroom"], /// &["list", "help"], /// ).unwrap_err(); /// assert_eq!( /// early_exit, /// argh::EarlyExit { /// output: r#"Usage: classroom list [--teacher-name ] /// /// list all the classes. /// /// Options: /// --teacher-name list classes for only this teacher. /// --help display usage information /// "#.to_string(), /// status: Ok(()), /// }, /// ); /// /// // Incorrect arguments will error out. /// let err = ClassroomCmd::from_args( /// &["classroom"], /// &["lisp"], /// ).unwrap_err(); /// assert_eq!( /// err, /// argh::EarlyExit { /// output: "Unrecognized argument: lisp\n".to_string(), /// status: Err(()), /// }, /// ); /// ``` fn from_args(command_name: &[&str], args: &[&str]) -> Result; /// Get a String with just the argument names, e.g., options, flags, subcommands, etc, but /// without the values of the options and arguments. This can be useful as a means to capture /// anonymous usage statistics without revealing the content entered by the end user. /// /// The first argument `command_name` is the identifier for the current command. In most cases, /// users should only pass in a single item for the command name, which typically comes from /// the first item from `std::env::args()`. Implementations however should append the /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This /// allows `argh` to generate correct subcommand help strings. /// /// The second argument `args` is the rest of the command line arguments. /// /// # Examples /// /// ```rust /// # use argh::FromArgs; /// /// /// Command to manage a classroom. /// #[derive(FromArgs)] /// struct ClassroomCmd { /// #[argh(subcommand)] /// subcommands: Subcommands, /// } /// /// #[derive(FromArgs)] /// #[argh(subcommand)] /// enum Subcommands { /// List(ListCmd), /// Add(AddCmd), /// } /// /// /// list all the classes. /// #[derive(FromArgs)] /// #[argh(subcommand, name = "list")] /// struct ListCmd { /// /// list classes for only this teacher. /// #[argh(option)] /// teacher_name: Option, /// } /// /// /// add students to a class. /// #[derive(FromArgs)] /// #[argh(subcommand, name = "add")] /// struct AddCmd { /// /// the name of the class's teacher. /// #[argh(option)] /// teacher_name: String, /// /// /// has the class started yet? /// #[argh(switch)] /// started: bool, /// /// /// the name of the class. /// #[argh(positional)] /// class_name: String, /// /// /// the student names. /// #[argh(positional)] /// students: Vec, /// } /// /// let args = ClassroomCmd::redact_arg_values( /// &["classroom"], /// &["list"], /// ).unwrap(); /// assert_eq!( /// args, /// &[ /// "classroom", /// "list", /// ], /// ); /// /// let args = ClassroomCmd::redact_arg_values( /// &["classroom"], /// &["list", "--teacher-name", "Smith"], /// ).unwrap(); /// assert_eq!( /// args, /// &[ /// "classroom", /// "list", /// "--teacher-name", /// ], /// ); /// /// let args = ClassroomCmd::redact_arg_values( /// &["classroom"], /// &["add", "--teacher-name", "Smith", "--started", "Math", "Abe", "Sung"], /// ).unwrap(); /// assert_eq!( /// args, /// &[ /// "classroom", /// "add", /// "--teacher-name", /// "--started", /// "class_name", /// "students", /// "students", /// ], /// ); /// /// // `ClassroomCmd::redact_arg_values` will error out if passed invalid arguments. /// assert_eq!( /// ClassroomCmd::redact_arg_values(&["classroom"], &["add", "--teacher-name"]), /// Err(argh::EarlyExit { /// output: "No value provided for option '--teacher-name'.\n".into(), /// status: Err(()), /// }), /// ); /// /// // `ClassroomCmd::redact_arg_values` will generate help messages. /// assert_eq!( /// ClassroomCmd::redact_arg_values(&["classroom"], &["help"]), /// Err(argh::EarlyExit { /// output: r#"Usage: classroom [] /// /// Command to manage a classroom. /// /// Options: /// --help display usage information /// /// Commands: /// list list all the classes. /// add add students to a class. /// "#.to_string(), /// status: Ok(()), /// }), /// ); /// ``` fn redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result, EarlyExit> { Ok(vec!["<>".into()]) } } /// A top-level `FromArgs` implementation that is not a subcommand. pub trait TopLevelCommand: FromArgs {} /// A `FromArgs` implementation that can parse into one or more subcommands. 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. pub trait SubCommand: FromArgs { /// Information about the subcommand. const COMMAND: &'static CommandInfo; } impl 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, 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>; } /// 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`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct EarlyExit { /// The output to display to the user of the commandline tool. pub output: String, /// Status of argument parsing. /// /// `Ok` if the command was parsed successfully and the early exit is due /// to a flag like `--help` causing early exit with output. /// /// `Err` if the arguments were not successfully parsed. // TODO replace with std::process::ExitCode when stable. pub status: Result<(), ()>, } impl From for EarlyExit { fn from(err_msg: String) -> Self { Self { output: err_msg, status: Err(()) } } } /// Extract the base cmd from a path 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`. /// /// This function will exit early from the current process if argument parsing /// 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 { let strings: Vec = std::env::args().collect(); 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| { std::process::exit(match early_exit.status { Ok(()) => { println!("{}", early_exit.output); 0 } Err(()) => { eprintln!("{}\nRun {} --help for more information.", early_exit.output, cmd); 1 } }) }) } /// Create a `FromArgs` type from the current process's `env::args`. /// /// This special cases usages where argh is being used in an environment where cargo is /// driving the build. We skip the second env variable. /// /// This function will exit early from the current process if argument parsing /// was unsuccessful or if information like `--help` was requested. Error messages will be printed /// to stderr, and `--help` output to stdout. pub fn cargo_from_env() -> T { let strings: Vec = std::env::args().collect(); let cmd = cmd(&strings[1], &strings[1]); let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect(); T::from_args(&[cmd], &strs[2..]).unwrap_or_else(|early_exit| { std::process::exit(match early_exit.status { Ok(()) => { println!("{}", early_exit.output); 0 } Err(()) => { eprintln!("{}\nRun --help for more information.", early_exit.output); 1 } }) }) } /// Types which can be constructed from a single commandline value. /// /// Any field type declared in a struct that derives `FromArgs` must implement /// this trait. A blanket implementation exists for types implementing /// `FromStr`. Custom types can implement this trait /// directly. pub trait FromArgValue: Sized { /// Construct the type from a commandline value, returning an error string /// on failure. fn from_arg_value(value: &str) -> Result; } impl FromArgValue for T where T: FromStr, T::Err: std::fmt::Display, { fn from_arg_value(value: &str) -> Result { T::from_str(value).map_err(|x| x.to_string()) } } // The following items are all used by the generated code, and should not be considered part // of this library's public API surface. #[doc(hidden)] pub trait ParseFlag { fn set_flag(&mut self, arg: &str); } impl ParseFlag for T { fn set_flag(&mut self, _arg: &str) { ::set_flag(self); } } #[doc(hidden)] pub struct RedactFlag { pub slot: Option, } impl ParseFlag for RedactFlag { fn set_flag(&mut self, arg: &str) { self.slot = Some(arg.to_string()); } } // A trait for for slots that reserve space for a value and know how to parse that value // from a command-line `&str` argument. // // This trait is only implemented for the type `ParseValueSlotTy`. This indirection is // necessary to allow abstracting over `ParseValueSlotTy` instances with different // generic parameters. #[doc(hidden)] pub trait ParseValueSlot { fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>; } // The concrete type implementing the `ParseValueSlot` trait. // // `T` is the type to be parsed from a single string. // `Slot` is the type of the container that can hold a value or values of type `T`. #[doc(hidden)] pub struct ParseValueSlotTy { // The slot for a parsed value. pub slot: Slot, // The function to parse the value from a string pub parse_func: fn(&str, &str) -> Result, } // `ParseValueSlotTy, T>` is used as the slot for all non-repeating // arguments, both optional and required. impl ParseValueSlot for ParseValueSlotTy, T> { fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> { if self.slot.is_some() { return Err("duplicate values provided".to_string()); } self.slot = Some((self.parse_func)(arg, value)?); Ok(()) } } // `ParseValueSlotTy, T>` is used as the slot for repeating arguments. impl ParseValueSlot for ParseValueSlotTy, T> { fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> { self.slot.push((self.parse_func)(arg, value)?); Ok(()) } } /// A type which can be the receiver of a `Flag`. pub trait Flag { /// Creates a default instance of the flag value; fn default() -> Self where Self: Sized; /// Sets the flag. This function is called when the flag is provided. fn set_flag(&mut self); } impl Flag for bool { fn default() -> Self { false } fn set_flag(&mut self) { *self = true; } } macro_rules! impl_flag_for_integers { ($($ty:ty,)*) => { $( impl Flag for $ty { fn default() -> Self { 0 } fn set_flag(&mut self) { *self = self.saturating_add(1); } } )* } } impl_flag_for_integers![u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,]; /// This function implements argument parsing for structs. /// /// `cmd_name`: The identifier for the current command. /// `args`: The command line arguments. /// `parse_options`: Helper to parse optional arguments. /// `parse_positionals`: Helper to parse positional arguments. /// `parse_subcommand`: Helper to parse a subcommand. /// `help_func`: Generate a help message. #[doc(hidden)] pub fn parse_struct_args( cmd_name: &[&str], args: &[&str], mut parse_options: ParseStructOptions<'_>, mut parse_positionals: ParseStructPositionals<'_>, mut parse_subcommand: Option>, help_func: &dyn Fn() -> String, ) -> Result<(), EarlyExit> { let mut help = false; let mut remaining_args = args; let mut positional_index = 0; let mut options_ended = false; '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 == "--" { options_ended = true; continue; } if help { return Err("Trailing arguments are not allowed after `help`.".to_string().into()); } parse_options.parse(next_arg, &mut remaining_args)?; continue; } if let Some(ref mut parse_subcommand) = parse_subcommand { if parse_subcommand.parse(help, cmd_name, next_arg, remaining_args)? { // Unset `help`, since we handled it in the subcommand help = false; break 'parse_args; } } options_ended |= parse_positionals.parse(&mut positional_index, next_arg)?; } if help { Err(EarlyExit { output: help_func(), status: Ok(()) }) } else { Ok(()) } } #[doc(hidden)] pub struct ParseStructOptions<'a> { /// A mapping from option string literals to the entry /// in the output table. This may contain multiple entries mapping to /// the same location in the table if both a short and long version /// of the option exist (`-z` and `--zoo`). pub arg_to_slot: &'static [(&'static str, usize)], /// The storage for argument output data. pub slots: &'a mut [ParseStructOption<'a>], } impl<'a> ParseStructOptions<'a> { /// Parse a commandline option. /// /// `arg`: the current option argument being parsed (e.g. `--foo`). /// `remaining_args`: the remaining command line arguments. This slice /// will be advanced forwards if the option takes a value argument. fn parse(&mut self, arg: &str, remaining_args: &mut &[&str]) -> Result<(), String> { let pos = self .arg_to_slot .iter() .find_map(|&(name, pos)| if name == arg { Some(pos) } else { None }) .ok_or_else(|| unrecognized_argument(arg))?; match self.slots[pos] { ParseStructOption::Flag(ref mut b) => b.set_flag(arg), ParseStructOption::Value(ref mut pvs) => { let value = remaining_args .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| { ["Error parsing option '", arg, "' with value '", value, "': ", &s, "\n"] .concat() })?; } } Ok(()) } } fn unrecognized_argument(x: &str) -> String { ["Unrecognized argument: ", x, "\n"].concat() } // `--` or `-` options, including a mutable reference to their value. #[doc(hidden)] pub enum ParseStructOption<'a> { // A flag which is set to `true` when provided. Flag(&'a mut dyn ParseFlag), // A value which is parsed from the string following the `--` argument, // e.g. `--foo bar`. Value(&'a mut dyn ParseValueSlot), } #[doc(hidden)] 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. /// /// Returns true if non-positional argument parsing should stop /// after this one. fn parse(&mut self, index: &mut usize, arg: &str) -> Result { if *index < self.positionals.len() { self.positionals[*index].parse(arg)?; 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) } } else { Err(EarlyExit { output: unrecognized_arg(arg), status: Err(()) }) } } } #[doc(hidden)] pub struct ParseStructPositional<'a> { // The positional's name pub name: &'static str, // The function to parse the positional. pub slot: &'a mut dyn ParseValueSlot, } impl<'a> ParseStructPositional<'a> { /// Parse a positional argument. /// /// `arg`: the argument supplied by the user. fn parse(&mut self, arg: &str) -> Result<(), EarlyExit> { self.slot.fill_slot("", arg).map_err(|s| { [ "Error parsing positional argument '", self.name, "' with value '", arg, "': ", &s, "\n", ] .concat() .into() }) } } // A type to simplify parsing struct subcommands. // // This indirection is necessary to allow abstracting over `FromArgs` instances with different // generic parameters. #[doc(hidden)] 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>, } impl<'a> ParseStructSubCommand<'a> { fn parse( &mut self, help: bool, cmd_name: &[&str], arg: &str, remaining_args: &[&str], ) -> Result { 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); 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); } } Ok(false) } } // Prepend `help` to a list of arguments. // This is used to pass the `help` argument on to subcommands. fn prepend_help<'a>(args: &[&'a str]) -> Vec<&'a str> { [&["help"], args].concat() } #[doc(hidden)] pub fn print_subcommands<'a>(commands: impl Iterator) -> String { let mut out = String::new(); for cmd in commands { argh_shared::write_description(&mut out, cmd); } out } fn unrecognized_arg(arg: &str) -> String { ["Unrecognized argument: ", arg, "\n"].concat() } // An error string builder to report missing required options and subcommands. #[doc(hidden)] #[derive(Default)] pub struct MissingRequirements { options: Vec<&'static str>, subcommands: Option>, positional_args: Vec<&'static str>, } const NEWLINE_INDENT: &str = "\n "; impl MissingRequirements { // Add a missing required option. #[doc(hidden)] pub fn missing_option(&mut self, name: &'static str) { self.options.push(name) } // Add a missing required subcommand. #[doc(hidden)] pub fn missing_subcommands(&mut self, commands: impl Iterator) { self.subcommands = Some(commands.collect()); } // Add a missing positional argument. #[doc(hidden)] pub fn missing_positional_arg(&mut self, name: &'static str) { self.positional_args.push(name) } // If any missing options or subcommands were provided, returns an error string // describing the missing args. #[doc(hidden)] pub fn err_on_any(&self) -> Result<(), String> { if self.options.is_empty() && self.subcommands.is_none() && self.positional_args.is_empty() { return Ok(()); } let mut output = String::new(); if !self.positional_args.is_empty() { output.push_str("Required positional arguments not provided:"); for arg in &self.positional_args { output.push_str(NEWLINE_INDENT); output.push_str(arg); } } if !self.options.is_empty() { if !self.positional_args.is_empty() { output.push('\n'); } output.push_str("Required options not provided:"); for option in &self.options { output.push_str(NEWLINE_INDENT); output.push_str(option); } } if let Some(missing_subcommands) = &self.subcommands { if !self.options.is_empty() { output.push('\n'); } output.push_str("One of the following subcommands must be present:"); output.push_str(NEWLINE_INDENT); output.push_str("help"); for subcommand in missing_subcommands { output.push_str(NEWLINE_INDENT); output.push_str(subcommand.name); } } output.push('\n'); Err(output) } } #[cfg(test)] mod test { use super::*; #[test] fn test_cmd_extraction() { let expected = "test_cmd"; let path = format!("/tmp/{}", expected); let cmd = cmd(&path, &path); assert_eq!(expected, cmd); } } argh-0.1.9/tests/compiletest.rs000064400000000000000000000001441046102023000146130ustar 00000000000000#[test] fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/**/*.rs"); } argh-0.1.9/tests/lib.rs000064400000000000000000001301661046102023000130410ustar 00000000000000// 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] fn basic_example() { #[derive(FromArgs, PartialEq, Debug)] /// Reach new heights. struct GoUp { /// 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, } let up = GoUp::from_args(&["cmdname"], &["--height", "5"]).expect("failed go_up"); assert_eq!(up, GoUp { jump: false, height: 5, pilot_nickname: None }); } #[test] fn generic_example() { use std::fmt::Display; use std::str::FromStr; #[derive(FromArgs, PartialEq, Debug)] /// Reach new heights. struct GoUp where ::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, } let up = GoUp::::from_args(&["cmdname"], &["--height", "5"]).expect("failed go_up"); assert_eq!(up, GoUp:: { jump: false, height: 5, pilot_nickname: None }); } #[test] fn custom_from_str_example() { #[derive(FromArgs)] /// Goofy thing. struct FiveStruct { /// always five #[argh(option, from_str_fn(always_five))] five: usize, } 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)] /// 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, } 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 }) },); } #[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, 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, 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 struct Cmd { #[argh(switch)] /// a switch with a description /// that is spread across /// a number of /// lines of comments. _s: bool, } assert_help_string::( r###"Usage: test_arg_0 [--s] Short description Options: --s a switch with a description that is spread across a number of lines of comments. --help display usage information "###, ); } #[test] fn explicit_long_value_for_option() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option, long = "foo")] /// bar bar x: u8, } let cmd = Cmd::from_args(&["cmdname"], &["--foo", "5"]).unwrap(); assert_eq!(cmd.x, 5); } /// Test that descriptions can start with an initialism despite /// usually being required to start with a lowercase letter. #[derive(FromArgs)] #[allow(unused)] struct DescriptionStartsWithInitialism { /// URL fooey #[argh(option)] x: u8, } #[test] fn default_number() { #[derive(FromArgs)] /// Short description struct Cmd { #[argh(option, default = "5")] /// fooey x: u8, } let cmd = Cmd::from_args(&["cmdname"], &[]).unwrap(); assert_eq!(cmd.x, 5); } #[test] fn default_function() { const MSG: &str = "hey I just met you"; fn call_me_maybe() -> String { MSG.to_owned() } #[derive(FromArgs)] /// Short description struct Cmd { #[argh(option, default = "call_me_maybe()")] /// fooey msg: String, } let cmd = Cmd::from_args(&["cmdname"], &[]).unwrap(); assert_eq!(cmd.msg, MSG); } #[test] fn missing_option_value() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option)] /// fooey _msg: String, } let e = Cmd::from_args(&["cmdname"], &["--msg"]) .expect_err("Parsing missing option value should fail"); assert_eq!(e.output, "No value provided for option \'--msg\'.\n"); assert!(e.status.is_err()); } fn assert_help_string(help_str: &str) { match T::from_args(&["test_arg_0"], &["--help"]) { Ok(_) => panic!("help was parsed as args"), Err(e) => { assert_eq!(help_str, e.output); e.status.expect("help returned an error"); } } } fn assert_output(args: &[&str], expected: T) { let t = T::from_args(&["cmd"], args).expect("failed to parse"); assert_eq!(t, expected); } fn assert_error(args: &[&str], err_msg: &str) { let e = T::from_args(&["cmd"], args).expect_err("unexpectedly succeeded parsing"); assert_eq!(err_msg, e.output); e.status.expect_err("error had a positive status"); } mod options { use super::*; #[derive(argh::FromArgs, Debug, PartialEq)] /// Woot struct Parsed { #[argh(option, short = 'n')] /// fooey n: usize, } #[test] fn parsed() { assert_output(&["-n", "5"], Parsed { n: 5 }); assert_error::( &["-n", "x"], r###"Error parsing option '-n' with value 'x': invalid digit found in string "###, ); } #[derive(argh::FromArgs, Debug, PartialEq)] /// Woot struct Repeating { #[argh(option, short = 'n')] /// fooey n: Vec, } #[test] fn repeating() { assert_help_string::( r###"Usage: test_arg_0 [-n ] Woot Options: -n, --n fooey --help display usage information "###, ); } #[derive(argh::FromArgs, Debug, PartialEq)] /// Woot struct WithArgName { #[argh(option, arg_name = "name")] /// fooey option_name: Option, } #[test] fn with_arg_name() { assert_help_string::( r###"Usage: test_arg_0 [--option-name ] Woot Options: --option-name fooey --help display usage information "###, ); } } mod positional { use super::*; #[derive(FromArgs, Debug, PartialEq)] /// Woot struct LastRepeating { #[argh(positional)] /// fooey a: u32, #[argh(positional)] /// fooey b: Vec, } #[test] fn repeating() { assert_output(&["5"], LastRepeating { a: 5, b: vec![] }); assert_output(&["5", "foo"], LastRepeating { a: 5, b: vec!["foo".into()] }); assert_output( &["5", "foo", "bar"], LastRepeating { a: 5, b: vec!["foo".into(), "bar".into()] }, ); assert_help_string::( r###"Usage: test_arg_0 [] Woot Positional Arguments: a fooey b fooey Options: --help display usage information "###, ); } #[derive(FromArgs, Debug, PartialEq)] /// Woot struct LastRepeatingGreedy { #[argh(positional)] /// fooey a: u32, #[argh(switch)] /// woo b: bool, #[argh(option)] /// stuff c: Option, #[argh(positional, greedy)] /// fooey d: Vec, } #[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::( r###"Usage: test_arg_0 [--b] [--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 a: u32, #[argh(positional)] /// fooey b: Option, } #[test] fn optional() { assert_output(&["5"], LastOptional { a: 5, b: None }); assert_output(&["5", "6"], LastOptional { a: 5, b: Some("6".into()) }); assert_error::(&["5", "6", "7"], "Unrecognized argument: 7\n"); } #[derive(FromArgs, Debug, PartialEq)] /// Woot struct LastDefaulted { #[argh(positional)] /// fooey a: u32, #[argh(positional, default = "5")] /// fooey b: u32, } #[test] fn defaulted() { assert_output(&["5"], LastDefaulted { a: 5, b: 5 }); assert_output(&["5", "6"], LastDefaulted { a: 5, b: 6 }); assert_error::(&["5", "6", "7"], "Unrecognized argument: 7\n"); } #[derive(FromArgs, Debug, PartialEq)] /// Woot struct LastRequired { #[argh(positional)] /// fooey a: u32, #[argh(positional)] /// fooey b: u32, } #[test] fn required() { assert_output(&["5", "6"], LastRequired { a: 5, b: 6 }); assert_error::( &[], r###"Required positional arguments not provided: a b "###, ); assert_error::( &["5"], r###"Required positional arguments not provided: b "###, ); } #[derive(argh::FromArgs, Debug, PartialEq)] /// Woot struct Parsed { #[argh(positional)] /// fooey n: usize, } #[test] fn parsed() { assert_output(&["5"], Parsed { n: 5 }); assert_error::( &["x"], r###"Error parsing positional argument 'n' with value 'x': invalid digit found in string "###, ); } #[derive(FromArgs, Debug, PartialEq)] /// Woot struct WithOption { #[argh(positional)] /// fooey a: String, #[argh(option)] /// fooey b: String, } #[test] fn mixed_with_option() { assert_output(&["first", "--b", "foo"], WithOption { a: "first".into(), b: "foo".into() }); assert_error::( &[], r###"Required positional arguments not provided: a Required options not provided: --b "###, ); } #[derive(FromArgs, Debug, PartialEq)] /// Woot struct WithSubcommand { #[argh(positional)] /// fooey a: String, #[argh(subcommand)] /// fooey b: Subcommand, #[argh(positional)] /// fooey c: Vec, } #[derive(FromArgs, Debug, PartialEq)] #[argh(subcommand, name = "a")] /// Subcommand of positional::WithSubcommand. struct Subcommand { #[argh(positional)] /// fooey a: String, #[argh(positional)] /// fooey b: Vec, } #[test] fn mixed_with_subcommand() { assert_output( &["first", "a", "a"], WithSubcommand { a: "first".into(), b: Subcommand { a: "a".into(), b: vec![] }, c: vec![], }, ); assert_error::( &["a", "a", "a"], r###"Required positional arguments not provided: a "###, ); assert_output( &["1", "2", "3", "a", "b", "c"], WithSubcommand { a: "1".into(), b: Subcommand { a: "b".into(), b: vec!["c".into()] }, c: vec!["2".into(), "3".into()], }, ); } } /// Tests derived from /// https://fuchsia.dev/fuchsia-src/development/api/cli and /// https://fuchsia.dev/fuchsia-src/development/api/cli_help mod fuchsia_commandline_tools_rubric { use super::*; /// Tests for the three required command line argument types: /// - exact text /// - arguments /// - options (i.e. switches and keys) #[test] fn three_command_line_argument_types() { // TODO(cramertj) add support for exact text and positional arguments } /// A piece of exact text may be required or optional #[test] fn exact_text_required_and_optional() { // TODO(cramertj) add support for exact text } /// Arguments are like function parameters or slots for data. /// The order often matters. #[test] fn arguments_ordered() { // TODO(cramertj) add support for ordered positional arguments } /// If a single argument is repeated, order may not matter, e.g. `...` #[test] fn arguments_unordered() { // TODO(cramertj) add support for repeated positional arguments } // Short argument names must use one dash and a single letter. // TODO(cramertj): this should be a compile-fail test // Short argument names are optional, but all choices are required to have a `--` option. // TODO(cramertj): this should be a compile-fail test // Numeric options, such as `-1` and `-2`, are not allowed. // TODO(cramertj): this should be a compile-fail test #[derive(FromArgs)] /// One switch. struct OneSwitch { #[argh(switch, short = 's')] /// just a switch switchy: bool, } /// The presence of a switch means the feature it represents is "on", /// while its absence means that it is "off". #[test] fn switch_on_when_present() { let on = OneSwitch::from_args(&["cmdname"], &["-s"]).expect("parsing on"); assert!(on.switchy); let off = OneSwitch::from_args(&["cmdname"], &[]).expect("parsing off"); assert!(!off.switchy); } #[derive(FromArgs, Debug)] /// Two Switches struct TwoSwitches { #[argh(switch, short = 'a')] /// a _a: bool, #[argh(switch, short = 'b')] /// b _b: bool, } /// Running switches together is not allowed #[test] fn switches_cannot_run_together() { TwoSwitches::from_args(&["cmdname"], &["-a", "-b"]) .expect("parsing separate should succeed"); TwoSwitches::from_args(&["cmdname"], &["-ab"]).expect_err("parsing together should fail"); } #[derive(FromArgs, Debug)] /// One keyed option struct OneOption { #[argh(option)] /// some description _foo: String, } /// Do not use an equals punctuation or similar to separate the key and value. #[test] fn keyed_no_equals() { OneOption::from_args(&["cmdname"], &["--foo", "bar"]) .expect("Parsing option value as separate arg should succeed"); let e = OneOption::from_args(&["cmdname"], &["--foo=bar"]) .expect_err("Parsing option value using `=` should fail"); assert_eq!(e.output, "Unrecognized argument: --foo=bar\n"); assert!(e.status.is_err()); } // Two dashes on their own indicates the end of options. // Subsequent values are given to the tool as-is. // // It's unclear exactly what "are given to the tool as-is" in means in this // context, so we provide a few options for handling `--`, with it being // an error by default. // // TODO(cramertj) implement some behavior for `--` /// Double-dash is treated as an error by default. #[test] fn double_dash_default_error() {} /// Double-dash can be ignored for later manual parsing. #[test] fn double_dash_ignore() {} /// Double-dash should be treated as the end of flags and optional arguments, /// and the remainder of the values should be treated purely as positional arguments, /// even when their syntax matches that of options. e.g. `foo -- -e` should be parsed /// as passing a single positional argument with the value `-e`. #[test] fn double_dash_positional() { #[derive(FromArgs, Debug, PartialEq)] /// Positional arguments list struct StringList { #[argh(positional)] /// a list of strings strs: Vec, #[argh(switch)] /// some flag flag: bool, } assert_output( &["--", "a", "-b", "--flag"], StringList { strs: vec!["a".into(), "-b".into(), "--flag".into()], flag: false }, ); assert_output( &["--flag", "--", "-a", "b"], StringList { strs: vec!["-a".into(), "b".into()], flag: true }, ); assert_output(&["--", "--help"], StringList { strs: vec!["--help".into()], flag: false }); assert_output( &["--", "-a", "--help"], StringList { strs: vec!["-a".into(), "--help".into()], flag: false }, ); } /// Double-dash can be parsed into an optional field using a provided /// `fn(&[&str]) -> Result`. #[test] fn double_dash_custom() {} /// Repeating switches may be used to apply more emphasis. /// A common example is increasing verbosity by passing more `-v` switches. #[test] fn switches_repeating() { #[derive(FromArgs, Debug)] /// A type for testing repeating `-v` struct CountVerbose { #[argh(switch, short = 'v')] /// increase the verbosity of the command. verbose: i128, } let cv = CountVerbose::from_args(&["cmdname"], &["-v", "-v", "-v"]) .expect("Parsing verbose flags should succeed"); assert_eq!(cv.verbose, 3); } // When a tool has many subcommands, it should also have a help subcommand // that displays help about the subcommands, e.g. `fx help build`. // // Elsewhere in the docs, it says the syntax `--help` is required, so we // interpret that to mean: // // - `help` should always be accepted as a "keyword" in place of the first // positional argument for both the main command and subcommands. // // - If followed by the name of a subcommand it should forward to the // `--help` of said subcommand, otherwise it will fall back to the // help of the righmost command / subcommand. // // - `--help` will always consider itself the only meaningful argument to // the rightmost command / subcommand, and any following arguments will // be treated as an error. #[derive(FromArgs, Debug)] /// A type for testing `--help`/`help` struct HelpTopLevel { #[argh(subcommand)] _sub: HelpFirstSub, } #[derive(FromArgs, Debug)] #[argh(subcommand, name = "first")] /// First subcommmand for testing `help`. struct HelpFirstSub { #[argh(subcommand)] _sub: HelpSecondSub, } #[derive(FromArgs, Debug)] #[argh(subcommand, name = "second")] /// Second subcommand for testing `help`. struct HelpSecondSub {} fn expect_help(args: &[&str], expected_help_string: &str) { let e = HelpTopLevel::from_args(&["cmdname"], args).expect_err("should exit early"); assert_eq!(expected_help_string, e.output); e.status.expect("help returned an error"); } const MAIN_HELP_STRING: &str = r###"Usage: cmdname [] A type for testing `--help`/`help` Options: --help display usage information Commands: first First subcommmand for testing `help`. "###; const FIRST_HELP_STRING: &str = r###"Usage: cmdname first [] First subcommmand for testing `help`. Options: --help display usage information Commands: second Second subcommand for testing `help`. "###; const SECOND_HELP_STRING: &str = r###"Usage: cmdname first second Second subcommand for testing `help`. Options: --help display usage information "###; #[test] fn help_keyword_main() { expect_help(&["help"], MAIN_HELP_STRING) } #[test] fn help_keyword_with_following_subcommand() { expect_help(&["help", "first"], FIRST_HELP_STRING); } #[test] fn help_keyword_between_subcommands() { expect_help(&["first", "help", "second"], SECOND_HELP_STRING); } #[test] fn help_keyword_with_two_trailing_subcommands() { expect_help(&["help", "first", "second"], SECOND_HELP_STRING); } #[test] fn help_flag_main() { expect_help(&["--help"], MAIN_HELP_STRING); } #[test] fn help_flag_subcommand() { expect_help(&["first", "--help"], FIRST_HELP_STRING); } #[test] fn help_flag_trailing_arguments_are_an_error() { let e = OneOption::from_args(&["cmdname"], &["--help", "--foo", "bar"]) .expect_err("should exit early"); assert_eq!("Trailing arguments are not allowed after `help`.", e.output); e.status.expect_err("should be an error"); } #[derive(FromArgs, PartialEq, Debug)] #[argh( description = "Destroy the contents of .", 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, PartialEq, Debug)] #[argh(subcommand)] enum HelpExampleSubCommands { BlowUp(BlowUp), Grind(GrindCommand), #[argh(dynamic)] Plugin(HelpExamplePlugin), } #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "blow-up")] /// explosively separate struct BlowUp { /// blow up bombs safely #[argh(switch)] safely: bool, } #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "grind", description = "make smaller by many small cuts")] struct GrindCommand { /// wear a visor while grinding #[argh(switch)] 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, argh::EarlyExit>> { Some(Err(argh::EarlyExit::from("Test should not redact".to_owned()))) } fn try_from_args( command_name: &[&str], args: &[&str], ) -> Option> { 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( &["program-name"], &["-f", "--scribble", "fooey", "blow-up", "--safely"], ) .unwrap(); assert_eq!( help_example, HelpExample { force: true, scribble: "fooey".to_owned(), really_really_really_long_name_for_pat: false, verbose: false, command: HelpExampleSubCommands::BlowUp(BlowUp { safely: true }), }, ); } #[test] fn example_errors_on_missing_required_option_and_missing_required_subcommand() { let exit = HelpExample::from_args(&["program-name"], &[]).unwrap_err(); exit.status.unwrap_err(); assert_eq!( exit.output, concat!( "Required options not provided:\n", " --scribble\n", "One of the following subcommands must be present:\n", " help\n", " blow-up\n", " grind\n", " plugin\n", ), ); } #[test] fn help_example() { assert_help_string::( r###"Usage: test_arg_0 [-f] [--really-really-really-long-name-for-pat] -s [-v] [] Destroy the contents of . Options: -f, --force force, ignore minor errors. This description is so long that it wraps to the next line. --really-really-really-long-name-for-pat documentation -s, --scribble write repeatedly -v, --verbose say more. Defaults to $BLAST_VERBOSE. --help display usage information Commands: blow-up explosively separate grind make smaller by many small cuts plugin Example dynamic command Examples: Scribble 'abc' and then run |grind|. $ test_arg_0 -s 'abc' grind old.txt taxes.cp Notes: Use `test_arg_0 help ` for details on [] for a subcommand. Error codes: 2 The blade is too dull. 3 Out of fuel. "###, ); } #[allow(dead_code)] #[derive(argh::FromArgs)] /// Destroy the contents of . struct WithArgName { #[argh(positional, arg_name = "name")] username: String, } #[test] fn with_arg_name() { assert_help_string::( r###"Usage: test_arg_0 Destroy the contents of . Positional Arguments: name Options: --help display usage information "###, ); } } #[test] fn redact_arg_values_no_args() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option)] /// a msg param _msg: Option, } let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap(); assert_eq!(actual, &["program-name"]); } #[test] fn redact_arg_values_optional_arg() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option)] /// a msg param _msg: Option, } let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "hello"]).unwrap(); assert_eq!(actual, &["program-name", "--msg"]); } #[test] fn redact_arg_values_optional_arg_short() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option, short = 'm')] /// a msg param _msg: Option, } let actual = Cmd::redact_arg_values(&["program-name"], &["-m", "hello"]).unwrap(); assert_eq!(actual, &["program-name", "-m"]); } #[test] fn redact_arg_values_optional_arg_long() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option, long = "my-msg")] /// a msg param _msg: Option, } let actual = Cmd::redact_arg_values(&["program-name"], &["--my-msg", "hello"]).unwrap(); assert_eq!(actual, &["program-name", "--my-msg"]); } #[test] fn redact_arg_values_two_option_args() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option)] /// a msg param _msg: String, #[argh(option)] /// a delivery param _delivery: String, } let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "hello", "--delivery", "next day"]) .unwrap(); assert_eq!(actual, &["program-name", "--msg", "--delivery"]); } #[test] fn redact_arg_values_option_one_optional_args() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option)] /// a msg param _msg: String, #[argh(option)] /// a delivery param _delivery: Option, } let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "hello", "--delivery", "next day"]) .unwrap(); assert_eq!(actual, &["program-name", "--msg", "--delivery"]); let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "hello"]).unwrap(); assert_eq!(actual, &["program-name", "--msg"]); } #[test] fn redact_arg_values_option_repeating() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(option)] /// fooey _msg: Vec, } let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap(); assert_eq!(actual, &["program-name"]); let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "abc", "--msg", "xyz"]).unwrap(); assert_eq!(actual, &["program-name", "--msg", "--msg"]); } #[test] fn redact_arg_values_switch() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(switch, short = 'f')] /// speed of cmd _faster: bool, } let actual = Cmd::redact_arg_values(&["program-name"], &["--faster"]).unwrap(); assert_eq!(actual, &["program-name", "--faster"]); let actual = Cmd::redact_arg_values(&["program-name"], &["-f"]).unwrap(); assert_eq!(actual, &["program-name", "-f"]); } #[test] fn redact_arg_values_positional() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[allow(unused)] #[argh(positional)] /// speed of cmd speed: u8, } let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap(); assert_eq!(actual, &["program-name", "speed"]); } #[test] fn redact_arg_values_positional_arg_name() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional, arg_name = "speed")] /// speed of cmd _speed: u8, } let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap(); assert_eq!(actual, &["program-name", "speed"]); } #[test] fn redact_arg_values_positional_repeating() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional, arg_name = "speed")] /// speed of cmd _speed: Vec, } let actual = Cmd::redact_arg_values(&["program-name"], &["5", "6"]).unwrap(); assert_eq!(actual, &["program-name", "speed", "speed"]); } #[test] fn redact_arg_values_positional_err() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional, arg_name = "speed")] /// speed of cmd _speed: u8, } let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap_err(); assert_eq!( actual, argh::EarlyExit { output: "Required positional arguments not provided:\n speed\n".into(), status: Err(()), } ); } #[test] fn redact_arg_values_two_positional() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional, arg_name = "speed")] /// speed of cmd _speed: u8, #[argh(positional, arg_name = "direction")] /// direction _direction: String, } let actual = Cmd::redact_arg_values(&["program-name"], &["5", "north"]).unwrap(); assert_eq!(actual, &["program-name", "speed", "direction"]); } #[test] fn redact_arg_values_positional_option() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional, arg_name = "speed")] /// speed of cmd _speed: u8, #[argh(option)] /// direction _direction: String, } let actual = Cmd::redact_arg_values(&["program-name"], &["5", "--direction", "north"]).unwrap(); assert_eq!(actual, &["program-name", "speed", "--direction"]); } #[test] fn redact_arg_values_positional_optional_option() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional, arg_name = "speed")] /// speed of cmd _speed: u8, #[argh(option)] /// direction _direction: Option, } let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap(); assert_eq!(actual, &["program-name", "speed"]); } #[test] fn redact_arg_values_subcommand() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional, arg_name = "speed")] /// speed of cmd _speed: u8, #[argh(subcommand)] /// means of transportation _means: MeansSubcommand, } #[derive(FromArgs, Debug)] /// Short description #[argh(subcommand)] enum MeansSubcommand { Walking(WalkingSubcommand), Biking(BikingSubcommand), Driving(DrivingSubcommand), } #[derive(FromArgs, Debug)] #[argh(subcommand, name = "walking")] /// Short description struct WalkingSubcommand { #[argh(option)] /// a song to listen to _music: String, } #[derive(FromArgs, Debug)] #[argh(subcommand, name = "biking")] /// Short description struct BikingSubcommand {} #[derive(FromArgs, Debug)] #[argh(subcommand, name = "driving")] /// short description struct DrivingSubcommand {} let actual = Cmd::redact_arg_values(&["program-name"], &["5", "walking", "--music", "Bach"]).unwrap(); assert_eq!(actual, &["program-name", "speed", "walking", "--music"]); } #[test] fn redact_arg_values_subcommand_with_space_in_name() { #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional, arg_name = "speed")] /// speed of cmd _speed: u8, #[argh(subcommand)] /// means of transportation _means: MeansSubcommand, } #[derive(FromArgs, Debug)] /// Short description #[argh(subcommand)] enum MeansSubcommand { Walking(WalkingSubcommand), Biking(BikingSubcommand), } #[derive(FromArgs, Debug)] #[argh(subcommand, name = "has space")] /// Short description struct WalkingSubcommand { #[argh(option)] /// a song to listen to _music: String, } #[derive(FromArgs, Debug)] #[argh(subcommand, name = "biking")] /// Short description struct BikingSubcommand {} let actual = Cmd::redact_arg_values(&["program-name"], &["5", "has space", "--music", "Bach"]).unwrap(); assert_eq!(actual, &["program-name", "speed", "has space", "--music"]); } #[test] fn redact_arg_values_produces_help() { #[derive(argh::FromArgs, Debug, PartialEq)] /// Woot struct Repeating { #[argh(option, short = 'n')] /// fooey n: Vec, } assert_eq!( Repeating::redact_arg_values(&["program-name"], &["--help"]), Err(argh::EarlyExit { output: r###"Usage: program-name [-n ] Woot Options: -n, --n fooey --help display usage information "### .to_owned(), status: Ok(()), }), ); } #[test] fn redact_arg_values_produces_errors_with_bad_arguments() { #[derive(argh::FromArgs, Debug, PartialEq)] /// Woot struct Cmd { #[argh(option, short = 'n')] /// fooey n: String, } assert_eq!( Cmd::redact_arg_values(&["program-name"], &["--n"]), Err(argh::EarlyExit { output: "No value provided for option '--n'.\n".to_owned(), status: Err(()), }), ); } #[test] fn redact_arg_values_does_not_warn_if_used() { #[forbid(unused)] #[derive(FromArgs, Debug)] /// Short description struct Cmd { #[argh(positional)] /// speed of cmd speed: u8, } let cmd = Cmd::from_args(&["program-name"], &["5"]).unwrap(); assert_eq!(cmd.speed, 5); 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"); } argh-0.1.9/tests/ui/conflicting-tails/positional-and-greedy.rs000064400000000000000000000003321046102023000225060ustar 00000000000000/// Command #[derive(argh::FromArgs)] struct Cmd { #[argh(positional)] /// positional positional: Vec, #[argh(positional, greedy)] /// remainder remainder: Vec, } fn main() {} argh-0.1.9/tests/ui/conflicting-tails/positional-and-greedy.stderr000064400000000000000000000005261046102023000233720ustar 00000000000000error: 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)] | ^ argh-0.1.9/tests/ui/duplicate-name/duplicate-long-name.rs000064400000000000000000000004761046102023000214250ustar 00000000000000/// 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() {} argh-0.1.9/tests/ui/duplicate-name/duplicate-long-name.stderr000064400000000000000000000014671046102023000223050ustar 00000000000000error: 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, | |_____________^ argh-0.1.9/tests/ui/duplicate-name/duplicate-short-name.rs000064400000000000000000000004721046102023000216210ustar 00000000000000/// 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() {} argh-0.1.9/tests/ui/duplicate-name/duplicate-short-name.stderr000064400000000000000000000014631046102023000225010ustar 00000000000000error: 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, | |_____________^