aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs993
1 files changed, 993 insertions, 0 deletions
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);
+ }
+}