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