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 //! fn main() {
35 //!     let up: GoUp = argh::from_env();
36 //! }
37 //! ```
38 //!
39 //! `./some_bin --help` will then output the following:
40 //!
41 //! ```bash
42 //! Usage: cmdname [-j] --height <height> [--pilot-nickname <pilot-nickname>]
43 //!
44 //! Reach new heights.
45 //!
46 //! Options:
47 //!   -j, --jump        whether or not to jump
48 //!   --height          how high to go
49 //!   --pilot-nickname  an optional nickname for the pilot
50 //!   --help            display usage information
51 //! ```
52 //!
53 //! The resulting program can then be used in any of these ways:
54 //! - `./some_bin --height 5`
55 //! - `./some_bin -j --height 5`
56 //! - `./some_bin --jump --height 5 --pilot-nickname Wes`
57 //!
58 //! Switches, like `jump`, are optional and will be set to true if provided.
59 //!
60 //! Options, like `height` and `pilot_nickname`, can be either required,
61 //! optional, or repeating, depending on whether they are contained in an
62 //! `Option` or a `Vec`. Default values can be provided using the
63 //! `#[argh(default = "<your_code_here>")]` attribute, and in this case an
64 //! option is treated as optional.
65 //!
66 //! ```rust
67 //! use argh::FromArgs;
68 //!
69 //! fn default_height() -> usize {
70 //!     5
71 //! }
72 //!
73 //! #[derive(FromArgs)]
74 //! /// Reach new heights.
75 //! struct GoUp {
76 //!     /// an optional nickname for the pilot
77 //!     #[argh(option)]
78 //!     pilot_nickname: Option<String>,
79 //!
80 //!     /// an optional height
81 //!     #[argh(option, default = "default_height()")]
82 //!     height: usize,
83 //!
84 //!     /// an optional direction which is "up" by default
85 //!     #[argh(option, default = "String::from(\"only up\")")]
86 //!     direction: String,
87 //! }
88 //!
89 //! fn main() {
90 //!     let up: GoUp = argh::from_env();
91 //! }
92 //! ```
93 //!
94 //! Custom option types can be deserialized so long as they implement the
95 //! `FromArgValue` trait (automatically implemented for all `FromStr` types).
96 //! If more customized parsing is required, you can supply a custom
97 //! `fn(&str) -> Result<T, String>` using the `from_str_fn` attribute:
98 //!
99 //! ```
100 //! # use argh::FromArgs;
101 //!
102 //! #[derive(FromArgs)]
103 //! /// Goofy thing.
104 //! struct FiveStruct {
105 //!     /// always five
106 //!     #[argh(option, from_str_fn(always_five))]
107 //!     five: usize,
108 //! }
109 //!
110 //! fn always_five(_value: &str) -> Result<usize, String> {
111 //!     Ok(5)
112 //! }
113 //! ```
114 //!
115 //! Positional arguments can be declared using `#[argh(positional)]`.
116 //! These arguments will be parsed in order of their declaration in
117 //! the structure:
118 //!
119 //! ```rust
120 //! use argh::FromArgs;
121 //! #[derive(FromArgs, PartialEq, Debug)]
122 //! /// A command with positional arguments.
123 //! struct WithPositional {
124 //!     #[argh(positional)]
125 //!     first: String,
126 //! }
127 //! ```
128 //!
129 //! The last positional argument may include a default, or be wrapped in
130 //! `Option` or `Vec` to indicate an optional or repeating positional argument.
131 //!
132 //! Subcommands are also supported. To use a subcommand, declare a separate
133 //! `FromArgs` type for each subcommand as well as an enum that cases
134 //! over each command:
135 //!
136 //! ```rust
137 //! # use argh::FromArgs;
138 //!
139 //! #[derive(FromArgs, PartialEq, Debug)]
140 //! /// Top-level command.
141 //! struct TopLevel {
142 //!     #[argh(subcommand)]
143 //!     nested: MySubCommandEnum,
144 //! }
145 //!
146 //! #[derive(FromArgs, PartialEq, Debug)]
147 //! #[argh(subcommand)]
148 //! enum MySubCommandEnum {
149 //!     One(SubCommandOne),
150 //!     Two(SubCommandTwo),
151 //! }
152 //!
153 //! #[derive(FromArgs, PartialEq, Debug)]
154 //! /// First subcommand.
155 //! #[argh(subcommand, name = "one")]
156 //! struct SubCommandOne {
157 //!     #[argh(option)]
158 //!     /// how many x
159 //!     x: usize,
160 //! }
161 //!
162 //! #[derive(FromArgs, PartialEq, Debug)]
163 //! /// Second subcommand.
164 //! #[argh(subcommand, name = "two")]
165 //! struct SubCommandTwo {
166 //!     #[argh(switch)]
167 //!     /// whether to fooey
168 //!     fooey: bool,
169 //! }
170 //! ```
171 
172 #![deny(missing_docs)]
173 
174 use std::str::FromStr;
175 
176 pub use argh_derive::FromArgs;
177 
178 /// Information about a particular command used for output.
179 pub type CommandInfo = argh_shared::CommandInfo<'static>;
180 
181 /// Types which can be constructed from a set of commandline arguments.
182 pub trait FromArgs: Sized {
183     /// Construct the type from an input set of arguments.
184     ///
185     /// The first argument `command_name` is the identifier for the current command. In most cases,
186     /// users should only pass in a single item for the command name, which typically comes from
187     /// the first item from `std::env::args()`. Implementations however should append the
188     /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This
189     /// allows `argh` to generate correct subcommand help strings.
190     ///
191     /// The second argument `args` is the rest of the command line arguments.
192     ///
193     /// # Examples
194     ///
195     /// ```rust
196     /// # use argh::FromArgs;
197     ///
198     /// /// Command to manage a classroom.
199     /// #[derive(Debug, PartialEq, FromArgs)]
200     /// struct ClassroomCmd {
201     ///     #[argh(subcommand)]
202     ///     subcommands: Subcommands,
203     /// }
204     ///
205     /// #[derive(Debug, PartialEq, FromArgs)]
206     /// #[argh(subcommand)]
207     /// enum Subcommands {
208     ///     List(ListCmd),
209     ///     Add(AddCmd),
210     /// }
211     ///
212     /// /// list all the classes.
213     /// #[derive(Debug, PartialEq, FromArgs)]
214     /// #[argh(subcommand, name = "list")]
215     /// struct ListCmd {
216     ///     /// list classes for only this teacher.
217     ///     #[argh(option)]
218     ///     teacher_name: Option<String>,
219     /// }
220     ///
221     /// /// add students to a class.
222     /// #[derive(Debug, PartialEq, FromArgs)]
223     /// #[argh(subcommand, name = "add")]
224     /// struct AddCmd {
225     ///     /// the name of the class's teacher.
226     ///     #[argh(option)]
227     ///     teacher_name: String,
228     ///
229     ///     /// the name of the class.
230     ///     #[argh(positional)]
231     ///     class_name: String,
232     /// }
233     ///
234     /// let args = ClassroomCmd::from_args(
235     ///     &["classroom"],
236     ///     &["list", "--teacher-name", "Smith"],
237     /// ).unwrap();
238     /// assert_eq!(
239     ///    args,
240     ///     ClassroomCmd {
241     ///         subcommands: Subcommands::List(ListCmd {
242     ///             teacher_name: Some("Smith".to_string()),
243     ///         })
244     ///     },
245     /// );
246     ///
247     /// // Help returns an error, but internally returns an `Ok` status.
248     /// let early_exit = ClassroomCmd::from_args(
249     ///     &["classroom"],
250     ///     &["help"],
251     /// ).unwrap_err();
252     /// assert_eq!(
253     ///     early_exit,
254     ///     argh::EarlyExit {
255     ///        output: r#"Usage: classroom <command> [<args>]
256     ///
257     /// Command to manage a classroom.
258     ///
259     /// Options:
260     ///   --help            display usage information
261     ///
262     /// Commands:
263     ///   list              list all the classes.
264     ///   add               add students to a class.
265     /// "#.to_string(),
266     ///        status: Ok(()),
267     ///     },
268     /// );
269     ///
270     /// // Help works with subcommands.
271     /// let early_exit = ClassroomCmd::from_args(
272     ///     &["classroom"],
273     ///     &["list", "help"],
274     /// ).unwrap_err();
275     /// assert_eq!(
276     ///     early_exit,
277     ///     argh::EarlyExit {
278     ///        output: r#"Usage: classroom list [--teacher-name <teacher-name>]
279     ///
280     /// list all the classes.
281     ///
282     /// Options:
283     ///   --teacher-name    list classes for only this teacher.
284     ///   --help            display usage information
285     /// "#.to_string(),
286     ///        status: Ok(()),
287     ///     },
288     /// );
289     ///
290     /// // Incorrect arguments will error out.
291     /// let err = ClassroomCmd::from_args(
292     ///     &["classroom"],
293     ///     &["lisp"],
294     /// ).unwrap_err();
295     /// assert_eq!(
296     ///    err,
297     ///    argh::EarlyExit {
298     ///        output: "Unrecognized argument: lisp\n".to_string(),
299     ///        status: Err(()),
300     ///     },
301     /// );
302     /// ```
from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit>303     fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit>;
304 
305     /// Get a String with just the argument names, e.g., options, flags, subcommands, etc, but
306     /// without the values of the options and arguments. This can be useful as a means to capture
307     /// anonymous usage statistics without revealing the content entered by the end user.
308     ///
309     /// The first argument `command_name` is the identifier for the current command. In most cases,
310     /// users should only pass in a single item for the command name, which typically comes from
311     /// the first item from `std::env::args()`. Implementations however should append the
312     /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This
313     /// allows `argh` to generate correct subcommand help strings.
314     ///
315     /// The second argument `args` is the rest of the command line arguments.
316     ///
317     /// # Examples
318     ///
319     /// ```rust
320     /// # use argh::FromArgs;
321     ///
322     /// /// Command to manage a classroom.
323     /// #[derive(FromArgs)]
324     /// struct ClassroomCmd {
325     ///     #[argh(subcommand)]
326     ///     subcommands: Subcommands,
327     /// }
328     ///
329     /// #[derive(FromArgs)]
330     /// #[argh(subcommand)]
331     /// enum Subcommands {
332     ///     List(ListCmd),
333     ///     Add(AddCmd),
334     /// }
335     ///
336     /// /// list all the classes.
337     /// #[derive(FromArgs)]
338     /// #[argh(subcommand, name = "list")]
339     /// struct ListCmd {
340     ///     /// list classes for only this teacher.
341     ///     #[argh(option)]
342     ///     teacher_name: Option<String>,
343     /// }
344     ///
345     /// /// add students to a class.
346     /// #[derive(FromArgs)]
347     /// #[argh(subcommand, name = "add")]
348     /// struct AddCmd {
349     ///     /// the name of the class's teacher.
350     ///     #[argh(option)]
351     ///     teacher_name: String,
352     ///
353     ///     /// has the class started yet?
354     ///     #[argh(switch)]
355     ///     started: bool,
356     ///
357     ///     /// the name of the class.
358     ///     #[argh(positional)]
359     ///     class_name: String,
360     ///
361     ///     /// the student names.
362     ///     #[argh(positional)]
363     ///     students: Vec<String>,
364     /// }
365     ///
366     /// let args = ClassroomCmd::redact_arg_values(
367     ///     &["classroom"],
368     ///     &["list"],
369     /// ).unwrap();
370     /// assert_eq!(
371     ///     args,
372     ///     &[
373     ///         "classroom",
374     ///         "list",
375     ///     ],
376     /// );
377     ///
378     /// let args = ClassroomCmd::redact_arg_values(
379     ///     &["classroom"],
380     ///     &["list", "--teacher-name", "Smith"],
381     /// ).unwrap();
382     /// assert_eq!(
383     ///    args,
384     ///    &[
385     ///         "classroom",
386     ///         "list",
387     ///         "--teacher-name",
388     ///     ],
389     /// );
390     ///
391     /// let args = ClassroomCmd::redact_arg_values(
392     ///     &["classroom"],
393     ///     &["add", "--teacher-name", "Smith", "--started", "Math", "Abe", "Sung"],
394     /// ).unwrap();
395     /// assert_eq!(
396     ///     args,
397     ///     &[
398     ///         "classroom",
399     ///         "add",
400     ///         "--teacher-name",
401     ///         "--started",
402     ///         "class_name",
403     ///         "students",
404     ///         "students",
405     ///     ],
406     /// );
407     ///
408     /// // `ClassroomCmd::redact_arg_values` will error out if passed invalid arguments.
409     /// assert_eq!(
410     ///     ClassroomCmd::redact_arg_values(&["classroom"], &["add", "--teacher-name"]),
411     ///     Err(argh::EarlyExit {
412     ///         output: "No value provided for option '--teacher-name'.\n".into(),
413     ///         status: Err(()),
414     ///     }),
415     /// );
416     ///
417     /// // `ClassroomCmd::redact_arg_values` will generate help messages.
418     /// assert_eq!(
419     ///     ClassroomCmd::redact_arg_values(&["classroom"], &["help"]),
420     ///     Err(argh::EarlyExit {
421     ///         output: r#"Usage: classroom <command> [<args>]
422     ///
423     /// Command to manage a classroom.
424     ///
425     /// Options:
426     ///   --help            display usage information
427     ///
428     /// Commands:
429     ///   list              list all the classes.
430     ///   add               add students to a class.
431     /// "#.to_string(),
432     ///         status: Ok(()),
433     ///     }),
434     /// );
435     /// ```
redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result<Vec<String>, EarlyExit>436     fn redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result<Vec<String>, EarlyExit> {
437         Ok(vec!["<<REDACTED>>".into()])
438     }
439 }
440 
441 /// A top-level `FromArgs` implementation that is not a subcommand.
442 pub trait TopLevelCommand: FromArgs {}
443 
444 /// A `FromArgs` implementation that can parse into one or more subcommands.
445 pub trait SubCommands: FromArgs {
446     /// Info for the commands.
447     const COMMANDS: &'static [&'static CommandInfo];
448 }
449 
450 /// A `FromArgs` implementation that represents a single subcommand.
451 pub trait SubCommand: FromArgs {
452     /// Information about the subcommand.
453     const COMMAND: &'static CommandInfo;
454 }
455 
456 impl<T: SubCommand> SubCommands for T {
457     const COMMANDS: &'static [&'static CommandInfo] = &[T::COMMAND];
458 }
459 
460 /// Information to display to the user about why a `FromArgs` construction exited early.
461 ///
462 /// This can occur due to either failed parsing or a flag like `--help`.
463 #[derive(Debug, Clone, PartialEq, Eq)]
464 pub struct EarlyExit {
465     /// The output to display to the user of the commandline tool.
466     pub output: String,
467     /// Status of argument parsing.
468     ///
469     /// `Ok` if the command was parsed successfully and the early exit is due
470     /// to a flag like `--help` causing early exit with output.
471     ///
472     /// `Err` if the arguments were not successfully parsed.
473     // TODO replace with std::process::ExitCode when stable.
474     pub status: Result<(), ()>,
475 }
476 
477 impl From<String> for EarlyExit {
from(err_msg: String) -> Self478     fn from(err_msg: String) -> Self {
479         Self { output: err_msg, status: Err(()) }
480     }
481 }
482 
483 /// Extract the base cmd from a path
cmd<'a>(default: &'a String, path: &'a String) -> &'a str484 fn cmd<'a>(default: &'a String, path: &'a String) -> &'a str {
485     std::path::Path::new(path).file_name().map(|s| s.to_str()).flatten().unwrap_or(default.as_str())
486 }
487 
488 /// Create a `FromArgs` type from the current process's `env::args`.
489 ///
490 /// This function will exit early from the current process if argument parsing
491 /// was unsuccessful or if information like `--help` was requested. Error messages will be printed
492 /// to stderr, and `--help` output to stdout.
from_env<T: TopLevelCommand>() -> T493 pub fn from_env<T: TopLevelCommand>() -> T {
494     let strings: Vec<String> = std::env::args().collect();
495     let cmd = cmd(&strings[0], &strings[0]);
496     let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
497     T::from_args(&[cmd], &strs[1..]).unwrap_or_else(|early_exit| {
498         std::process::exit(match early_exit.status {
499             Ok(()) => {
500                 println!("{}", early_exit.output);
501                 0
502             }
503             Err(()) => {
504                 eprintln!("{}", early_exit.output);
505                 1
506             }
507         })
508     })
509 }
510 
511 /// Create a `FromArgs` type from the current process's `env::args`.
512 ///
513 /// This special cases usages where argh is being used in an environment where cargo is
514 /// driving the build. We skip the second env variable.
515 ///
516 /// This function will exit early from the current process if argument parsing
517 /// was unsuccessful or if information like `--help` was requested. Error messages will be printed
518 /// to stderr, and `--help` output to stdout.
cargo_from_env<T: TopLevelCommand>() -> T519 pub fn cargo_from_env<T: TopLevelCommand>() -> T {
520     let strings: Vec<String> = std::env::args().collect();
521     let cmd = cmd(&strings[1], &strings[1]);
522     let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
523     T::from_args(&[cmd], &strs[2..]).unwrap_or_else(|early_exit| {
524         std::process::exit(match early_exit.status {
525             Ok(()) => {
526                 println!("{}", early_exit.output);
527                 0
528             }
529             Err(()) => {
530                 eprintln!("{}", early_exit.output);
531                 1
532             }
533         })
534     })
535 }
536 
537 /// Types which can be constructed from a single commandline value.
538 ///
539 /// Any field type declared in a struct that derives `FromArgs` must implement
540 /// this trait. A blanket implementation exists for types implementing
541 /// `FromStr<Error: Display>`. Custom types can implement this trait
542 /// directly.
543 pub trait FromArgValue: Sized {
544     /// Construct the type from a commandline value, returning an error string
545     /// on failure.
from_arg_value(value: &str) -> Result<Self, String>546     fn from_arg_value(value: &str) -> Result<Self, String>;
547 }
548 
549 impl<T> FromArgValue for T
550 where
551     T: FromStr,
552     T::Err: std::fmt::Display,
553 {
from_arg_value(value: &str) -> Result<Self, String>554     fn from_arg_value(value: &str) -> Result<Self, String> {
555         T::from_str(value).map_err(|x| x.to_string())
556     }
557 }
558 
559 // The following items are all used by the generated code, and should not be considered part
560 // of this library's public API surface.
561 
562 #[doc(hidden)]
563 pub trait ParseFlag {
set_flag(&mut self, arg: &str)564     fn set_flag(&mut self, arg: &str);
565 }
566 
567 impl<T: Flag> ParseFlag for T {
set_flag(&mut self, _arg: &str)568     fn set_flag(&mut self, _arg: &str) {
569         <T as Flag>::set_flag(self);
570     }
571 }
572 
573 #[doc(hidden)]
574 pub struct RedactFlag {
575     pub slot: Option<String>,
576 }
577 
578 impl ParseFlag for RedactFlag {
set_flag(&mut self, arg: &str)579     fn set_flag(&mut self, arg: &str) {
580         self.slot = Some(arg.to_string());
581     }
582 }
583 
584 // A trait for for slots that reserve space for a value and know how to parse that value
585 // from a command-line `&str` argument.
586 //
587 // This trait is only implemented for the type `ParseValueSlotTy`. This indirection is
588 // necessary to allow abstracting over `ParseValueSlotTy` instances with different
589 // generic parameters.
590 #[doc(hidden)]
591 pub trait ParseValueSlot {
fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>592     fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>;
593 }
594 
595 // The concrete type implementing the `ParseValueSlot` trait.
596 //
597 // `T` is the type to be parsed from a single string.
598 // `Slot` is the type of the container that can hold a value or values of type `T`.
599 #[doc(hidden)]
600 pub struct ParseValueSlotTy<Slot, T> {
601     // The slot for a parsed value.
602     pub slot: Slot,
603     // The function to parse the value from a string
604     pub parse_func: fn(&str, &str) -> Result<T, String>,
605 }
606 
607 // `ParseValueSlotTy<Option<T>, T>` is used as the slot for all non-repeating
608 // arguments, both optional and required.
609 impl<T> ParseValueSlot for ParseValueSlotTy<Option<T>, T> {
fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>610     fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {
611         if self.slot.is_some() {
612             return Err("duplicate values provided".to_string());
613         }
614         self.slot = Some((self.parse_func)(arg, value)?);
615         Ok(())
616     }
617 }
618 
619 // `ParseValueSlotTy<Vec<T>, T>` is used as the slot for repeating arguments.
620 impl<T> ParseValueSlot for ParseValueSlotTy<Vec<T>, T> {
fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>621     fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {
622         self.slot.push((self.parse_func)(arg, value)?);
623         Ok(())
624     }
625 }
626 
627 /// A type which can be the receiver of a `Flag`.
628 pub trait Flag {
629     /// Creates a default instance of the flag value;
default() -> Self where Self: Sized630     fn default() -> Self
631     where
632         Self: Sized;
633 
634     /// Sets the flag. This function is called when the flag is provided.
set_flag(&mut self)635     fn set_flag(&mut self);
636 }
637 
638 impl Flag for bool {
default() -> Self639     fn default() -> Self {
640         false
641     }
set_flag(&mut self)642     fn set_flag(&mut self) {
643         *self = true;
644     }
645 }
646 
647 macro_rules! impl_flag_for_integers {
648     ($($ty:ty,)*) => {
649         $(
650             impl Flag for $ty {
651                 fn default() -> Self {
652                     0
653                 }
654                 fn set_flag(&mut self) {
655                     *self = self.saturating_add(1);
656                 }
657             }
658         )*
659     }
660 }
661 
662 impl_flag_for_integers![u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,];
663 
664 /// This function implements argument parsing for structs.
665 ///
666 /// `cmd_name`: The identifier for the current command.
667 /// `args`: The command line arguments.
668 /// `parse_options`: Helper to parse optional arguments.
669 /// `parse_positionals`: Helper to parse positional arguments.
670 /// `parse_subcommand`: Helper to parse a subcommand.
671 /// `help_func`: Generate a help message.
672 #[doc(hidden)]
parse_struct_args( cmd_name: &[&str], args: &[&str], mut parse_options: ParseStructOptions<'_>, mut parse_positionals: ParseStructPositionals<'_>, mut parse_subcommand: Option<ParseStructSubCommand<'_>>, help_func: &dyn Fn() -> String, ) -> Result<(), EarlyExit>673 pub fn parse_struct_args(
674     cmd_name: &[&str],
675     args: &[&str],
676     mut parse_options: ParseStructOptions<'_>,
677     mut parse_positionals: ParseStructPositionals<'_>,
678     mut parse_subcommand: Option<ParseStructSubCommand<'_>>,
679     help_func: &dyn Fn() -> String,
680 ) -> Result<(), EarlyExit> {
681     let mut help = false;
682     let mut remaining_args = args;
683     let mut positional_index = 0;
684     let mut options_ended = false;
685 
686     'parse_args: while let Some(&next_arg) = remaining_args.get(0) {
687         remaining_args = &remaining_args[1..];
688         if (next_arg == "--help" || next_arg == "help") && !options_ended {
689             help = true;
690             continue;
691         }
692 
693         if next_arg.starts_with("-") && !options_ended {
694             if next_arg == "--" {
695                 options_ended = true;
696                 continue;
697             }
698 
699             if help {
700                 return Err("Trailing arguments are not allowed after `help`.".to_string().into());
701             }
702 
703             parse_options.parse(next_arg, &mut remaining_args)?;
704             continue;
705         }
706 
707         if let Some(ref mut parse_subcommand) = parse_subcommand {
708             if parse_subcommand.parse(help, cmd_name, next_arg, remaining_args)? {
709                 // Unset `help`, since we handled it in the subcommand
710                 help = false;
711                 break 'parse_args;
712             }
713         }
714 
715         parse_positionals.parse(&mut positional_index, next_arg)?;
716     }
717 
718     if help {
719         Err(EarlyExit { output: help_func(), status: Ok(()) })
720     } else {
721         Ok(())
722     }
723 }
724 
725 #[doc(hidden)]
726 pub struct ParseStructOptions<'a> {
727     /// A mapping from option string literals to the entry
728     /// in the output table. This may contain multiple entries mapping to
729     /// the same location in the table if both a short and long version
730     /// of the option exist (`-z` and `--zoo`).
731     pub arg_to_slot: &'static [(&'static str, usize)],
732 
733     /// The storage for argument output data.
734     pub slots: &'a mut [ParseStructOption<'a>],
735 }
736 
737 impl<'a> ParseStructOptions<'a> {
738     /// Parse a commandline option.
739     ///
740     /// `arg`: the current option argument being parsed (e.g. `--foo`).
741     /// `remaining_args`: the remaining command line arguments. This slice
742     /// will be advanced forwards if the option takes a value argument.
parse(&mut self, arg: &str, remaining_args: &mut &[&str]) -> Result<(), String>743     fn parse(&mut self, arg: &str, remaining_args: &mut &[&str]) -> Result<(), String> {
744         let pos = self
745             .arg_to_slot
746             .iter()
747             .find_map(|&(name, pos)| if name == arg { Some(pos) } else { None })
748             .ok_or_else(|| unrecognized_argument(arg))?;
749 
750         match self.slots[pos] {
751             ParseStructOption::Flag(ref mut b) => b.set_flag(arg),
752             ParseStructOption::Value(ref mut pvs) => {
753                 let value = remaining_args
754                     .get(0)
755                     .ok_or_else(|| ["No value provided for option '", arg, "'.\n"].concat())?;
756                 *remaining_args = &remaining_args[1..];
757                 pvs.fill_slot(arg, value).map_err(|s| {
758                     ["Error parsing option '", arg, "' with value '", value, "': ", &s, "\n"]
759                         .concat()
760                 })?;
761             }
762         }
763 
764         Ok(())
765     }
766 }
767 
unrecognized_argument(x: &str) -> String768 fn unrecognized_argument(x: &str) -> String {
769     ["Unrecognized argument: ", x, "\n"].concat()
770 }
771 
772 // `--` or `-` options, including a mutable reference to their value.
773 #[doc(hidden)]
774 pub enum ParseStructOption<'a> {
775     // A flag which is set to `true` when provided.
776     Flag(&'a mut dyn ParseFlag),
777     // A value which is parsed from the string following the `--` argument,
778     // e.g. `--foo bar`.
779     Value(&'a mut dyn ParseValueSlot),
780 }
781 
782 #[doc(hidden)]
783 pub struct ParseStructPositionals<'a> {
784     pub positionals: &'a mut [ParseStructPositional<'a>],
785     pub last_is_repeating: bool,
786 }
787 
788 impl<'a> ParseStructPositionals<'a> {
789     /// Parse the next positional argument.
790     ///
791     /// `arg`: the argument supplied by the user.
parse(&mut self, index: &mut usize, arg: &str) -> Result<(), EarlyExit>792     fn parse(&mut self, index: &mut usize, arg: &str) -> Result<(), EarlyExit> {
793         if *index < self.positionals.len() {
794             self.positionals[*index].parse(arg)?;
795 
796             // Don't increment position if we're at the last arg
797             // *and* the last arg is repeating.
798             let skip_increment = self.last_is_repeating && *index == self.positionals.len() - 1;
799 
800             if !skip_increment {
801                 *index += 1;
802             }
803 
804             Ok(())
805         } else {
806             Err(EarlyExit { output: unrecognized_arg(arg), status: Err(()) })
807         }
808     }
809 }
810 
811 #[doc(hidden)]
812 pub struct ParseStructPositional<'a> {
813     // The positional's name
814     pub name: &'static str,
815 
816     // The function to parse the positional.
817     pub slot: &'a mut dyn ParseValueSlot,
818 }
819 
820 impl<'a> ParseStructPositional<'a> {
821     /// Parse a positional argument.
822     ///
823     /// `arg`: the argument supplied by the user.
parse(&mut self, arg: &str) -> Result<(), EarlyExit>824     fn parse(&mut self, arg: &str) -> Result<(), EarlyExit> {
825         self.slot.fill_slot("", arg).map_err(|s| {
826             [
827                 "Error parsing positional argument '",
828                 self.name,
829                 "' with value '",
830                 arg,
831                 "': ",
832                 &s,
833                 "\n",
834             ]
835             .concat()
836             .into()
837         })
838     }
839 }
840 
841 // A type to simplify parsing struct subcommands.
842 //
843 // This indirection is necessary to allow abstracting over `FromArgs` instances with different
844 // generic parameters.
845 #[doc(hidden)]
846 pub struct ParseStructSubCommand<'a> {
847     // The subcommand commands
848     pub subcommands: &'static [&'static CommandInfo],
849 
850     // The function to parse the subcommand arguments.
851     pub parse_func: &'a mut dyn FnMut(&[&str], &[&str]) -> Result<(), EarlyExit>,
852 }
853 
854 impl<'a> ParseStructSubCommand<'a> {
parse( &mut self, help: bool, cmd_name: &[&str], arg: &str, remaining_args: &[&str], ) -> Result<bool, EarlyExit>855     fn parse(
856         &mut self,
857         help: bool,
858         cmd_name: &[&str],
859         arg: &str,
860         remaining_args: &[&str],
861     ) -> Result<bool, EarlyExit> {
862         for subcommand in self.subcommands {
863             if subcommand.name == arg {
864                 let mut command = cmd_name.to_owned();
865                 command.push(subcommand.name);
866                 let prepended_help;
867                 let remaining_args = if help {
868                     prepended_help = prepend_help(remaining_args);
869                     &prepended_help
870                 } else {
871                     remaining_args
872                 };
873 
874                 (self.parse_func)(&command, remaining_args)?;
875 
876                 return Ok(true);
877             }
878         }
879 
880         return Ok(false);
881     }
882 }
883 
884 // Prepend `help` to a list of arguments.
885 // This is used to pass the `help` argument on to subcommands.
prepend_help<'a>(args: &[&'a str]) -> Vec<&'a str>886 fn prepend_help<'a>(args: &[&'a str]) -> Vec<&'a str> {
887     [&["help"], args].concat()
888 }
889 
890 #[doc(hidden)]
print_subcommands(commands: &[&CommandInfo]) -> String891 pub fn print_subcommands(commands: &[&CommandInfo]) -> String {
892     let mut out = String::new();
893     for cmd in commands {
894         argh_shared::write_description(&mut out, cmd);
895     }
896     out
897 }
898 
unrecognized_arg(arg: &str) -> String899 fn unrecognized_arg(arg: &str) -> String {
900     ["Unrecognized argument: ", arg, "\n"].concat()
901 }
902 
903 // An error string builder to report missing required options and subcommands.
904 #[doc(hidden)]
905 #[derive(Default)]
906 pub struct MissingRequirements {
907     options: Vec<&'static str>,
908     subcommands: Option<&'static [&'static CommandInfo]>,
909     positional_args: Vec<&'static str>,
910 }
911 
912 const NEWLINE_INDENT: &str = "\n    ";
913 
914 impl MissingRequirements {
915     // Add a missing required option.
916     #[doc(hidden)]
missing_option(&mut self, name: &'static str)917     pub fn missing_option(&mut self, name: &'static str) {
918         self.options.push(name)
919     }
920 
921     // Add a missing required subcommand.
922     #[doc(hidden)]
missing_subcommands(&mut self, commands: &'static [&'static CommandInfo])923     pub fn missing_subcommands(&mut self, commands: &'static [&'static CommandInfo]) {
924         self.subcommands = Some(commands);
925     }
926 
927     // Add a missing positional argument.
928     #[doc(hidden)]
missing_positional_arg(&mut self, name: &'static str)929     pub fn missing_positional_arg(&mut self, name: &'static str) {
930         self.positional_args.push(name)
931     }
932 
933     // If any missing options or subcommands were provided, returns an error string
934     // describing the missing args.
935     #[doc(hidden)]
err_on_any(&self) -> Result<(), String>936     pub fn err_on_any(&self) -> Result<(), String> {
937         if self.options.is_empty() && self.subcommands.is_none() && self.positional_args.is_empty()
938         {
939             return Ok(());
940         }
941 
942         let mut output = String::new();
943 
944         if !self.positional_args.is_empty() {
945             output.push_str("Required positional arguments not provided:");
946             for arg in &self.positional_args {
947                 output.push_str(NEWLINE_INDENT);
948                 output.push_str(arg);
949             }
950         }
951 
952         if !self.options.is_empty() {
953             if !self.positional_args.is_empty() {
954                 output.push_str("\n");
955             }
956             output.push_str("Required options not provided:");
957             for option in &self.options {
958                 output.push_str(NEWLINE_INDENT);
959                 output.push_str(option);
960             }
961         }
962 
963         if let Some(missing_subcommands) = self.subcommands {
964             if !self.options.is_empty() {
965                 output.push_str("\n");
966             }
967             output.push_str("One of the following subcommands must be present:");
968             output.push_str(NEWLINE_INDENT);
969             output.push_str("help");
970             for subcommand in missing_subcommands {
971                 output.push_str(NEWLINE_INDENT);
972                 output.push_str(subcommand.name);
973             }
974         }
975 
976         output.push('\n');
977 
978         Err(output)
979     }
980 }
981 
982 #[cfg(test)]
983 mod test {
984     use super::*;
985 
986     #[test]
test_cmd_extraction()987     fn test_cmd_extraction() {
988         let expected = "test_cmd";
989         let path = format!("/tmp/{}", expected);
990         let cmd = cmd(&path, &path);
991         assert_eq!(expected, cmd);
992     }
993 }
994