aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Walbran <qwandor@google.com>2022-01-21 13:46:38 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-01-21 13:46:38 +0000
commit08526c8a78b4e7b0374284635f8a9009c60c0f34 (patch)
tree83520f0ca59756de4617534146595ab55027b706
parent35a97e06cbeb8f6a018ddeea41c8a00e9b019a39 (diff)
parent69a9df1869729a82f3a8f7ebf2da4b58cf06cc4f (diff)
downloadargh-08526c8a78b4e7b0374284635f8a9009c60c0f34.tar.gz
Initial import. am: 0f3c1ab533 am: 68d187f38f am: 2b561b8c7a am: 69a9df1869
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/argh/+/1954545 Change-Id: I08219a26257ff41591233aad58589b26b2c1d906
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp61
-rw-r--r--Cargo.toml26
-rw-r--r--Cargo.toml.orig14
-rw-r--r--LICENSE27
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_BSD_LIKE0
-rw-r--r--OWNERS1
-rw-r--r--README.md177
-rw-r--r--cargo2android.json9
-rw-r--r--src/lib.rs993
-rw-r--r--tests/lib.rs1308
12 files changed, 2641 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..23c489b
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "f1f85d2d89cbe09314dc1b59e581b8a43531cf3e"
+ },
+ "path_in_vcs": "argh"
+} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..3b27556
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,61 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+
+
+rust_test {
+ name: "argh_test_src_lib",
+ host_supported: true,
+ crate_name: "argh",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.7",
+ srcs: ["src/lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2018",
+ rustlibs: [
+ "libargh_shared",
+ ],
+ proc_macros: ["libargh_derive"],
+}
+
+rust_test {
+ name: "argh_test_tests_lib",
+ host_supported: true,
+ crate_name: "lib",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.7",
+ srcs: ["tests/lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2018",
+ rustlibs: [
+ "libargh",
+ "libargh_shared",
+ ],
+ proc_macros: ["libargh_derive"],
+}
+
+rust_library {
+ name: "libargh",
+ host_supported: true,
+ crate_name: "argh",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.7",
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ rustlibs: [
+ "libargh_shared",
+ ],
+ proc_macros: ["libargh_derive"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..b20c0fc
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,26 @@
+# 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.7"
+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"]
+license = "BSD-3-Clause"
+repository = "https://github.com/google/argh"
+[dependencies.argh_derive]
+version = "0.1.7"
+
+[dependencies.argh_shared]
+version = "0.1.7"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..ec92149
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,14 @@
+[package]
+name = "argh"
+version = "0.1.7"
+authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"]
+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.7", path = "../argh_shared" }
+argh_derive = { version = "0.1.7", path = "../argh_derive" }
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..87f152c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright 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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..a9d08d0
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "argh"
+description: "Derive-based argument parser optimized for code size"
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/argh"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/argh/argh-0.1.7.crate"
+ }
+ version: "0.1.7"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2022
+ month: 1
+ day: 13
+ }
+}
diff --git a/MODULE_LICENSE_BSD_LIKE b/MODULE_LICENSE_BSD_LIKE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD_LIKE
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4e949e4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,177 @@
+# 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<String>,
+}
+
+fn main() {
+ let up: GoUp = argh::from_env();
+}
+```
+
+`./some_bin --help` will then output the following:
+
+```
+Usage: cmdname [-j] --height <height> [--pilot-nickname <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 = "<your_code_here>")]` 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<String>,
+
+ /// 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<T, String>` 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<usize, String> {
+ 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.
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..6e516e0
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "apex-available": [
+ "//apex_available:platform",
+ "com.android.virt"
+ ],
+ "device": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..984d927
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,993 @@
+// 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<String>,
+//! }
+//!
+//! fn main() {
+//! let up: GoUp = argh::from_env();
+//! }
+//! ```
+//!
+//! `./some_bin --help` will then output the following:
+//!
+//! ```bash
+//! Usage: cmdname [-j] --height <height> [--pilot-nickname <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 = "<your_code_here>")]` 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<String>,
+//!
+//! /// 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<T, String>` 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<usize, String> {
+//! 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,
+//! }
+//! ```
+
+#![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<String>,
+ /// }
+ ///
+ /// /// add students to a class.
+ /// #[derive(Debug, PartialEq, FromArgs)]
+ /// #[argh(subcommand, name = "add")]
+ /// struct AddCmd {
+ /// /// the name of the class's teacher.
+ /// #[argh(option)]
+ /// teacher_name: String,
+ ///
+ /// /// the name of the class.
+ /// #[argh(positional)]
+ /// class_name: String,
+ /// }
+ ///
+ /// let args = ClassroomCmd::from_args(
+ /// &["classroom"],
+ /// &["list", "--teacher-name", "Smith"],
+ /// ).unwrap();
+ /// assert_eq!(
+ /// args,
+ /// ClassroomCmd {
+ /// subcommands: Subcommands::List(ListCmd {
+ /// teacher_name: Some("Smith".to_string()),
+ /// })
+ /// },
+ /// );
+ ///
+ /// // Help returns an error, but internally returns an `Ok` status.
+ /// let early_exit = ClassroomCmd::from_args(
+ /// &["classroom"],
+ /// &["help"],
+ /// ).unwrap_err();
+ /// assert_eq!(
+ /// early_exit,
+ /// argh::EarlyExit {
+ /// output: r#"Usage: classroom <command> [<args>]
+ ///
+ /// Command to manage a classroom.
+ ///
+ /// Options:
+ /// --help display usage information
+ ///
+ /// Commands:
+ /// list list all the classes.
+ /// add add students to a class.
+ /// "#.to_string(),
+ /// status: Ok(()),
+ /// },
+ /// );
+ ///
+ /// // Help works with subcommands.
+ /// let early_exit = ClassroomCmd::from_args(
+ /// &["classroom"],
+ /// &["list", "help"],
+ /// ).unwrap_err();
+ /// assert_eq!(
+ /// early_exit,
+ /// argh::EarlyExit {
+ /// output: r#"Usage: classroom list [--teacher-name <teacher-name>]
+ ///
+ /// list all the classes.
+ ///
+ /// Options:
+ /// --teacher-name list classes for only this teacher.
+ /// --help display usage information
+ /// "#.to_string(),
+ /// status: Ok(()),
+ /// },
+ /// );
+ ///
+ /// // Incorrect arguments will error out.
+ /// let err = ClassroomCmd::from_args(
+ /// &["classroom"],
+ /// &["lisp"],
+ /// ).unwrap_err();
+ /// assert_eq!(
+ /// err,
+ /// argh::EarlyExit {
+ /// output: "Unrecognized argument: lisp\n".to_string(),
+ /// status: Err(()),
+ /// },
+ /// );
+ /// ```
+ fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit>;
+
+ /// Get a String with just the argument names, e.g., options, flags, subcommands, etc, but
+ /// without the values of the options and arguments. This can be useful as a means to capture
+ /// anonymous usage statistics without revealing the content entered by the end user.
+ ///
+ /// The first argument `command_name` is the identifier for the current command. 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<String>,
+ /// }
+ ///
+ /// /// 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<String>,
+ /// }
+ ///
+ /// 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> [<args>]
+ ///
+ /// Command to manage a classroom.
+ ///
+ /// Options:
+ /// --help display usage information
+ ///
+ /// Commands:
+ /// list list all the classes.
+ /// add add students to a class.
+ /// "#.to_string(),
+ /// status: Ok(()),
+ /// }),
+ /// );
+ /// ```
+ fn redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result<Vec<String>, EarlyExit> {
+ Ok(vec!["<<REDACTED>>".into()])
+ }
+}
+
+/// 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];
+}
+
+/// A `FromArgs` implementation that represents a single subcommand.
+pub trait SubCommand: FromArgs {
+ /// Information about the subcommand.
+ const COMMAND: &'static CommandInfo;
+}
+
+impl<T: SubCommand> SubCommands for T {
+ const COMMANDS: &'static [&'static CommandInfo] = &[T::COMMAND];
+}
+
+/// 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<String> 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 String, path: &'a String) -> &'a str {
+ std::path::Path::new(path).file_name().map(|s| s.to_str()).flatten().unwrap_or(default.as_str())
+}
+
+/// 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: TopLevelCommand>() -> T {
+ let strings: Vec<String> = 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!("{}", early_exit.output);
+ 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: TopLevelCommand>() -> T {
+ let strings: Vec<String> = 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!("{}", 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<Error: Display>`. 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<Self, String>;
+}
+
+impl<T> FromArgValue for T
+where
+ T: FromStr,
+ T::Err: std::fmt::Display,
+{
+ fn from_arg_value(value: &str) -> Result<Self, String> {
+ 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<T: Flag> ParseFlag for T {
+ fn set_flag(&mut self, _arg: &str) {
+ <T as Flag>::set_flag(self);
+ }
+}
+
+#[doc(hidden)]
+pub struct RedactFlag {
+ pub slot: Option<String>,
+}
+
+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<Slot, T> {
+ // 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<T, String>,
+}
+
+// `ParseValueSlotTy<Option<T>, T>` is used as the slot for all non-repeating
+// arguments, both optional and required.
+impl<T> ParseValueSlot for ParseValueSlotTy<Option<T>, 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<Vec<T>, T>` is used as the slot for repeating arguments.
+impl<T> ParseValueSlot for ParseValueSlotTy<Vec<T>, 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<ParseStructSubCommand<'_>>,
+ 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.get(0) {
+ 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;
+ }
+ }
+
+ 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
+ .get(0)
+ .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,
+}
+
+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> {
+ 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 {
+ *index += 1;
+ }
+
+ Ok(())
+ } 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],
+
+ // The function to parse the subcommand arguments.
+ 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<bool, EarlyExit> {
+ for subcommand in self.subcommands {
+ 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);
+ }
+ }
+
+ return 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(commands: &[&CommandInfo]) -> 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<&'static [&'static CommandInfo]>,
+ 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: &'static [&'static CommandInfo]) {
+ self.subcommands = Some(commands);
+ }
+
+ // 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_str("\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_str("\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);
+ }
+}
diff --git a/tests/lib.rs b/tests/lib.rs
new file mode 100644
index 0000000..fe8c858
--- /dev/null
+++ b/tests/lib.rs
@@ -0,0 +1,1308 @@
+#![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.
+
+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<String>,
+ }
+
+ 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<usize, String> {
+ 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 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::<Cmd>(
+ 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_string()
+ }
+
+ #[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<T: FromArgs>(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<T: FromArgs + Debug + PartialEq>(args: &[&str], expected: T) {
+ let t = T::from_args(&["cmd"], args).expect("failed to parse");
+ assert_eq!(t, expected);
+}
+
+fn assert_error<T: FromArgs + Debug>(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::<Parsed>(
+ &["-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<String>,
+ }
+
+ #[test]
+ fn repeating() {
+ assert_help_string::<Repeating>(
+ r###"Usage: test_arg_0 [-n <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<String>,
+ }
+
+ #[test]
+ fn with_arg_name() {
+ assert_help_string::<WithArgName>(
+ r###"Usage: test_arg_0 [--option-name <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<String>,
+ }
+
+ #[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::<LastRepeating>(
+ r###"Usage: test_arg_0 <a> [<b...>]
+
+Woot
+
+Positional Arguments:
+ a fooey
+ b fooey
+
+Options:
+ --help display usage information
+"###,
+ );
+ }
+
+ #[derive(FromArgs, Debug, PartialEq)]
+ /// Woot
+ struct LastOptional {
+ #[argh(positional)]
+ /// fooey
+ a: u32,
+ #[argh(positional)]
+ /// fooey
+ b: Option<String>,
+ }
+
+ #[test]
+ fn optional() {
+ assert_output(&["5"], LastOptional { a: 5, b: None });
+ assert_output(&["5", "6"], LastOptional { a: 5, b: Some("6".into()) });
+ assert_error::<LastOptional>(&["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::<LastDefaulted>(&["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::<LastRequired>(
+ &[],
+ r###"Required positional arguments not provided:
+ a
+ b
+"###,
+ );
+ assert_error::<LastRequired>(
+ &["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::<Parsed>(
+ &["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::<WithOption>(
+ &[],
+ 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<String>,
+ }
+
+ #[derive(FromArgs, Debug, PartialEq)]
+ #[argh(subcommand, name = "a")]
+ /// Subcommand of positional::WithSubcommand.
+ struct Subcommand {
+ #[argh(positional)]
+ /// fooey
+ a: String,
+ #[argh(positional)]
+ /// fooey
+ b: Vec<String>,
+ }
+
+ #[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::<WithSubcommand>(
+ &["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. `<files>...`
+ #[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<String>,
+
+ #[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<T, EarlyExit>`.
+ #[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 <command> [<args>]
+
+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 <command> [<args>]
+
+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 <file>.",
+ example = "Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp",
+ note = "Use `{command_name} help <command>` for details on [<args>] 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 <scribble> 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),
+ }
+
+ #[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,
+ }
+
+ #[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_string(),
+ 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",
+ ),
+ );
+ }
+
+ #[test]
+ fn help_example() {
+ assert_help_string::<HelpExample>(
+ r###"Usage: test_arg_0 [-f] [--really-really-really-long-name-for-pat] -s <scribble> [-v] <command> [<args>]
+
+Destroy the contents of <file>.
+
+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 <scribble> 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
+
+Examples:
+ Scribble 'abc' and then run |grind|.
+ $ test_arg_0 -s 'abc' grind old.txt taxes.cp
+
+Notes:
+ Use `test_arg_0 help <command>` for details on [<args>] 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 <file>.
+ struct WithArgName {
+ #[argh(positional, arg_name = "name")]
+ username: String,
+ }
+
+ #[test]
+ fn with_arg_name() {
+ assert_help_string::<WithArgName>(
+ r###"Usage: test_arg_0 <name>
+
+Destroy the contents of <file>.
+
+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<String>,
+ }
+
+ 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<String>,
+ }
+
+ 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<String>,
+ }
+
+ 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<String>,
+ }
+
+ 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<String>,
+ }
+
+ 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<String>,
+ }
+
+ 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<u8>,
+ }
+
+ 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<String>,
+ }
+
+ 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<String>,
+ }
+
+ assert_eq!(
+ Repeating::redact_arg_values(&["program-name"], &["--help"]),
+ Err(argh::EarlyExit {
+ output: r###"Usage: program-name [-n <n...>]
+
+Woot
+
+Options:
+ -n, --n fooey
+ --help display usage information
+"###
+ .to_string(),
+ 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_string(),
+ 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"]);
+}