• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::FromArgs;
322 
323 /// Information about a particular command used for output.
324 pub type CommandInfo = argh_shared::CommandInfo<'static>;
325 
326 /// Types which can be constructed from a set of commandline arguments.
327 pub trait FromArgs: Sized {
328     /// Construct the type from an input set of arguments.
329     ///
330     /// The first argument `command_name` is the identifier for the current command. In most cases,
331     /// users should only pass in a single item for the command name, which typically comes from
332     /// the first item from `std::env::args()`. Implementations however should append the
333     /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This
334     /// allows `argh` to generate correct subcommand help strings.
335     ///
336     /// The second argument `args` is the rest of the command line arguments.
337     ///
338     /// # Examples
339     ///
340     /// ```rust
341     /// # use argh::FromArgs;
342     ///
343     /// /// Command to manage a classroom.
344     /// #[derive(Debug, PartialEq, FromArgs)]
345     /// struct ClassroomCmd {
346     ///     #[argh(subcommand)]
347     ///     subcommands: Subcommands,
348     /// }
349     ///
350     /// #[derive(Debug, PartialEq, FromArgs)]
351     /// #[argh(subcommand)]
352     /// enum Subcommands {
353     ///     List(ListCmd),
354     ///     Add(AddCmd),
355     /// }
356     ///
357     /// /// list all the classes.
358     /// #[derive(Debug, PartialEq, FromArgs)]
359     /// #[argh(subcommand, name = "list")]
360     /// struct ListCmd {
361     ///     /// list classes for only this teacher.
362     ///     #[argh(option)]
363     ///     teacher_name: Option<String>,
364     /// }
365     ///
366     /// /// add students to a class.
367     /// #[derive(Debug, PartialEq, FromArgs)]
368     /// #[argh(subcommand, name = "add")]
369     /// struct AddCmd {
370     ///     /// the name of the class's teacher.
371     ///     #[argh(option)]
372     ///     teacher_name: String,
373     ///
374     ///     /// the name of the class.
375     ///     #[argh(positional)]
376     ///     class_name: String,
377     /// }
378     ///
379     /// let args = ClassroomCmd::from_args(
380     ///     &["classroom"],
381     ///     &["list", "--teacher-name", "Smith"],
382     /// ).unwrap();
383     /// assert_eq!(
384     ///    args,
385     ///     ClassroomCmd {
386     ///         subcommands: Subcommands::List(ListCmd {
387     ///             teacher_name: Some("Smith".to_string()),
388     ///         })
389     ///     },
390     /// );
391     ///
392     /// // Help returns an error, but internally returns an `Ok` status.
393     /// let early_exit = ClassroomCmd::from_args(
394     ///     &["classroom"],
395     ///     &["help"],
396     /// ).unwrap_err();
397     /// assert_eq!(
398     ///     early_exit,
399     ///     argh::EarlyExit {
400     ///        output: r#"Usage: classroom <command> [<args>]
401     ///
402     /// Command to manage a classroom.
403     ///
404     /// Options:
405     ///   --help            display usage information
406     ///
407     /// Commands:
408     ///   list              list all the classes.
409     ///   add               add students to a class.
410     /// "#.to_string(),
411     ///        status: Ok(()),
412     ///     },
413     /// );
414     ///
415     /// // Help works with subcommands.
416     /// let early_exit = ClassroomCmd::from_args(
417     ///     &["classroom"],
418     ///     &["list", "help"],
419     /// ).unwrap_err();
420     /// assert_eq!(
421     ///     early_exit,
422     ///     argh::EarlyExit {
423     ///        output: r#"Usage: classroom list [--teacher-name <teacher-name>]
424     ///
425     /// list all the classes.
426     ///
427     /// Options:
428     ///   --teacher-name    list classes for only this teacher.
429     ///   --help            display usage information
430     /// "#.to_string(),
431     ///        status: Ok(()),
432     ///     },
433     /// );
434     ///
435     /// // Incorrect arguments will error out.
436     /// let err = ClassroomCmd::from_args(
437     ///     &["classroom"],
438     ///     &["lisp"],
439     /// ).unwrap_err();
440     /// assert_eq!(
441     ///    err,
442     ///    argh::EarlyExit {
443     ///        output: "Unrecognized argument: lisp\n".to_string(),
444     ///        status: Err(()),
445     ///     },
446     /// );
447     /// ```
from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit>448     fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit>;
449 
450     /// Get a String with just the argument names, e.g., options, flags, subcommands, etc, but
451     /// without the values of the options and arguments. This can be useful as a means to capture
452     /// anonymous usage statistics without revealing the content entered by the end user.
453     ///
454     /// The first argument `command_name` is the identifier for the current command. In most cases,
455     /// users should only pass in a single item for the command name, which typically comes from
456     /// the first item from `std::env::args()`. Implementations however should append the
457     /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This
458     /// allows `argh` to generate correct subcommand help strings.
459     ///
460     /// The second argument `args` is the rest of the command line arguments.
461     ///
462     /// # Examples
463     ///
464     /// ```rust
465     /// # use argh::FromArgs;
466     ///
467     /// /// Command to manage a classroom.
468     /// #[derive(FromArgs)]
469     /// struct ClassroomCmd {
470     ///     #[argh(subcommand)]
471     ///     subcommands: Subcommands,
472     /// }
473     ///
474     /// #[derive(FromArgs)]
475     /// #[argh(subcommand)]
476     /// enum Subcommands {
477     ///     List(ListCmd),
478     ///     Add(AddCmd),
479     /// }
480     ///
481     /// /// list all the classes.
482     /// #[derive(FromArgs)]
483     /// #[argh(subcommand, name = "list")]
484     /// struct ListCmd {
485     ///     /// list classes for only this teacher.
486     ///     #[argh(option)]
487     ///     teacher_name: Option<String>,
488     /// }
489     ///
490     /// /// add students to a class.
491     /// #[derive(FromArgs)]
492     /// #[argh(subcommand, name = "add")]
493     /// struct AddCmd {
494     ///     /// the name of the class's teacher.
495     ///     #[argh(option)]
496     ///     teacher_name: String,
497     ///
498     ///     /// has the class started yet?
499     ///     #[argh(switch)]
500     ///     started: bool,
501     ///
502     ///     /// the name of the class.
503     ///     #[argh(positional)]
504     ///     class_name: String,
505     ///
506     ///     /// the student names.
507     ///     #[argh(positional)]
508     ///     students: Vec<String>,
509     /// }
510     ///
511     /// let args = ClassroomCmd::redact_arg_values(
512     ///     &["classroom"],
513     ///     &["list"],
514     /// ).unwrap();
515     /// assert_eq!(
516     ///     args,
517     ///     &[
518     ///         "classroom",
519     ///         "list",
520     ///     ],
521     /// );
522     ///
523     /// let args = ClassroomCmd::redact_arg_values(
524     ///     &["classroom"],
525     ///     &["list", "--teacher-name", "Smith"],
526     /// ).unwrap();
527     /// assert_eq!(
528     ///    args,
529     ///    &[
530     ///         "classroom",
531     ///         "list",
532     ///         "--teacher-name",
533     ///     ],
534     /// );
535     ///
536     /// let args = ClassroomCmd::redact_arg_values(
537     ///     &["classroom"],
538     ///     &["add", "--teacher-name", "Smith", "--started", "Math", "Abe", "Sung"],
539     /// ).unwrap();
540     /// assert_eq!(
541     ///     args,
542     ///     &[
543     ///         "classroom",
544     ///         "add",
545     ///         "--teacher-name",
546     ///         "--started",
547     ///         "class_name",
548     ///         "students",
549     ///         "students",
550     ///     ],
551     /// );
552     ///
553     /// // `ClassroomCmd::redact_arg_values` will error out if passed invalid arguments.
554     /// assert_eq!(
555     ///     ClassroomCmd::redact_arg_values(&["classroom"], &["add", "--teacher-name"]),
556     ///     Err(argh::EarlyExit {
557     ///         output: "No value provided for option '--teacher-name'.\n".into(),
558     ///         status: Err(()),
559     ///     }),
560     /// );
561     ///
562     /// // `ClassroomCmd::redact_arg_values` will generate help messages.
563     /// assert_eq!(
564     ///     ClassroomCmd::redact_arg_values(&["classroom"], &["help"]),
565     ///     Err(argh::EarlyExit {
566     ///         output: r#"Usage: classroom <command> [<args>]
567     ///
568     /// Command to manage a classroom.
569     ///
570     /// Options:
571     ///   --help            display usage information
572     ///
573     /// Commands:
574     ///   list              list all the classes.
575     ///   add               add students to a class.
576     /// "#.to_string(),
577     ///         status: Ok(()),
578     ///     }),
579     /// );
580     /// ```
redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result<Vec<String>, EarlyExit>581     fn redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result<Vec<String>, EarlyExit> {
582         Ok(vec!["<<REDACTED>>".into()])
583     }
584 }
585 
586 /// A top-level `FromArgs` implementation that is not a subcommand.
587 pub trait TopLevelCommand: FromArgs {}
588 
589 /// A `FromArgs` implementation that can parse into one or more subcommands.
590 pub trait SubCommands: FromArgs {
591     /// Info for the commands.
592     const COMMANDS: &'static [&'static CommandInfo];
593 
594     /// Get a list of commands that are discovered at runtime.
dynamic_commands() -> &'static [&'static CommandInfo]595     fn dynamic_commands() -> &'static [&'static CommandInfo] {
596         &[]
597     }
598 }
599 
600 /// A `FromArgs` implementation that represents a single subcommand.
601 pub trait SubCommand: FromArgs {
602     /// Information about the subcommand.
603     const COMMAND: &'static CommandInfo;
604 }
605 
606 impl<T: SubCommand> SubCommands for T {
607     const COMMANDS: &'static [&'static CommandInfo] = &[T::COMMAND];
608 }
609 
610 /// Trait implemented by values returned from a dynamic subcommand handler.
611 pub trait DynamicSubCommand: Sized {
612     /// Info about supported subcommands.
commands() -> &'static [&'static CommandInfo]613     fn commands() -> &'static [&'static CommandInfo];
614 
615     /// Perform the function of `FromArgs::redact_arg_values` for this dynamic
616     /// command.
617     ///
618     /// The full list of subcommands, ending with the subcommand that should be
619     /// dynamically recognized, is passed in `command_name`. If the command
620     /// passed is not recognized, this function should return `None`. Otherwise
621     /// it should return `Some`, and the value within the `Some` has the same
622     /// semantics as the return of `FromArgs::redact_arg_values`.
try_redact_arg_values( command_name: &[&str], args: &[&str], ) -> Option<Result<Vec<String>, EarlyExit>>623     fn try_redact_arg_values(
624         command_name: &[&str],
625         args: &[&str],
626     ) -> Option<Result<Vec<String>, EarlyExit>>;
627 
628     /// Perform the function of `FromArgs::from_args` for this dynamic command.
629     ///
630     /// The full list of subcommands, ending with the subcommand that should be
631     /// dynamically recognized, is passed in `command_name`. If the command
632     /// passed is not recognized, this function should return `None`. Otherwise
633     /// it should return `Some`, and the value within the `Some` has the same
634     /// semantics as the return of `FromArgs::from_args`.
try_from_args(command_name: &[&str], args: &[&str]) -> Option<Result<Self, EarlyExit>>635     fn try_from_args(command_name: &[&str], args: &[&str]) -> Option<Result<Self, EarlyExit>>;
636 }
637 
638 /// Information to display to the user about why a `FromArgs` construction exited early.
639 ///
640 /// This can occur due to either failed parsing or a flag like `--help`.
641 #[derive(Debug, Clone, PartialEq, Eq)]
642 pub struct EarlyExit {
643     /// The output to display to the user of the commandline tool.
644     pub output: String,
645     /// Status of argument parsing.
646     ///
647     /// `Ok` if the command was parsed successfully and the early exit is due
648     /// to a flag like `--help` causing early exit with output.
649     ///
650     /// `Err` if the arguments were not successfully parsed.
651     // TODO replace with std::process::ExitCode when stable.
652     pub status: Result<(), ()>,
653 }
654 
655 impl From<String> for EarlyExit {
from(err_msg: String) -> Self656     fn from(err_msg: String) -> Self {
657         Self { output: err_msg, status: Err(()) }
658     }
659 }
660 
661 /// Extract the base cmd from a path
cmd<'a>(default: &'a str, path: &'a str) -> &'a str662 fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str {
663     std::path::Path::new(path).file_name().and_then(|s| s.to_str()).unwrap_or(default)
664 }
665 
666 /// Create a `FromArgs` type from the current process's `env::args`.
667 ///
668 /// This function will exit early from the current process if argument parsing
669 /// was unsuccessful or if information like `--help` was requested. Error messages will be printed
670 /// to stderr, and `--help` output to stdout.
from_env<T: TopLevelCommand>() -> T671 pub fn from_env<T: TopLevelCommand>() -> T {
672     let strings: Vec<String> = std::env::args_os()
673         .map(|s| s.into_string())
674         .collect::<Result<Vec<_>, _>>()
675         .unwrap_or_else(|arg| {
676             eprintln!("Invalid utf8: {}", arg.to_string_lossy());
677             std::process::exit(1)
678         });
679 
680     if strings.is_empty() {
681         eprintln!("No program name, argv is empty");
682         std::process::exit(1)
683     }
684 
685     let cmd = cmd(&strings[0], &strings[0]);
686     let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
687     T::from_args(&[cmd], &strs[1..]).unwrap_or_else(|early_exit| {
688         std::process::exit(match early_exit.status {
689             Ok(()) => {
690                 println!("{}", early_exit.output);
691                 0
692             }
693             Err(()) => {
694                 eprintln!("{}\nRun {} --help for more information.", early_exit.output, cmd);
695                 1
696             }
697         })
698     })
699 }
700 
701 /// Create a `FromArgs` type from the current process's `env::args`.
702 ///
703 /// This special cases usages where argh is being used in an environment where cargo is
704 /// driving the build. We skip the second env variable.
705 ///
706 /// This function will exit early from the current process if argument parsing
707 /// was unsuccessful or if information like `--help` was requested. Error messages will be printed
708 /// to stderr, and `--help` output to stdout.
cargo_from_env<T: TopLevelCommand>() -> T709 pub fn cargo_from_env<T: TopLevelCommand>() -> T {
710     let strings: Vec<String> = std::env::args().collect();
711     let cmd = cmd(&strings[1], &strings[1]);
712     let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
713     T::from_args(&[cmd], &strs[2..]).unwrap_or_else(|early_exit| {
714         std::process::exit(match early_exit.status {
715             Ok(()) => {
716                 println!("{}", early_exit.output);
717                 0
718             }
719             Err(()) => {
720                 eprintln!("{}\nRun --help for more information.", early_exit.output);
721                 1
722             }
723         })
724     })
725 }
726 
727 /// Types which can be constructed from a single commandline value.
728 ///
729 /// Any field type declared in a struct that derives `FromArgs` must implement
730 /// this trait. A blanket implementation exists for types implementing
731 /// `FromStr<Error: Display>`. Custom types can implement this trait
732 /// directly.
733 pub trait FromArgValue: Sized {
734     /// Construct the type from a commandline value, returning an error string
735     /// on failure.
from_arg_value(value: &str) -> Result<Self, String>736     fn from_arg_value(value: &str) -> Result<Self, String>;
737 }
738 
739 impl<T> FromArgValue for T
740 where
741     T: FromStr,
742     T::Err: std::fmt::Display,
743 {
from_arg_value(value: &str) -> Result<Self, String>744     fn from_arg_value(value: &str) -> Result<Self, String> {
745         T::from_str(value).map_err(|x| x.to_string())
746     }
747 }
748 
749 // The following items are all used by the generated code, and should not be considered part
750 // of this library's public API surface.
751 
752 #[doc(hidden)]
753 pub trait ParseFlag {
set_flag(&mut self, arg: &str)754     fn set_flag(&mut self, arg: &str);
755 }
756 
757 impl<T: Flag> ParseFlag for T {
set_flag(&mut self, _arg: &str)758     fn set_flag(&mut self, _arg: &str) {
759         <T as Flag>::set_flag(self);
760     }
761 }
762 
763 #[doc(hidden)]
764 pub struct RedactFlag {
765     pub slot: Option<String>,
766 }
767 
768 impl ParseFlag for RedactFlag {
set_flag(&mut self, arg: &str)769     fn set_flag(&mut self, arg: &str) {
770         self.slot = Some(arg.to_string());
771     }
772 }
773 
774 // A trait for for slots that reserve space for a value and know how to parse that value
775 // from a command-line `&str` argument.
776 //
777 // This trait is only implemented for the type `ParseValueSlotTy`. This indirection is
778 // necessary to allow abstracting over `ParseValueSlotTy` instances with different
779 // generic parameters.
780 #[doc(hidden)]
781 pub trait ParseValueSlot {
fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>782     fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>;
783 }
784 
785 // The concrete type implementing the `ParseValueSlot` trait.
786 //
787 // `T` is the type to be parsed from a single string.
788 // `Slot` is the type of the container that can hold a value or values of type `T`.
789 #[doc(hidden)]
790 pub struct ParseValueSlotTy<Slot, T> {
791     // The slot for a parsed value.
792     pub slot: Slot,
793     // The function to parse the value from a string
794     pub parse_func: fn(&str, &str) -> Result<T, String>,
795 }
796 
797 // `ParseValueSlotTy<Option<T>, T>` is used as the slot for all non-repeating
798 // arguments, both optional and required.
799 impl<T> ParseValueSlot for ParseValueSlotTy<Option<T>, T> {
fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>800     fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {
801         if self.slot.is_some() {
802             return Err("duplicate values provided".to_string());
803         }
804         self.slot = Some((self.parse_func)(arg, value)?);
805         Ok(())
806     }
807 }
808 
809 // `ParseValueSlotTy<Vec<T>, T>` is used as the slot for repeating arguments.
810 impl<T> ParseValueSlot for ParseValueSlotTy<Vec<T>, T> {
fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>811     fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {
812         self.slot.push((self.parse_func)(arg, value)?);
813         Ok(())
814     }
815 }
816 
817 /// A type which can be the receiver of a `Flag`.
818 pub trait Flag {
819     /// Creates a default instance of the flag value;
default() -> Self where Self: Sized820     fn default() -> Self
821     where
822         Self: Sized;
823 
824     /// Sets the flag. This function is called when the flag is provided.
set_flag(&mut self)825     fn set_flag(&mut self);
826 }
827 
828 impl Flag for bool {
default() -> Self829     fn default() -> Self {
830         false
831     }
set_flag(&mut self)832     fn set_flag(&mut self) {
833         *self = true;
834     }
835 }
836 
837 impl Flag for Option<bool> {
default() -> Self838     fn default() -> Self {
839         None
840     }
841 
set_flag(&mut self)842     fn set_flag(&mut self) {
843         *self = Some(true);
844     }
845 }
846 
847 macro_rules! impl_flag_for_integers {
848     ($($ty:ty,)*) => {
849         $(
850             impl Flag for $ty {
851                 fn default() -> Self {
852                     0
853                 }
854                 fn set_flag(&mut self) {
855                     *self = self.saturating_add(1);
856                 }
857             }
858         )*
859     }
860 }
861 
862 impl_flag_for_integers![u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,];
863 
864 /// This function implements argument parsing for structs.
865 ///
866 /// `cmd_name`: The identifier for the current command.
867 /// `args`: The command line arguments.
868 /// `parse_options`: Helper to parse optional arguments.
869 /// `parse_positionals`: Helper to parse positional arguments.
870 /// `parse_subcommand`: Helper to parse a subcommand.
871 /// `help_func`: Generate a help message.
872 #[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>873 pub fn parse_struct_args(
874     cmd_name: &[&str],
875     args: &[&str],
876     mut parse_options: ParseStructOptions<'_>,
877     mut parse_positionals: ParseStructPositionals<'_>,
878     mut parse_subcommand: Option<ParseStructSubCommand<'_>>,
879     help_func: &dyn Fn() -> String,
880 ) -> Result<(), EarlyExit> {
881     let mut help = false;
882     let mut remaining_args = args;
883     let mut positional_index = 0;
884     let mut options_ended = false;
885 
886     'parse_args: while let Some(&next_arg) = remaining_args.first() {
887         remaining_args = &remaining_args[1..];
888         if (next_arg == "--help" || next_arg == "help") && !options_ended {
889             help = true;
890             continue;
891         }
892 
893         if next_arg.starts_with('-') && !options_ended {
894             if next_arg == "--" {
895                 options_ended = true;
896                 continue;
897             }
898 
899             if help {
900                 return Err("Trailing arguments are not allowed after `help`.".to_string().into());
901             }
902 
903             parse_options.parse(next_arg, &mut remaining_args)?;
904             continue;
905         }
906 
907         if let Some(ref mut parse_subcommand) = parse_subcommand {
908             if parse_subcommand.parse(help, cmd_name, next_arg, remaining_args)? {
909                 // Unset `help`, since we handled it in the subcommand
910                 help = false;
911                 break 'parse_args;
912             }
913         }
914 
915         options_ended |= parse_positionals.parse(&mut positional_index, next_arg)?;
916     }
917 
918     if help {
919         Err(EarlyExit { output: help_func(), status: Ok(()) })
920     } else {
921         Ok(())
922     }
923 }
924 
925 #[doc(hidden)]
926 pub struct ParseStructOptions<'a> {
927     /// A mapping from option string literals to the entry
928     /// in the output table. This may contain multiple entries mapping to
929     /// the same location in the table if both a short and long version
930     /// of the option exist (`-z` and `--zoo`).
931     pub arg_to_slot: &'static [(&'static str, usize)],
932 
933     /// The storage for argument output data.
934     pub slots: &'a mut [ParseStructOption<'a>],
935 }
936 
937 impl<'a> ParseStructOptions<'a> {
938     /// Parse a commandline option.
939     ///
940     /// `arg`: the current option argument being parsed (e.g. `--foo`).
941     /// `remaining_args`: the remaining command line arguments. This slice
942     /// will be advanced forwards if the option takes a value argument.
parse(&mut self, arg: &str, remaining_args: &mut &[&str]) -> Result<(), String>943     fn parse(&mut self, arg: &str, remaining_args: &mut &[&str]) -> Result<(), String> {
944         let pos = self
945             .arg_to_slot
946             .iter()
947             .find_map(|&(name, pos)| if name == arg { Some(pos) } else { None })
948             .ok_or_else(|| unrecognized_argument(arg))?;
949 
950         match self.slots[pos] {
951             ParseStructOption::Flag(ref mut b) => b.set_flag(arg),
952             ParseStructOption::Value(ref mut pvs) => {
953                 let value = remaining_args
954                     .first()
955                     .ok_or_else(|| ["No value provided for option '", arg, "'.\n"].concat())?;
956                 *remaining_args = &remaining_args[1..];
957                 pvs.fill_slot(arg, value).map_err(|s| {
958                     ["Error parsing option '", arg, "' with value '", value, "': ", &s, "\n"]
959                         .concat()
960                 })?;
961             }
962         }
963 
964         Ok(())
965     }
966 }
967 
unrecognized_argument(x: &str) -> String968 fn unrecognized_argument(x: &str) -> String {
969     ["Unrecognized argument: ", x, "\n"].concat()
970 }
971 
972 // `--` or `-` options, including a mutable reference to their value.
973 #[doc(hidden)]
974 pub enum ParseStructOption<'a> {
975     // A flag which is set to `true` when provided.
976     Flag(&'a mut dyn ParseFlag),
977     // A value which is parsed from the string following the `--` argument,
978     // e.g. `--foo bar`.
979     Value(&'a mut dyn ParseValueSlot),
980 }
981 
982 #[doc(hidden)]
983 pub struct ParseStructPositionals<'a> {
984     pub positionals: &'a mut [ParseStructPositional<'a>],
985     pub last_is_repeating: bool,
986     pub last_is_greedy: bool,
987 }
988 
989 impl<'a> ParseStructPositionals<'a> {
990     /// Parse the next positional argument.
991     ///
992     /// `arg`: the argument supplied by the user.
993     ///
994     /// Returns true if non-positional argument parsing should stop
995     /// after this one.
parse(&mut self, index: &mut usize, arg: &str) -> Result<bool, EarlyExit>996     fn parse(&mut self, index: &mut usize, arg: &str) -> Result<bool, EarlyExit> {
997         if *index < self.positionals.len() {
998             self.positionals[*index].parse(arg)?;
999 
1000             if self.last_is_repeating && *index == self.positionals.len() - 1 {
1001                 // Don't increment position if we're at the last arg
1002                 // *and* the last arg is repeating. If it's also remainder,
1003                 // halt non-option processing after this.
1004                 Ok(self.last_is_greedy)
1005             } else {
1006                 // If it is repeating, though, increment the index and continue
1007                 // processing options.
1008                 *index += 1;
1009                 Ok(false)
1010             }
1011         } else {
1012             Err(EarlyExit { output: unrecognized_arg(arg), status: Err(()) })
1013         }
1014     }
1015 }
1016 
1017 #[doc(hidden)]
1018 pub struct ParseStructPositional<'a> {
1019     // The positional's name
1020     pub name: &'static str,
1021 
1022     // The function to parse the positional.
1023     pub slot: &'a mut dyn ParseValueSlot,
1024 }
1025 
1026 impl<'a> ParseStructPositional<'a> {
1027     /// Parse a positional argument.
1028     ///
1029     /// `arg`: the argument supplied by the user.
parse(&mut self, arg: &str) -> Result<(), EarlyExit>1030     fn parse(&mut self, arg: &str) -> Result<(), EarlyExit> {
1031         self.slot.fill_slot("", arg).map_err(|s| {
1032             [
1033                 "Error parsing positional argument '",
1034                 self.name,
1035                 "' with value '",
1036                 arg,
1037                 "': ",
1038                 &s,
1039                 "\n",
1040             ]
1041             .concat()
1042             .into()
1043         })
1044     }
1045 }
1046 
1047 // A type to simplify parsing struct subcommands.
1048 //
1049 // This indirection is necessary to allow abstracting over `FromArgs` instances with different
1050 // generic parameters.
1051 #[doc(hidden)]
1052 pub struct ParseStructSubCommand<'a> {
1053     // The subcommand commands
1054     pub subcommands: &'static [&'static CommandInfo],
1055 
1056     pub dynamic_subcommands: &'a [&'static CommandInfo],
1057 
1058     // The function to parse the subcommand arguments.
1059     #[allow(clippy::type_complexity)]
1060     pub parse_func: &'a mut dyn FnMut(&[&str], &[&str]) -> Result<(), EarlyExit>,
1061 }
1062 
1063 impl<'a> ParseStructSubCommand<'a> {
parse( &mut self, help: bool, cmd_name: &[&str], arg: &str, remaining_args: &[&str], ) -> Result<bool, EarlyExit>1064     fn parse(
1065         &mut self,
1066         help: bool,
1067         cmd_name: &[&str],
1068         arg: &str,
1069         remaining_args: &[&str],
1070     ) -> Result<bool, EarlyExit> {
1071         for subcommand in self.subcommands.iter().chain(self.dynamic_subcommands.iter()) {
1072             if subcommand.name == arg {
1073                 let mut command = cmd_name.to_owned();
1074                 command.push(subcommand.name);
1075                 let prepended_help;
1076                 let remaining_args = if help {
1077                     prepended_help = prepend_help(remaining_args);
1078                     &prepended_help
1079                 } else {
1080                     remaining_args
1081                 };
1082 
1083                 (self.parse_func)(&command, remaining_args)?;
1084 
1085                 return Ok(true);
1086             }
1087         }
1088 
1089         Ok(false)
1090     }
1091 }
1092 
1093 // Prepend `help` to a list of arguments.
1094 // This is used to pass the `help` argument on to subcommands.
prepend_help<'a>(args: &[&'a str]) -> Vec<&'a str>1095 fn prepend_help<'a>(args: &[&'a str]) -> Vec<&'a str> {
1096     [&["help"], args].concat()
1097 }
1098 
1099 #[doc(hidden)]
print_subcommands<'a>(commands: impl Iterator<Item = &'a CommandInfo>) -> String1100 pub fn print_subcommands<'a>(commands: impl Iterator<Item = &'a CommandInfo>) -> String {
1101     let mut out = String::new();
1102     for cmd in commands {
1103         argh_shared::write_description(&mut out, cmd);
1104     }
1105     out
1106 }
1107 
unrecognized_arg(arg: &str) -> String1108 fn unrecognized_arg(arg: &str) -> String {
1109     ["Unrecognized argument: ", arg, "\n"].concat()
1110 }
1111 
1112 // An error string builder to report missing required options and subcommands.
1113 #[doc(hidden)]
1114 #[derive(Default)]
1115 pub struct MissingRequirements {
1116     options: Vec<&'static str>,
1117     subcommands: Option<Vec<&'static CommandInfo>>,
1118     positional_args: Vec<&'static str>,
1119 }
1120 
1121 const NEWLINE_INDENT: &str = "\n    ";
1122 
1123 impl MissingRequirements {
1124     // Add a missing required option.
1125     #[doc(hidden)]
missing_option(&mut self, name: &'static str)1126     pub fn missing_option(&mut self, name: &'static str) {
1127         self.options.push(name)
1128     }
1129 
1130     // Add a missing required subcommand.
1131     #[doc(hidden)]
missing_subcommands(&mut self, commands: impl Iterator<Item = &'static CommandInfo>)1132     pub fn missing_subcommands(&mut self, commands: impl Iterator<Item = &'static CommandInfo>) {
1133         self.subcommands = Some(commands.collect());
1134     }
1135 
1136     // Add a missing positional argument.
1137     #[doc(hidden)]
missing_positional_arg(&mut self, name: &'static str)1138     pub fn missing_positional_arg(&mut self, name: &'static str) {
1139         self.positional_args.push(name)
1140     }
1141 
1142     // If any missing options or subcommands were provided, returns an error string
1143     // describing the missing args.
1144     #[doc(hidden)]
err_on_any(&self) -> Result<(), String>1145     pub fn err_on_any(&self) -> Result<(), String> {
1146         if self.options.is_empty() && self.subcommands.is_none() && self.positional_args.is_empty()
1147         {
1148             return Ok(());
1149         }
1150 
1151         let mut output = String::new();
1152 
1153         if !self.positional_args.is_empty() {
1154             output.push_str("Required positional arguments not provided:");
1155             for arg in &self.positional_args {
1156                 output.push_str(NEWLINE_INDENT);
1157                 output.push_str(arg);
1158             }
1159         }
1160 
1161         if !self.options.is_empty() {
1162             if !self.positional_args.is_empty() {
1163                 output.push('\n');
1164             }
1165             output.push_str("Required options not provided:");
1166             for option in &self.options {
1167                 output.push_str(NEWLINE_INDENT);
1168                 output.push_str(option);
1169             }
1170         }
1171 
1172         if let Some(missing_subcommands) = &self.subcommands {
1173             if !self.options.is_empty() {
1174                 output.push('\n');
1175             }
1176             output.push_str("One of the following subcommands must be present:");
1177             output.push_str(NEWLINE_INDENT);
1178             output.push_str("help");
1179             for subcommand in missing_subcommands {
1180                 output.push_str(NEWLINE_INDENT);
1181                 output.push_str(subcommand.name);
1182             }
1183         }
1184 
1185         output.push('\n');
1186 
1187         Err(output)
1188     }
1189 }
1190 
1191 #[cfg(test)]
1192 mod test {
1193     use super::*;
1194 
1195     #[test]
test_cmd_extraction()1196     fn test_cmd_extraction() {
1197         let expected = "test_cmd";
1198         let path = format!("/tmp/{}", expected);
1199         let cmd = cmd(&path, &path);
1200         assert_eq!(expected, cmd);
1201     }
1202 }
1203