[go: up one dir, main page]

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