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
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"]);
+}