Skip to main content

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