argh/
lib.rs

1// Copyright (c) 2020 Google LLC All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//! Derive-based argument parsing optimized for code size and conformance
6//! to the Fuchsia commandline tools specification
7//!
8//! The public API of this library consists primarily of the `FromArgs`
9//! derive and the `from_env` function, which can be used to produce
10//! a top-level `FromArgs` type from the current program's commandline
11//! arguments.
12//!
13//! ## Basic Example
14//!
15//! ```rust,no_run
16//! use argh::FromArgs;
17//!
18//! #[derive(FromArgs)]
19//! /// Reach new heights.
20//! struct GoUp {
21//!     /// whether or not to jump
22//!     #[argh(switch, short = 'j')]
23//!     jump: bool,
24//!
25//!     /// how high to go
26//!     #[argh(option)]
27//!     height: usize,
28//!
29//!     /// an optional nickname for the pilot
30//!     #[argh(option)]
31//!     pilot_nickname: Option<String>,
32//! }
33//!
34//! let up: GoUp = argh::from_env();
35//! ```
36//!
37//! `./some_bin --help` will then output the following:
38//!
39//! ```bash
40//! Usage: cmdname [-j] --height <height> [--pilot-nickname <pilot-nickname>]
41//!
42//! Reach new heights.
43//!
44//! Options:
45//!   -j, --jump        whether or not to jump
46//!   --height          how high to go
47//!   --pilot-nickname  an optional nickname for the pilot
48//!   --help            display usage information
49//! ```
50//!
51//! The resulting program can then be used in any of these ways:
52//! - `./some_bin --height 5`
53//! - `./some_bin -j --height 5`
54//! - `./some_bin --jump --height 5 --pilot-nickname Wes`
55//!
56//! Switches, like `jump`, are optional and will be set to true if provided.
57//!
58//! Options, like `height` and `pilot_nickname`, can be either required,
59//! optional, or repeating, depending on whether they are contained in an
60//! `Option` or a `Vec`. Default values can be provided using the
61//! `#[argh(default = "<your_code_here>")]` attribute, and in this case an
62//! option is treated as optional.
63//!
64//! ```rust
65//! use argh::FromArgs;
66//!
67//! fn default_height() -> usize {
68//!     5
69//! }
70//!
71//! #[derive(FromArgs)]
72//! /// Reach new heights.
73//! struct GoUp {
74//!     /// an optional nickname for the pilot
75//!     #[argh(option)]
76//!     pilot_nickname: Option<String>,
77//!
78//!     /// an optional height
79//!     #[argh(option, default = "default_height()")]
80//!     height: usize,
81//!
82//!     /// an optional direction which is "up" by default
83//!     #[argh(option, default = "String::from(\"only up\")")]
84//!     direction: String,
85//! }
86//!
87//! fn main() {
88//!     let up: GoUp = argh::from_env();
89//! }
90//! ```
91//!
92//! Custom option types can be deserialized so long as they implement the
93//! `FromArgValue` trait (automatically implemented for all `FromStr` types).
94//! If more customized parsing is required, you can supply a custom
95//! `fn(&str) -> Result<T, String>` using the `from_str_fn` attribute:
96//!
97//! ```
98//! # use argh::FromArgs;
99//!
100//! #[derive(FromArgs)]
101//! /// Goofy thing.
102//! struct FiveStruct {
103//!     /// always five
104//!     #[argh(option, from_str_fn(always_five))]
105//!     five: usize,
106//! }
107//!
108//! fn always_five(_value: &str) -> Result<usize, String> {
109//!     Ok(5)
110//! }
111//! ```
112//!
113//! Positional arguments can be declared using `#[argh(positional)]`.
114//! These arguments will be parsed in order of their declaration in
115//! the structure:
116//!
117//! ```rust
118//! use argh::FromArgs;
119//! #[derive(FromArgs, PartialEq, Debug)]
120//! /// A command with positional arguments.
121//! struct WithPositional {
122//!     #[argh(positional)]
123//!     first: String,
124//! }
125//! ```
126//!
127//! The last positional argument may include a default, or be wrapped in
128//! `Option` or `Vec` to indicate an optional or repeating positional argument.
129//!
130//! If your final positional argument has the `greedy` option on it, it will consume
131//! any arguments after it as if a `--` were placed before the first argument to
132//! match the greedy positional:
133//!
134//! ```rust
135//! use argh::FromArgs;
136//! #[derive(FromArgs, PartialEq, Debug)]
137//! /// A command with a greedy positional argument at the end.
138//! struct WithGreedyPositional {
139//!     /// some stuff
140//!     #[argh(option)]
141//!     stuff: Option<String>,
142//!     #[argh(positional, greedy)]
143//!     all_the_rest: Vec<String>,
144//! }
145//! ```
146//!
147//! Now if you pass `--stuff Something` after a positional argument, it will
148//! be consumed by `all_the_rest` instead of setting the `stuff` field.
149//!
150//! Note that `all_the_rest` won't be listed as a positional argument in the
151//! long text part of help output (and it will be listed at the end of the usage
152//! line as `[all_the_rest...]`), and it's up to the caller to append any
153//! extra help output for the meaning of the captured arguments. This is to
154//! enable situations where some amount of argument processing needs to happen
155//! before the rest of the arguments can be interpreted, and shouldn't be used
156//! for regular use as it might be confusing.
157//!
158//! Subcommands are also supported. To use a subcommand, declare a separate
159//! `FromArgs` type for each subcommand as well as an enum that cases
160//! over each command:
161//!
162//! ```rust
163//! # use argh::FromArgs;
164//!
165//! #[derive(FromArgs, PartialEq, Debug)]
166//! /// Top-level command.
167//! struct TopLevel {
168//!     #[argh(subcommand)]
169//!     nested: MySubCommandEnum,
170//! }
171//!
172//! #[derive(FromArgs, PartialEq, Debug)]
173//! #[argh(subcommand)]
174//! enum MySubCommandEnum {
175//!     One(SubCommandOne),
176//!     Two(SubCommandTwo),
177//! }
178//!
179//! #[derive(FromArgs, PartialEq, Debug)]
180//! /// First subcommand.
181//! #[argh(subcommand, name = "one")]
182//! struct SubCommandOne {
183//!     #[argh(option)]
184//!     /// how many x
185//!     x: usize,
186//! }
187//!
188//! #[derive(FromArgs, PartialEq, Debug)]
189//! /// Second subcommand.
190//! #[argh(subcommand, name = "two")]
191//! struct SubCommandTwo {
192//!     #[argh(switch)]
193//!     /// whether to fooey
194//!     fooey: bool,
195//! }
196//! ```
197//!
198//! You can also discover subcommands dynamically at runtime. To do this,
199//! declare subcommands as usual and add a variant to the enum with the
200//! `dynamic` attribute. Instead of deriving `FromArgs`, the value inside the
201//! dynamic variant should implement `DynamicSubCommand`.
202//!
203//! ```rust
204//! # use argh::CommandInfo;
205//! # use argh::DynamicSubCommand;
206//! # use argh::EarlyExit;
207//! # use argh::FromArgs;
208//! # use once_cell::sync::OnceCell;
209//!
210//! #[derive(FromArgs, PartialEq, Debug)]
211//! /// Top-level command.
212//! struct TopLevel {
213//!     #[argh(subcommand)]
214//!     nested: MySubCommandEnum,
215//! }
216//!
217//! #[derive(FromArgs, PartialEq, Debug)]
218//! #[argh(subcommand)]
219//! enum MySubCommandEnum {
220//!     Normal(NormalSubCommand),
221//!     #[argh(dynamic)]
222//!     Dynamic(Dynamic),
223//! }
224//!
225//! #[derive(FromArgs, PartialEq, Debug)]
226//! /// Normal subcommand.
227//! #[argh(subcommand, name = "normal")]
228//! struct NormalSubCommand {
229//!     #[argh(option)]
230//!     /// how many x
231//!     x: usize,
232//! }
233//!
234//! /// Dynamic subcommand.
235//! #[derive(PartialEq, Debug)]
236//! struct Dynamic {
237//!     name: String
238//! }
239//!
240//! impl DynamicSubCommand for Dynamic {
241//!     fn commands() -> &'static [&'static CommandInfo] {
242//!         static RET: OnceCell<Vec<&'static CommandInfo>> = OnceCell::new();
243//!         RET.get_or_init(|| {
244//!             let mut commands = Vec::new();
245//!
246//!             // argh needs the `CommandInfo` structs we generate to be valid
247//!             // for the static lifetime. We can allocate the structures on
248//!             // the heap with `Box::new` and use `Box::leak` to get a static
249//!             // reference to them. We could also just use a constant
250//!             // reference, but only because this is a synthetic example; the
251//!             // point of using dynamic commands is to have commands you
252//!             // don't know about until runtime!
253//!             commands.push(&*Box::leak(Box::new(CommandInfo {
254//!                 name: "dynamic_command",
255//!                 description: "A dynamic command",
256//!             })));
257//!
258//!             commands
259//!         })
260//!     }
261//!
262//!     fn try_redact_arg_values(
263//!         command_name: &[&str],
264//!         args: &[&str],
265//!     ) -> Option<Result<Vec<String>, EarlyExit>> {
266//!         for command in Self::commands() {
267//!             if command_name.last() == Some(&command.name) {
268//!                 // Process arguments and redact values here.
269//!                 if !args.is_empty() {
270//!                     return Some(Err("Our example dynamic command never takes arguments!"
271//!                                     .to_string().into()));
272//!                 }
273//!                 return Some(Ok(Vec::new()))
274//!             }
275//!         }
276//!         None
277//!     }
278//!
279//!     fn try_from_args(command_name: &[&str], args: &[&str]) -> Option<Result<Self, EarlyExit>> {
280//!         for command in Self::commands() {
281//!             if command_name.last() == Some(&command.name) {
282//!                 if !args.is_empty() {
283//!                     return Some(Err("Our example dynamic command never takes arguments!"
284//!                                     .to_string().into()));
285//!                 }
286//!                 return Some(Ok(Dynamic { name: command.name.to_string() }))
287//!             }
288//!         }
289//!         None
290//!     }
291//! }
292//! ```
293//!
294//! Programs that are run from an environment such as cargo may find it
295//! useful to have positional arguments present in the structure but
296//! omitted from the usage output. This can be accomplished by adding
297//! the `hidden_help` attribute to that argument:
298//!
299//! ```rust
300//! # use argh::FromArgs;
301//!
302//! #[derive(FromArgs)]
303//! /// Cargo arguments
304//! struct CargoArgs {
305//!     // Cargo puts the command name invoked into the first argument,
306//!     // so we don't want this argument to show up in the usage text.
307//!     #[argh(positional, hidden_help)]
308//!     command: String,
309//!     /// an option used for internal debugging
310//!     #[argh(option, hidden_help)]
311//!     internal_debugging: String,
312//!     #[argh(positional)]
313//!     real_first_arg: String,
314//! }
315//! ```
316
317#![deny(missing_docs)]
318
319use std::str::FromStr;
320
321pub use argh_derive::{ArgsInfo, FromArgs};
322
323/// Information about a particular command used for output.
324pub type CommandInfo = argh_shared::CommandInfo<'static>;
325
326/// Information about the command including the options and arguments.
327pub type CommandInfoWithArgs = argh_shared::CommandInfoWithArgs<'static>;
328
329/// Information about a subcommand.
330pub type SubCommandInfo = argh_shared::SubCommandInfo<'static>;
331
332pub use argh_shared::{ErrorCodeInfo, FlagInfo, FlagInfoKind, Optionality, PositionalInfo};
333
334/// Structured information about the command line arguments.
335pub trait ArgsInfo {
336    /// Returns the argument info.
337    fn get_args_info() -> CommandInfoWithArgs;
338
339    /// Returns the list of subcommands
340    fn get_subcommands() -> Vec<SubCommandInfo> {
341        Self::get_args_info().commands
342    }
343}
344
345/// Types which can be constructed from a set of commandline arguments.
346pub trait FromArgs: Sized {
347    /// Construct the type from an input set of arguments.
348    ///
349    /// The first argument `command_name` is the identifier for the current command. In most cases,
350    /// users should only pass in a single item for the command name, which typically comes from
351    /// the first item from `std::env::args()`. Implementations however should append the
352    /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This
353    /// allows `argh` to generate correct subcommand help strings.
354    ///
355    /// The second argument `args` is the rest of the command line arguments.
356    ///
357    /// # Examples
358    ///
359    /// ```rust
360    /// # use argh::FromArgs;
361    ///
362    /// /// Command to manage a classroom.
363    /// #[derive(Debug, PartialEq, FromArgs)]
364    /// struct ClassroomCmd {
365    ///     #[argh(subcommand)]
366    ///     subcommands: Subcommands,
367    /// }
368    ///
369    /// #[derive(Debug, PartialEq, FromArgs)]
370    /// #[argh(subcommand)]
371    /// enum Subcommands {
372    ///     List(ListCmd),
373    ///     Add(AddCmd),
374    /// }
375    ///
376    /// /// list all the classes.
377    /// #[derive(Debug, PartialEq, FromArgs)]
378    /// #[argh(subcommand, name = "list")]
379    /// struct ListCmd {
380    ///     /// list classes for only this teacher.
381    ///     #[argh(option)]
382    ///     teacher_name: Option<String>,
383    /// }
384    ///
385    /// /// add students to a class.
386    /// #[derive(Debug, PartialEq, FromArgs)]
387    /// #[argh(subcommand, name = "add")]
388    /// struct AddCmd {
389    ///     /// the name of the class's teacher.
390    ///     #[argh(option)]
391    ///     teacher_name: String,
392    ///
393    ///     /// the name of the class.
394    ///     #[argh(positional)]
395    ///     class_name: String,
396    /// }
397    ///
398    /// let args = ClassroomCmd::from_args(
399    ///     &["classroom"],
400    ///     &["list", "--teacher-name", "Smith"],
401    /// ).unwrap();
402    /// assert_eq!(
403    ///    args,
404    ///     ClassroomCmd {
405    ///         subcommands: Subcommands::List(ListCmd {
406    ///             teacher_name: Some("Smith".to_string()),
407    ///         })
408    ///     },
409    /// );
410    ///
411    /// // Help returns an error, but internally returns an `Ok` status.
412    /// let early_exit = ClassroomCmd::from_args(
413    ///     &["classroom"],
414    ///     &["help"],
415    /// ).unwrap_err();
416    /// assert_eq!(
417    ///     early_exit,
418    ///     argh::EarlyExit {
419    ///        output: r#"Usage: classroom <command> [<args>]
420    ///
421    /// Command to manage a classroom.
422    ///
423    /// Options:
424    ///   --help            display usage information
425    ///
426    /// Commands:
427    ///   list              list all the classes.
428    ///   add               add students to a class.
429    /// "#.to_string(),
430    ///        status: Ok(()),
431    ///     },
432    /// );
433    ///
434    /// // Help works with subcommands.
435    /// let early_exit = ClassroomCmd::from_args(
436    ///     &["classroom"],
437    ///     &["list", "help"],
438    /// ).unwrap_err();
439    /// assert_eq!(
440    ///     early_exit,
441    ///     argh::EarlyExit {
442    ///        output: r#"Usage: classroom list [--teacher-name <teacher-name>]
443    ///
444    /// list all the classes.
445    ///
446    /// Options:
447    ///   --teacher-name    list classes for only this teacher.
448    ///   --help            display usage information
449    /// "#.to_string(),
450    ///        status: Ok(()),
451    ///     },
452    /// );
453    ///
454    /// // Incorrect arguments will error out.
455    /// let err = ClassroomCmd::from_args(
456    ///     &["classroom"],
457    ///     &["lisp"],
458    /// ).unwrap_err();
459    /// assert_eq!(
460    ///    err,
461    ///    argh::EarlyExit {
462    ///        output: "Unrecognized argument: lisp\n".to_string(),
463    ///        status: Err(()),
464    ///     },
465    /// );
466    /// ```
467    fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit>;
468
469    /// Get a String with just the argument names, e.g., options, flags, subcommands, etc, but
470    /// without the values of the options and arguments. This can be useful as a means to capture
471    /// anonymous usage statistics without revealing the content entered by the end user.
472    ///
473    /// The first argument `command_name` is the identifier for the current command. In most cases,
474    /// users should only pass in a single item for the command name, which typically comes from
475    /// the first item from `std::env::args()`. Implementations however should append the
476    /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This
477    /// allows `argh` to generate correct subcommand help strings.
478    ///
479    /// The second argument `args` is the rest of the command line arguments.
480    ///
481    /// # Examples
482    ///
483    /// ```rust
484    /// # use argh::FromArgs;
485    ///
486    /// /// Command to manage a classroom.
487    /// #[derive(FromArgs)]
488    /// struct ClassroomCmd {
489    ///     #[argh(subcommand)]
490    ///     subcommands: Subcommands,
491    /// }
492    ///
493    /// #[derive(FromArgs)]
494    /// #[argh(subcommand)]
495    /// enum Subcommands {
496    ///     List(ListCmd),
497    ///     Add(AddCmd),
498    /// }
499    ///
500    /// /// list all the classes.
501    /// #[derive(FromArgs)]
502    /// #[argh(subcommand, name = "list")]
503    /// struct ListCmd {
504    ///     /// list classes for only this teacher.
505    ///     #[argh(option)]
506    ///     teacher_name: Option<String>,
507    /// }
508    ///
509    /// /// add students to a class.
510    /// #[derive(FromArgs)]
511    /// #[argh(subcommand, name = "add")]
512    /// struct AddCmd {
513    ///     /// the name of the class's teacher.
514    ///     #[argh(option)]
515    ///     teacher_name: String,
516    ///
517    ///     /// has the class started yet?
518    ///     #[argh(switch)]
519    ///     started: bool,
520    ///
521    ///     /// the name of the class.
522    ///     #[argh(positional)]
523    ///     class_name: String,
524    ///
525    ///     /// the student names.
526    ///     #[argh(positional)]
527    ///     students: Vec<String>,
528    /// }
529    ///
530    /// let args = ClassroomCmd::redact_arg_values(
531    ///     &["classroom"],
532    ///     &["list"],
533    /// ).unwrap();
534    /// assert_eq!(
535    ///     args,
536    ///     &[
537    ///         "classroom",
538    ///         "list",
539    ///     ],
540    /// );
541    ///
542    /// let args = ClassroomCmd::redact_arg_values(
543    ///     &["classroom"],
544    ///     &["list", "--teacher-name", "Smith"],
545    /// ).unwrap();
546    /// assert_eq!(
547    ///    args,
548    ///    &[
549    ///         "classroom",
550    ///         "list",
551    ///         "--teacher-name",
552    ///     ],
553    /// );
554    ///
555    /// let args = ClassroomCmd::redact_arg_values(
556    ///     &["classroom"],
557    ///     &["add", "--teacher-name", "Smith", "--started", "Math", "Abe", "Sung"],
558    /// ).unwrap();
559    /// assert_eq!(
560    ///     args,
561    ///     &[
562    ///         "classroom",
563    ///         "add",
564    ///         "--teacher-name",
565    ///         "--started",
566    ///         "class_name",
567    ///         "students",
568    ///         "students",
569    ///     ],
570    /// );
571    ///
572    /// // `ClassroomCmd::redact_arg_values` will error out if passed invalid arguments.
573    /// assert_eq!(
574    ///     ClassroomCmd::redact_arg_values(&["classroom"], &["add", "--teacher-name"]),
575    ///     Err(argh::EarlyExit {
576    ///         output: "No value provided for option '--teacher-name'.\n".into(),
577    ///         status: Err(()),
578    ///     }),
579    /// );
580    ///
581    /// // `ClassroomCmd::redact_arg_values` will generate help messages.
582    /// assert_eq!(
583    ///     ClassroomCmd::redact_arg_values(&["classroom"], &["help"]),
584    ///     Err(argh::EarlyExit {
585    ///         output: r#"Usage: classroom <command> [<args>]
586    ///
587    /// Command to manage a classroom.
588    ///
589    /// Options:
590    ///   --help            display usage information
591    ///
592    /// Commands:
593    ///   list              list all the classes.
594    ///   add               add students to a class.
595    /// "#.to_string(),
596    ///         status: Ok(()),
597    ///     }),
598    /// );
599    /// ```
600    fn redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result<Vec<String>, EarlyExit> {
601        Ok(vec!["<<REDACTED>>".into()])
602    }
603}
604
605/// A top-level `FromArgs` implementation that is not a subcommand.
606pub trait TopLevelCommand: FromArgs {}
607
608/// A `FromArgs` implementation that can parse into one or more subcommands.
609pub trait SubCommands: FromArgs {
610    /// Info for the commands.
611    const COMMANDS: &'static [&'static CommandInfo];
612
613    /// Get a list of commands that are discovered at runtime.
614    fn dynamic_commands() -> &'static [&'static CommandInfo] {
615        &[]
616    }
617}
618
619/// A `FromArgs` implementation that represents a single subcommand.
620pub trait SubCommand: FromArgs {
621    /// Information about the subcommand.
622    const COMMAND: &'static CommandInfo;
623}
624
625impl<T: SubCommand> SubCommands for T {
626    const COMMANDS: &'static [&'static CommandInfo] = &[T::COMMAND];
627}
628
629/// Trait implemented by values returned from a dynamic subcommand handler.
630pub trait DynamicSubCommand: Sized {
631    /// Info about supported subcommands.
632    fn commands() -> &'static [&'static CommandInfo];
633
634    /// Perform the function of `FromArgs::redact_arg_values` for this dynamic
635    /// command.
636    ///
637    /// The full list of subcommands, ending with the subcommand that should be
638    /// dynamically recognized, is passed in `command_name`. If the command
639    /// passed is not recognized, this function should return `None`. Otherwise
640    /// it should return `Some`, and the value within the `Some` has the same
641    /// semantics as the return of `FromArgs::redact_arg_values`.
642    fn try_redact_arg_values(
643        command_name: &[&str],
644        args: &[&str],
645    ) -> Option<Result<Vec<String>, EarlyExit>>;
646
647    /// Perform the function of `FromArgs::from_args` for this dynamic command.
648    ///
649    /// The full list of subcommands, ending with the subcommand that should be
650    /// dynamically recognized, is passed in `command_name`. If the command
651    /// passed is not recognized, this function should return `None`. Otherwise
652    /// it should return `Some`, and the value within the `Some` has the same
653    /// semantics as the return of `FromArgs::from_args`.
654    fn try_from_args(command_name: &[&str], args: &[&str]) -> Option<Result<Self, EarlyExit>>;
655}
656
657/// Information to display to the user about why a `FromArgs` construction exited early.
658///
659/// This can occur due to either failed parsing or a flag like `--help`.
660#[derive(Debug, Clone, PartialEq, Eq)]
661pub struct EarlyExit {
662    /// The output to display to the user of the commandline tool.
663    pub output: String,
664    /// Status of argument parsing.
665    ///
666    /// `Ok` if the command was parsed successfully and the early exit is due
667    /// to a flag like `--help` causing early exit with output.
668    ///
669    /// `Err` if the arguments were not successfully parsed.
670    // TODO replace with std::process::ExitCode when stable.
671    pub status: Result<(), ()>,
672}
673
674impl From<String> for EarlyExit {
675    fn from(err_msg: String) -> Self {
676        Self { output: err_msg, status: Err(()) }
677    }
678}
679
680/// Extract the base cmd from a path
681fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str {
682    std::path::Path::new(path).file_name().and_then(|s| s.to_str()).unwrap_or(default)
683}
684
685/// Create a `FromArgs` type from the current process's `env::args`.
686///
687/// This function will exit early from the current process if argument parsing
688/// was unsuccessful or if information like `--help` was requested. Error messages will be printed
689/// to stderr, and `--help` output to stdout.
690pub fn from_env<T: TopLevelCommand>() -> T {
691    let strings: Vec<String> = std::env::args_os()
692        .map(|s| s.into_string())
693        .collect::<Result<Vec<_>, _>>()
694        .unwrap_or_else(|arg| {
695            eprintln!("Invalid utf8: {}", arg.to_string_lossy());
696            std::process::exit(1)
697        });
698
699    if strings.is_empty() {
700        eprintln!("No program name, argv is empty");
701        std::process::exit(1)
702    }
703
704    let cmd = cmd(&strings[0], &strings[0]);
705    let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
706    T::from_args(&[cmd], &strs[1..]).unwrap_or_else(|early_exit| {
707        std::process::exit(match early_exit.status {
708            Ok(()) => {
709                println!("{}", early_exit.output);
710                0
711            }
712            Err(()) => {
713                eprintln!("{}\nRun {} --help for more information.", early_exit.output, cmd);
714                1
715            }
716        })
717    })
718}
719
720/// Create a `FromArgs` type from the current process's `env::args`.
721///
722/// This special cases usages where argh is being used in an environment where cargo is
723/// driving the build. We skip the second env variable.
724///
725/// This function will exit early from the current process if argument parsing
726/// was unsuccessful or if information like `--help` was requested. Error messages will be printed
727/// to stderr, and `--help` output to stdout.
728pub fn cargo_from_env<T: TopLevelCommand>() -> T {
729    let strings: Vec<String> = std::env::args().collect();
730    let cmd = cmd(&strings[1], &strings[1]);
731    let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
732    T::from_args(&[cmd], &strs[2..]).unwrap_or_else(|early_exit| {
733        std::process::exit(match early_exit.status {
734            Ok(()) => {
735                println!("{}", early_exit.output);
736                0
737            }
738            Err(()) => {
739                eprintln!("{}\nRun --help for more information.", early_exit.output);
740                1
741            }
742        })
743    })
744}
745
746/// Types which can be constructed from a single commandline value.
747///
748/// Any field type declared in a struct that derives `FromArgs` must implement
749/// this trait. A blanket implementation exists for types implementing
750/// `FromStr<Error: Display>`. Custom types can implement this trait
751/// directly.
752pub trait FromArgValue: Sized {
753    /// Construct the type from a commandline value, returning an error string
754    /// on failure.
755    fn from_arg_value(value: &str) -> Result<Self, String>;
756}
757
758impl<T> FromArgValue for T
759where
760    T: FromStr,
761    T::Err: std::fmt::Display,
762{
763    fn from_arg_value(value: &str) -> Result<Self, String> {
764        T::from_str(value).map_err(|x| x.to_string())
765    }
766}
767
768// The following items are all used by the generated code, and should not be considered part
769// of this library's public API surface.
770
771#[doc(hidden)]
772pub trait ParseFlag {
773    fn set_flag(&mut self, arg: &str);
774}
775
776impl<T: Flag> ParseFlag for T {
777    fn set_flag(&mut self, _arg: &str) {
778        <T as Flag>::set_flag(self);
779    }
780}
781
782#[doc(hidden)]
783pub struct RedactFlag {
784    pub slot: Option<String>,
785}
786
787impl ParseFlag for RedactFlag {
788    fn set_flag(&mut self, arg: &str) {
789        self.slot = Some(arg.to_string());
790    }
791}
792
793// A trait for for slots that reserve space for a value and know how to parse that value
794// from a command-line `&str` argument.
795//
796// This trait is only implemented for the type `ParseValueSlotTy`. This indirection is
797// necessary to allow abstracting over `ParseValueSlotTy` instances with different
798// generic parameters.
799#[doc(hidden)]
800pub trait ParseValueSlot {
801    fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>;
802}
803
804// The concrete type implementing the `ParseValueSlot` trait.
805//
806// `T` is the type to be parsed from a single string.
807// `Slot` is the type of the container that can hold a value or values of type `T`.
808#[doc(hidden)]
809pub struct ParseValueSlotTy<Slot, T> {
810    // The slot for a parsed value.
811    pub slot: Slot,
812    // The function to parse the value from a string
813    pub parse_func: fn(&str, &str) -> Result<T, String>,
814}
815
816// `ParseValueSlotTy<Option<T>, T>` is used as the slot for all non-repeating
817// arguments, both optional and required.
818impl<T> ParseValueSlot for ParseValueSlotTy<Option<T>, T> {
819    fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {
820        if self.slot.is_some() {
821            return Err("duplicate values provided".to_string());
822        }
823        self.slot = Some((self.parse_func)(arg, value)?);
824        Ok(())
825    }
826}
827
828// `ParseValueSlotTy<Vec<T>, T>` is used as the slot for repeating arguments.
829impl<T> ParseValueSlot for ParseValueSlotTy<Vec<T>, T> {
830    fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {
831        self.slot.push((self.parse_func)(arg, value)?);
832        Ok(())
833    }
834}
835
836/// A type which can be the receiver of a `Flag`.
837pub trait Flag {
838    /// Creates a default instance of the flag value;
839    fn default() -> Self
840    where
841        Self: Sized;
842
843    /// Sets the flag. This function is called when the flag is provided.
844    fn set_flag(&mut self);
845}
846
847impl Flag for bool {
848    fn default() -> Self {
849        false
850    }
851    fn set_flag(&mut self) {
852        *self = true;
853    }
854}
855
856impl Flag for Option<bool> {
857    fn default() -> Self {
858        None
859    }
860
861    fn set_flag(&mut self) {
862        *self = Some(true);
863    }
864}
865
866macro_rules! impl_flag_for_integers {
867    ($($ty:ty,)*) => {
868        $(
869            impl Flag for $ty {
870                fn default() -> Self {
871                    0
872                }
873                fn set_flag(&mut self) {
874                    *self = self.saturating_add(1);
875                }
876            }
877        )*
878    }
879}
880
881impl_flag_for_integers![u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,];
882
883/// This function implements argument parsing for structs.
884///
885/// `cmd_name`: The identifier for the current command.
886/// `args`: The command line arguments.
887/// `parse_options`: Helper to parse optional arguments.
888/// `parse_positionals`: Helper to parse positional arguments.
889/// `parse_subcommand`: Helper to parse a subcommand.
890/// `help_func`: Generate a help message.
891#[doc(hidden)]
892pub fn parse_struct_args(
893    cmd_name: &[&str],
894    args: &[&str],
895    mut parse_options: ParseStructOptions<'_>,
896    mut parse_positionals: ParseStructPositionals<'_>,
897    mut parse_subcommand: Option<ParseStructSubCommand<'_>>,
898    help_func: &dyn Fn() -> String,
899) -> Result<(), EarlyExit> {
900    let mut help = false;
901    let mut remaining_args = args;
902    let mut positional_index = 0;
903    let mut options_ended = false;
904
905    'parse_args: while let Some(&next_arg) = remaining_args.first() {
906        remaining_args = &remaining_args[1..];
907        if (next_arg == "--help" || next_arg == "help") && !options_ended {
908            help = true;
909            continue;
910        }
911
912        if next_arg.starts_with('-') && !options_ended {
913            if next_arg == "--" {
914                options_ended = true;
915                continue;
916            }
917
918            if help {
919                return Err("Trailing arguments are not allowed after `help`.".to_string().into());
920            }
921
922            parse_options.parse(next_arg, &mut remaining_args)?;
923            continue;
924        }
925
926        if let Some(ref mut parse_subcommand) = parse_subcommand {
927            if parse_subcommand.parse(help, cmd_name, next_arg, remaining_args)? {
928                // Unset `help`, since we handled it in the subcommand
929                help = false;
930                break 'parse_args;
931            }
932        }
933
934        options_ended |= parse_positionals.parse(&mut positional_index, next_arg)?;
935    }
936
937    if help {
938        Err(EarlyExit { output: help_func(), status: Ok(()) })
939    } else {
940        Ok(())
941    }
942}
943
944#[doc(hidden)]
945pub struct ParseStructOptions<'a> {
946    /// A mapping from option string literals to the entry
947    /// in the output table. This may contain multiple entries mapping to
948    /// the same location in the table if both a short and long version
949    /// of the option exist (`-z` and `--zoo`).
950    pub arg_to_slot: &'static [(&'static str, usize)],
951
952    /// The storage for argument output data.
953    pub slots: &'a mut [ParseStructOption<'a>],
954}
955
956impl<'a> ParseStructOptions<'a> {
957    /// Parse a commandline option.
958    ///
959    /// `arg`: the current option argument being parsed (e.g. `--foo`).
960    /// `remaining_args`: the remaining command line arguments. This slice
961    /// will be advanced forwards if the option takes a value argument.
962    fn parse(&mut self, arg: &str, remaining_args: &mut &[&str]) -> Result<(), String> {
963        let pos = self
964            .arg_to_slot
965            .iter()
966            .find_map(|&(name, pos)| if name == arg { Some(pos) } else { None })
967            .ok_or_else(|| unrecognized_argument(arg))?;
968
969        match self.slots[pos] {
970            ParseStructOption::Flag(ref mut b) => b.set_flag(arg),
971            ParseStructOption::Value(ref mut pvs) => {
972                let value = remaining_args
973                    .first()
974                    .ok_or_else(|| ["No value provided for option '", arg, "'.\n"].concat())?;
975                *remaining_args = &remaining_args[1..];
976                pvs.fill_slot(arg, value).map_err(|s| {
977                    ["Error parsing option '", arg, "' with value '", value, "': ", &s, "\n"]
978                        .concat()
979                })?;
980            }
981        }
982
983        Ok(())
984    }
985}
986
987fn unrecognized_argument(x: &str) -> String {
988    ["Unrecognized argument: ", x, "\n"].concat()
989}
990
991// `--` or `-` options, including a mutable reference to their value.
992#[doc(hidden)]
993pub enum ParseStructOption<'a> {
994    // A flag which is set to `true` when provided.
995    Flag(&'a mut dyn ParseFlag),
996    // A value which is parsed from the string following the `--` argument,
997    // e.g. `--foo bar`.
998    Value(&'a mut dyn ParseValueSlot),
999}
1000
1001#[doc(hidden)]
1002pub struct ParseStructPositionals<'a> {
1003    pub positionals: &'a mut [ParseStructPositional<'a>],
1004    pub last_is_repeating: bool,
1005    pub last_is_greedy: bool,
1006}
1007
1008impl<'a> ParseStructPositionals<'a> {
1009    /// Parse the next positional argument.
1010    ///
1011    /// `arg`: the argument supplied by the user.
1012    ///
1013    /// Returns true if non-positional argument parsing should stop
1014    /// after this one.
1015    fn parse(&mut self, index: &mut usize, arg: &str) -> Result<bool, EarlyExit> {
1016        if *index < self.positionals.len() {
1017            self.positionals[*index].parse(arg)?;
1018
1019            if self.last_is_repeating && *index == self.positionals.len() - 1 {
1020                // Don't increment position if we're at the last arg
1021                // *and* the last arg is repeating. If it's also remainder,
1022                // halt non-option processing after this.
1023                Ok(self.last_is_greedy)
1024            } else {
1025                // If it is repeating, though, increment the index and continue
1026                // processing options.
1027                *index += 1;
1028                Ok(false)
1029            }
1030        } else {
1031            Err(EarlyExit { output: unrecognized_arg(arg), status: Err(()) })
1032        }
1033    }
1034}
1035
1036#[doc(hidden)]
1037pub struct ParseStructPositional<'a> {
1038    // The positional's name
1039    pub name: &'static str,
1040
1041    // The function to parse the positional.
1042    pub slot: &'a mut dyn ParseValueSlot,
1043}
1044
1045impl<'a> ParseStructPositional<'a> {
1046    /// Parse a positional argument.
1047    ///
1048    /// `arg`: the argument supplied by the user.
1049    fn parse(&mut self, arg: &str) -> Result<(), EarlyExit> {
1050        self.slot.fill_slot("", arg).map_err(|s| {
1051            [
1052                "Error parsing positional argument '",
1053                self.name,
1054                "' with value '",
1055                arg,
1056                "': ",
1057                &s,
1058                "\n",
1059            ]
1060            .concat()
1061            .into()
1062        })
1063    }
1064}
1065
1066// A type to simplify parsing struct subcommands.
1067//
1068// This indirection is necessary to allow abstracting over `FromArgs` instances with different
1069// generic parameters.
1070#[doc(hidden)]
1071pub struct ParseStructSubCommand<'a> {
1072    // The subcommand commands
1073    pub subcommands: &'static [&'static CommandInfo],
1074
1075    pub dynamic_subcommands: &'a [&'static CommandInfo],
1076
1077    // The function to parse the subcommand arguments.
1078    #[allow(clippy::type_complexity)]
1079    pub parse_func: &'a mut dyn FnMut(&[&str], &[&str]) -> Result<(), EarlyExit>,
1080}
1081
1082impl<'a> ParseStructSubCommand<'a> {
1083    fn parse(
1084        &mut self,
1085        help: bool,
1086        cmd_name: &[&str],
1087        arg: &str,
1088        remaining_args: &[&str],
1089    ) -> Result<bool, EarlyExit> {
1090        for subcommand in self.subcommands.iter().chain(self.dynamic_subcommands.iter()) {
1091            if subcommand.name == arg {
1092                let mut command = cmd_name.to_owned();
1093                command.push(subcommand.name);
1094                let prepended_help;
1095                let remaining_args = if help {
1096                    prepended_help = prepend_help(remaining_args);
1097                    &prepended_help
1098                } else {
1099                    remaining_args
1100                };
1101
1102                (self.parse_func)(&command, remaining_args)?;
1103
1104                return Ok(true);
1105            }
1106        }
1107
1108        Ok(false)
1109    }
1110}
1111
1112// Prepend `help` to a list of arguments.
1113// This is used to pass the `help` argument on to subcommands.
1114fn prepend_help<'a>(args: &[&'a str]) -> Vec<&'a str> {
1115    [&["help"], args].concat()
1116}
1117
1118#[doc(hidden)]
1119pub fn print_subcommands<'a>(commands: impl Iterator<Item = &'a CommandInfo>) -> String {
1120    let mut out = String::new();
1121    for cmd in commands {
1122        argh_shared::write_description(&mut out, cmd);
1123    }
1124    out
1125}
1126
1127fn unrecognized_arg(arg: &str) -> String {
1128    ["Unrecognized argument: ", arg, "\n"].concat()
1129}
1130
1131// An error string builder to report missing required options and subcommands.
1132#[doc(hidden)]
1133#[derive(Default)]
1134pub struct MissingRequirements {
1135    options: Vec<&'static str>,
1136    subcommands: Option<Vec<&'static CommandInfo>>,
1137    positional_args: Vec<&'static str>,
1138}
1139
1140const NEWLINE_INDENT: &str = "\n    ";
1141
1142impl MissingRequirements {
1143    // Add a missing required option.
1144    #[doc(hidden)]
1145    pub fn missing_option(&mut self, name: &'static str) {
1146        self.options.push(name)
1147    }
1148
1149    // Add a missing required subcommand.
1150    #[doc(hidden)]
1151    pub fn missing_subcommands(&mut self, commands: impl Iterator<Item = &'static CommandInfo>) {
1152        self.subcommands = Some(commands.collect());
1153    }
1154
1155    // Add a missing positional argument.
1156    #[doc(hidden)]
1157    pub fn missing_positional_arg(&mut self, name: &'static str) {
1158        self.positional_args.push(name)
1159    }
1160
1161    // If any missing options or subcommands were provided, returns an error string
1162    // describing the missing args.
1163    #[doc(hidden)]
1164    pub fn err_on_any(&self) -> Result<(), String> {
1165        if self.options.is_empty() && self.subcommands.is_none() && self.positional_args.is_empty()
1166        {
1167            return Ok(());
1168        }
1169
1170        let mut output = String::new();
1171
1172        if !self.positional_args.is_empty() {
1173            output.push_str("Required positional arguments not provided:");
1174            for arg in &self.positional_args {
1175                output.push_str(NEWLINE_INDENT);
1176                output.push_str(arg);
1177            }
1178        }
1179
1180        if !self.options.is_empty() {
1181            if !self.positional_args.is_empty() {
1182                output.push('\n');
1183            }
1184            output.push_str("Required options not provided:");
1185            for option in &self.options {
1186                output.push_str(NEWLINE_INDENT);
1187                output.push_str(option);
1188            }
1189        }
1190
1191        if let Some(missing_subcommands) = &self.subcommands {
1192            if !self.options.is_empty() {
1193                output.push('\n');
1194            }
1195            output.push_str("One of the following subcommands must be present:");
1196            output.push_str(NEWLINE_INDENT);
1197            output.push_str("help");
1198            for subcommand in missing_subcommands {
1199                output.push_str(NEWLINE_INDENT);
1200                output.push_str(subcommand.name);
1201            }
1202        }
1203
1204        output.push('\n');
1205
1206        Err(output)
1207    }
1208}
1209
1210#[cfg(test)]
1211mod test {
1212    use super::*;
1213
1214    #[test]
1215    fn test_cmd_extraction() {
1216        let expected = "test_cmd";
1217        let path = format!("/tmp/{}", expected);
1218        let cmd = cmd(&path, &path);
1219        assert_eq!(expected, cmd);
1220    }
1221}