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