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