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