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