argwerk_no_std/
lib.rs

1//! [![Documentation](https://docs.rs/argwerk/badge.svg)](https://docs.rs/argwerk)
2//! [![Crates](https://img.shields.io/crates/v/argwerk.svg)](https://crates.io/crates/argwerk)
3//! [![Actions Status](https://github.com/udoprog/argwerk/workflows/Rust/badge.svg)](https://github.com/udoprog/argwerk/actions)
4//!
5//! Define a simple command-line parser through a declarative macro.
6//!
7//! This is **not** intended to be a complete command-line parser library.
8//! Instead this can be used as an alternative quick-and-dirty approach that can
9//! be cheaply incorporated into a tool.
10//!
11//! For a more complete command-line parsing library, use [clap].
12//!
13//! We provide:
14//! * A dependency-free command-line parsing framework using declarative macros.
15//! * A flexible mechanism for parsing.
16//! * Formatting of decent looking help messages.
17//!
18//! We *do not* provide:
19//! * As-close-to correct line wrapping with wide unicode characters as possible
20//!   (see [textwrap]).
21//! * Built-in complex command structures like subcommands (see the
22//!   [subcommands] example for how this can be accomplished).
23//!
24//! For how to use, see the documentation of [argwerk_no_std::define] and
25//! [argwerk_no_std::args].
26//!
27//! # Examples
28//!
29//! Initially when you're adding arguments to your program you can use
30//! [argwerk_no_std::args]. This allows for easily parsing out a handful of optional
31//! parameters.
32//!
33//! > This example is available as `simple`:
34//! > ```sh
35//! > cargo run --example simple -- --limit 20
36//! > ```
37//!
38//! ```rust
39//! # fn main() -> Result<(), argwerk_no_std::Error> {
40//! let args = argwerk_no_std::args! {
41//!     /// A simple tool.
42//!     "tool [-h]" {
43//!         help: bool,
44//!         limit: usize = 10,
45//!     }
46//!     /// The limit of the operation. (default: 10).
47//!     ["-l" | "--limit", int] => {
48//!         limit = str::parse(&int)?;
49//!     }
50//!     /// Print this help.
51//!     ["-h" | "--help"] => {
52//!         println!("{}", HELP);
53//!         help = true;
54//!     }
55//! }?;
56//!
57//! if args.help {
58//!     return Ok(());
59//! }
60//!
61//! dbg!(args);
62//! # Ok(()) }
63//! ```
64//!
65//! After a while you might want to graduate to defining a *named* struct
66//! containing the arguments. This can be useful if you want to pass the
67//! arguments around.
68//!
69//! > This example is available as `tour`:
70//! > ```sh
71//! > cargo run --example tour -- --help
72//! > ```
73//!
74//! ```rust
75//! use std::ffi::OsString;
76//!
77//! argwerk_no_std::define! {
78//!     /// A command touring the capabilities of argwerk.
79//!     #[derive(Default)]
80//!     #[usage = "tour [-h]"]
81//!     struct Args {
82//!         help: bool,
83//!         #[required = "--file must be specified"]
84//!         file: String,
85//!         input: Option<String>,
86//!         limit: usize = 10,
87//!         positional: Option<(String, Option<String>)>,
88//!         raw: Option<OsString>,
89//!         rest: Vec<String>,
90//!     }
91//!     /// Prints the help.
92//!     ///
93//!     /// This includes:
94//!     ///    * All the available switches.
95//!     ///    * All the available positional arguments.
96//!     ///    * Whatever else the developer decided to put in here! We even support wrapping comments which are overly long.
97//!     ["-h" | "--help"] => {
98//!         println!("{}", Args::help());
99//!         help = true;
100//!     }
101//!     /// Limit the number of things by <n> (default: 10).
102//!     ["--limit" | "-l", n] => {
103//!         limit = str::parse(&n)?;
104//!     }
105//!     /// Write to the file specified by <path>.
106//!     ["--file", path] if !file.is_some() => {
107//!         file = Some(path);
108//!     }
109//!     /// Read from the specified input.
110//!     ["--input", #[option] path] => {
111//!         input = path;
112//!     }
113//!     /// A really long argument that exceeds usage limit and forces the documentation to wrap around with newlines.
114//!     ["--really-really-really-long-argument", thing] => {
115//!     }
116//!     /// A raw argument that passes whatever was passed in from the operating system.
117//!     ["--raw", #[os] arg] => {
118//!         raw = Some(arg);
119//!     }
120//!     /// Takes argument at <foo> and <bar>.
121//!     ///
122//!     ///    * This is an indented message. The first alphanumeric character determines the indentation to use.
123//!     [foo, #[option] bar, #[rest] args] if positional.is_none() => {
124//!         positional = Some((foo, bar));
125//!         rest = args;
126//!     }
127//! }
128//!
129//! # fn main() -> anyhow::Result<()> {
130//! // Note: we're using `parse` here instead of `args` since it works better
131//! // with the example.
132//! let args = Args::parse(vec!["--file", "foo.txt", "--input", "-"])?;
133//!
134//! dbg!(args);
135//! # Ok(()) }
136//! ```
137//!
138//! ## Time and size compared to other projects
139//!
140//! argwerk aims to be a lightweight dependency that is fast to compile. This is
141//! how it stacks up to other projects in that regard.
142//!
143//! The following summary was generated from the [projects found here].
144//!
145//! | project    | cold build (release) | rebuild* (release) | size (release) |
146//! |------------|----------------------|--------------------|----------------|
147//! | argh** | 5.142723s (4.849361s) | 416.9594ms (468.7003ms) | 297k (180k) |
148//! | argwerk | 1.443709s (1.2971457s) | 403.0641ms (514.036ms) | 265k (185k) |
149//! | clap*** | 11.9863223s (13.1338799s) | 551.407ms (807.8939ms) | 2188k (750k) |
150//! > *: rebuild was triggered by adding a single newline to `main.rs`.<br>
151//! > **: argh `0.1.4` including 11 dependencies.<br>
152//! > ***: clap `3.0.0-beta.2` including 32 dependencies.<br>
153//!
154//! You can try and build it yourself with:
155//!
156//! ```sh
157//! cargo run --manifest-path tools/builder/Cargo.toml
158//! ```
159//!
160//! [projects found here]: https://github.com/udoprog/argwerk/tree/main/projects
161//! [argwerk_no_std::define]: https://docs.rs/argwerk/0/argwerk/macro.define.html
162//! [argwerk_no_std::args]: https://docs.rs/argwerk/0/argwerk/macro.args.html
163//! [clap]: https://docs.rs/clap
164//! [ok_or_else]: https://doc.rust-lang.org/std/option/enum.Option.html#method.ok_or_else
165//! [OsString]: https://doc.rust-lang.org/std/ffi/struct.OsString.html
166//! [textwrap]: https://docs.rs/textwrap/0.13.2/textwrap/#displayed-width-vs-byte-size
167//! [subcommands]: https://github.com/udoprog/argwerk/blob/main/examples/subcommands.rs
168
169#![deny(missing_docs)]
170#![no_std]
171
172extern crate alloc;
173
174use alloc::boxed::Box;
175use core::fmt;
176
177#[doc(hidden)]
178/// Macro helpers. Not intended for public use!
179pub mod helpers;
180
181pub use self::helpers::{Help, HelpFormat, InputError, Switch, TryIntoInput};
182
183/// An error raised by argwerk.
184#[derive(Debug)]
185pub struct Error {
186  kind: Box<ErrorKind>,
187}
188
189impl Error {
190  /// Construct a new error with the given kind.
191  pub fn new(kind: ErrorKind) -> Self {
192    Self {
193      kind: Box::new(kind),
194    }
195  }
196
197  /// Access the underlying error kind.
198  pub fn kind(&self) -> &ErrorKind {
199    &self.kind
200  }
201}
202
203impl fmt::Display for Error {
204  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205    match self.kind.as_ref() {
206      ErrorKind::UnsupportedArgument { argument } => {
207        write!(f, "unsupported argument `{}`", argument)
208      }
209      ErrorKind::UnsupportedSwitch { switch } => {
210        write!(f, "unsupported switch `{}`", switch)
211      }
212      ErrorKind::MissingSwitchArgument { switch, argument } => {
213        write!(f, "switch `{}` missing argument `{}`", switch, argument,)
214      }
215      ErrorKind::MissingPositional { name } => {
216        write!(f, "missing argument `{}`", name)
217      }
218      ErrorKind::MissingRequired { name, reason } => match reason {
219        Some(reason) => write!(f, "missing required argument: {}", reason),
220        None => write!(f, "missing required argument `{}`", name),
221      },
222      ErrorKind::InputError { error } => {
223        write!(f, "{}", error)
224      }
225      ErrorKind::Error { name, error } => {
226        write!(f, "error in argument `{}`: {}", name, error)
227      }
228    }
229  }
230}
231
232impl core_error::Error for Error {
233  fn source(&self) -> Option<&(dyn core_error::Error + 'static)> {
234    match self.kind.as_ref() {
235      ErrorKind::Error { error, .. } => Some(error.as_ref()),
236      _ => None,
237    }
238  }
239}
240
241impl From<crate::helpers::InputError> for Error {
242  fn from(error: crate::helpers::InputError) -> Self {
243    Error::new(ErrorKind::InputError { error })
244  }
245}
246
247/// The kind of an error.
248#[derive(Debug)]
249pub enum ErrorKind {
250  /// Encountered an argument that was not supported.
251  ///
252  /// An unsupported argument is triggered when none of the branches in the
253  /// parser matches the current agument.
254  ///
255  /// # Examples
256  ///
257  /// ```rust
258  /// argwerk_no_std::define! {
259  ///     struct Args { }
260  ///     // This errors because `bar` is not a supported switch, nor do we
261  ///     // match any positional arguments.
262  ///     ["--file", arg] => {}
263  /// }
264  ///
265  /// # fn main() -> Result<(), argwerk_no_std::Error> {
266  /// let error = Args::parse(vec!["bar"]).unwrap_err();
267  ///
268  /// assert!(matches!(error.kind(), argwerk_no_std::ErrorKind::UnsupportedArgument { .. }));
269  /// # Ok(()) }
270  /// ```
271  UnsupportedArgument {
272    /// The name of the unsupported argument.
273    argument: Box<str>,
274  },
275  /// Encountered a switch that was not supported.
276  ///
277  /// An unsupported switch is caused by the same reason as an unsupported
278  /// argument, but it's prefixed with a hyphen `-`.
279  ///
280  /// # Examples
281  ///
282  /// ```rust
283  /// argwerk_no_std::define! {
284  ///     #[usage = "command [-h]"]
285  ///     struct Args { }
286  ///     // This errors because `--path` is not a supported switch. But
287  ///     // `"--file"` is.
288  ///     ["--file", arg] => {}
289  /// }
290  ///
291  /// # fn main() -> Result<(), argwerk_no_std::Error> {
292  /// let error = Args::parse(vec!["--path"]).unwrap_err();
293  ///
294  /// assert!(matches!(error.kind(), argwerk_no_std::ErrorKind::UnsupportedSwitch { .. }));
295  /// # Ok(()) }
296  /// ```
297  UnsupportedSwitch {
298    /// The name of the unsupported switch.
299    switch: Box<str>,
300  },
301  /// When a parameter to an argument is missing.
302  ///
303  /// # Examples
304  ///
305  /// ```rust
306  /// argwerk_no_std::define! {
307  ///     struct Args { }
308  ///     // This errors because `--file` requires an argument `path`, but
309  ///     // that is not provided.
310  ///     ["--file", path] => {}
311  /// }
312  ///
313  /// # fn main() -> Result<(), argwerk_no_std::Error> {
314  /// let error = Args::parse(vec!["--file"]).unwrap_err();
315  ///
316  /// assert!(matches!(error.kind(), argwerk_no_std::ErrorKind::MissingSwitchArgument { .. }));
317  /// # Ok(()) }
318  /// ```
319  MissingSwitchArgument {
320    /// The switch where the argument was missing, like `--file` in `--file
321    /// <path>`.
322    switch: Box<str>,
323    /// The argument that was missing, like `path` in `--file <path>`.
324    argument: &'static str,
325  },
326  /// When a positional argument is missing.
327  ///
328  /// # Examples
329  ///
330  /// ```rust
331  /// argwerk_no_std::define! {
332  ///     struct Args { }
333  ///     // This errors because `b` is a required argument, but we only have
334  ///     // one which matches `a`.
335  ///     [a, b] => {}
336  /// }
337  ///
338  /// # fn main() -> Result<(), argwerk_no_std::Error> {
339  /// let error = Args::parse(vec!["foo"]).unwrap_err();
340  ///
341  /// assert!(matches!(error.kind(), argwerk_no_std::ErrorKind::MissingPositional { .. }));
342  /// # Ok(()) }
343  /// ```
344  MissingPositional {
345    /// The name of the argument missing like `path` in `<path>`.
346    name: &'static str,
347  },
348  /// When a positional argument is missing.
349  ///
350  /// # Examples
351  ///
352  /// ```rust
353  /// argwerk_no_std::define! {
354  ///     struct Args {
355  ///         #[required = "--name must be used"]
356  ///         name: String,
357  ///     }
358  ///     ["--name", n] => {
359  ///         name = Some(n);
360  ///     }
361  ///     [rest] => {}
362  /// }
363  ///
364  /// # fn main() -> Result<(), argwerk_no_std::Error> {
365  /// let error = Args::parse(vec!["foo"]).unwrap_err();
366  ///
367  /// assert!(matches!(error.kind(), argwerk_no_std::ErrorKind::MissingRequired { name: "name", .. }));
368  /// # Ok(()) }
369  /// ```
370  MissingRequired {
371    /// The name of the required variable that is missing.
372    name: &'static str,
373    /// The reason that the required argument was missing.
374    reason: Option<&'static str>,
375  },
376  /// Failed to parse input as unicode string.
377  ///
378  /// This is raised in case argwerk needs to treat an input as a string, but
379  /// that is not possible.
380  ///
381  /// This is required if the string needs to be used in a [switch
382  /// branch][define#parsing-switches-likes---help].
383  InputError {
384    /// The underlying error.
385    error: crate::helpers::InputError,
386  },
387  /// When an error has been raised while processing an argument, typically
388  /// when something is being parsed.
389  ///
390  /// # Examples
391  ///
392  /// ```rust
393  /// argwerk_no_std::define! {
394  ///     #[usage = "command [-h]"]
395  ///     struct Args { }
396  ///     // This errors because we raise an error in the branch body.
397  ///     ["foo"] => {
398  ///         Err("something went wrong")
399  ///     }
400  /// }
401  ///
402  /// # fn main() -> Result<(), argwerk_no_std::Error> {
403  /// let error = Args::parse(vec!["foo"]).unwrap_err();
404  ///
405  /// assert!(matches!(error.kind(), argwerk_no_std::ErrorKind::Error { .. }));
406  /// # Ok(()) }
407  /// ```
408  Error {
409    /// The name of the switch or positional that couldn't be processed.
410    name: Box<str>,
411    /// The error that caused the parsing error.
412    error: Box<dyn core_error::Error + Send + Sync + 'static>,
413  },
414}
415
416/// Parse command-line arguments.
417///
418/// This will generate an anonymous structure containing the arguments defined
419/// which is returned by the macro.
420///
421/// Each branch is executed when an incoming argument matches and must return a
422/// [Result], like `Ok(())`. Error raised in the branch will cause a
423/// [ErrorKind::Error] error to be raised associated with that argument
424/// with the relevant error attached.
425///
426/// ## The generated arguments structure
427///
428/// The first part of the macro defines the state available to the parser. These
429/// are field-like declarations which can specify a default initialization
430/// value. Fields which do not specify a value will be initialized using
431/// [Default::default]. This is the only required component of the macro.
432///
433/// The macro produces an arguments struct with fields matching this
434/// declaration. This can be used to conveniently group and access data
435/// populated during argument parsing.
436///
437/// You can use arbitrary attributes for the struct.
438/// Note that [`std::fmt::Debug`] will be automatically derived.
439///
440/// ```rust
441/// argwerk_no_std::define! {
442///     /// A simple test command.
443///     #[usage = "command [-h]"]
444///     #[derive(Default)]
445///     struct Args {
446///         help: bool,
447///         limit: usize = 10,
448///     }
449///     /// Print this help.
450///     ["-h" | "--help"] => {
451///         help = true;
452///     }
453///     /// Specify a limit (default: 10).
454///     ["--limit", n] => {
455///         limit = str::parse(&n)?;
456///     }
457/// }
458///
459/// # fn main() -> Result<(), argwerk_no_std::Error> {
460/// let args = Args::parse(["--limit", "20"].iter().copied())?;
461///
462/// if args.help {
463///     println!("{}", Args::help());
464/// }
465///
466/// assert_eq!(args.help, false);
467/// assert_eq!(args.limit, 20);
468/// # Ok(()) }
469/// ```
470///
471/// This structure also has two associated functions which can be used to parse
472/// input:
473///
474/// * `args` - Which parses OS arguments using [std::env::args_os].
475/// * `parse` - Which can be provided with a custom iterator. This is what's
476///   used in almost all the examples.
477///
478/// When using the custom parse function each item produced by the passed in
479/// iterator must implement [TryIntoInput]. This is implemented by types such
480/// as: `&str`, `String`, `OsString` and `&OsStr`.
481///
482/// ```rust
483/// argwerk_no_std::define! {
484///     /// A simple test command.
485///     #[usage = "command [-h]"]
486///     struct Args {
487///         help: bool,
488///         limit: usize = 10,
489///         positional: Option<(String, String, String)>,
490///     }
491///     /// Print this help.
492///     ["-h" | "--help"] => {
493///         help = true;
494///     }
495///     [a, b, c] => {
496///         positional = Some((a, b, c));
497///     }
498/// }
499///
500/// # fn main() -> Result<(), argwerk_no_std::Error> {
501/// let args = Args::parse(std::env::args().skip(1))?;
502///
503/// if args.help {
504///     println!("{}", Args::help());
505/// }
506///
507/// let args = Args::parse(vec!["foo", "bar", "baz"])?;
508///
509/// assert_eq!(args.positional, Some((String::from("foo"), String::from("bar"), String::from("baz"))));
510/// # Ok(()) }
511/// ```
512///
513/// ## Parsing switches likes `--help`
514///
515/// The basic form of an argument branch one which matches on a string literal.
516/// The string literal (e.g. `"--help"`) will then be treated as the switch for
517/// the branch. You can specify multiple matches for each branch by separating
518/// them with a pipe (`|`).
519///
520/// It's not necessary that switches start with `-`, but this is assumed for
521/// convenience. In particular, argwerk will treat any arguments starting with a
522/// hyphen as "switch-like". This is used to determine whether an argument is
523/// present if its optional (see later section).
524///
525/// ```rust
526/// argwerk_no_std::define! {
527///     #[usage = "command [-h]"]
528///     struct Args {
529///         help: bool
530///     }
531///     ["-h" | "--help"] => {
532///         help = true;
533///     }
534/// }
535///
536/// # fn main() -> Result<(), argwerk_no_std::Error> {
537/// let args = Args::parse(vec!["-h"])?;
538///
539/// if args.help {
540///     println!("{}", Args::help());
541/// }
542///
543/// assert_eq!(args.help, true);
544/// # Ok(()) }
545/// ```
546///
547/// ## Parsing positional arguments
548///
549/// Positional arguments are parsed by specifying a vector of bindings in a
550/// branch. Like `[foo, bar]`.
551///
552/// The following is a basic example. Two arguments `foo` and `bar` are required
553/// if the branch matches. If there is no such input an
554/// [ErrorKind::MissingPositional] error will be raised.
555///
556/// ```rust
557/// argwerk_no_std::define! {
558///     #[usage = "command [-h]"]
559///     struct Args {
560///         positional: Option<(String, String)>,
561///     }
562///     [foo, bar] if positional.is_none() => {
563///         positional = Some((foo, bar));
564///     }
565/// }
566///
567/// # fn main() -> Result<(), argwerk_no_std::Error> {
568/// let args = Args::parse(["a", "b"].iter().copied())?;
569///
570/// assert_eq!(args.positional, Some((String::from("a"), String::from("b"))));
571/// # Ok(()) }
572/// ```
573///
574/// ## Help documentation
575///
576/// You specify documentation for switches and arguments using doc comments
577/// (e.g. `/// Hello World`). These are automatically wrapped to 80 characters.
578///
579/// Documentation can be formatted with the `help` associated function, which
580/// returns a static instance of [Help]. This is also available as the `HELP`
581/// static variable inside of match branches. Help formatting can be further
582/// customized using [Help::format].
583///
584/// ```rust
585/// argwerk_no_std::define! {
586///     /// A simple test command.
587///     #[usage = "command [-h]"]
588///     struct Args {
589///         help2: bool,
590///     }
591///     /// Prints the help.
592///     ///
593///     /// This includes:
594///     ///    * All the available switches.
595///     ///    * All the available positional arguments.
596///     ///    * Whatever else the developer decided to put in here! We even support wrapping comments which are overly long.
597///     ["-h" | "--help"] => {
598///         println!("{}", HELP.format().width(120));
599///     }
600///     ["--help2"] => {
601///         help2 = true;
602///     }
603/// }
604///
605/// # fn main() -> Result<(), argwerk_no_std::Error> {
606/// let args = Args::parse(std::env::args().skip(1))?;
607///
608/// // Another way to access and format help documentation.
609/// if args.help2 {
610///     println!("{}", Args::help().format().width(120));
611/// }
612///
613/// # Ok(()) }
614/// ```
615///
616/// Invoking this with `-h` would print:
617///
618/// ```text
619/// Usage: command [-h]
620/// A simple test command.
621///
622/// This is nice!
623///
624/// Options:
625///   -h, --help  Prints the help.
626///
627///               This includes:
628///                  * All the available switches.
629///                  * All the available positional arguments.
630///                  * Whatever else the developer decided to put in here! We even
631///                    support wrapping comments which are overly long.
632/// ```
633///
634/// We determine the initial indentation level from the first doc comment.
635/// Looking at the code above, this would be the line containing `Prints the
636/// help.`. We then wrap additional lines relative to this level of indentation.
637///
638/// We also determine the individual indentation level of a line by looking at
639/// all the non-alphanumerical character that prefixes that line. That's why the
640/// "overly long" markdown list bullet above wraps correctly. Instead of
641/// wrapping at the `*`, it wraps to the first alphanumeric character after it.
642///
643/// ## Required arguments using `#[required]`
644///
645/// You can specify required arguments using the `#[required]` attribute in the
646/// field specification. Fields which are marked as `#[required]` have the type
647/// [Option\<T\>][Option]. If the field is left as uninitialized (`None`) once
648/// all arguments have been parsed will cause an error to be raised. See
649/// [ErrorKind::MissingRequired].
650///
651/// A reason that the argument is required can be optionally provided by doing
652/// `#[required = "--name is required"]`.
653///
654/// # Examples
655///
656/// ```rust
657/// argwerk_no_std::define! {
658///     struct Args {
659///         #[required = "--name must be used"]
660///         name: String,
661///     }
662///     ["--name", n] => {
663///         name = Some(n);
664///     }
665/// }
666///
667/// # fn main() -> Result<(), argwerk_no_std::Error> {
668/// let args = Args::parse(vec!["--name", "John"])?;
669/// assert_eq!(args.name, "John");
670/// # Ok(()) }
671/// ```
672///
673/// ## Raw os arguments with `#[os]`
674///
675/// In argwerk you can specify that a branch takes a raw argument using the
676/// `#[os]` attribute. This value will then be an
677/// [OsString][::std::ffi::OsString] and represents exactly what was fed to your
678/// program from the operating system.
679///
680/// ```rust
681/// use std::ffi::OsString;
682///
683/// argwerk_no_std::define! {
684///     /// A simple test command.
685///     #[usage = "command [-h]"]
686///     struct Args {
687///         raw: Option<OsString>,
688///     }
689///     ["--raw", #[os] arg] => {
690///         raw = Some(arg);
691///     }
692/// }
693///
694/// # fn main() -> Result<(), argwerk_no_std::Error> {
695/// let args = Args::parse(vec![OsString::from("--raw"), OsString::from("baz")])?;
696///
697/// assert!(args.raw.is_some());
698/// # Ok(()) }
699/// ```
700///
701/// ## Capture all available arguments using `#[rest]`
702///
703/// You can write a branch that receives all available arguments using the
704/// `#[rest]` attribute. This can be done both with arguments to switches, and
705/// positional arguments.
706///
707/// You can get the rest of the arguments in their raw form using `#[rest]`.
708///
709/// The following showcases capturing using a positional argument:
710///
711/// ```rust
712/// argwerk_no_std::define! {
713///     /// A simple test command.
714///     #[usage = "command [-h]"]
715///     struct Args {
716///         rest: Vec<String>,
717///     }
718///     [#[rest] args] => {
719///         rest = args;
720///     }
721/// }
722///
723/// # fn main() -> Result<(), argwerk_no_std::Error> {
724/// let args = Args::parse(["foo", "bar", "baz"].iter().copied())?;
725///
726/// assert_eq!(args.rest, &["foo", "bar", "baz"]);
727/// # Ok(()) }
728/// ```
729///
730/// And the following through a switch:
731///
732/// ```rust
733/// argwerk_no_std::define! {
734///     #[usage = "command [-h]"]
735///     struct Args {
736///         rest: Vec<String>,
737///     }
738///     ["--test", #[rest] args] => {
739///         rest = args;
740///     }
741/// }
742///
743/// # fn main() -> Result<(), argwerk_no_std::Error> {
744/// let args = Args::parse(["--test", "foo", "bar", "baz"].iter().copied())?;
745///
746/// assert_eq!(args.rest, &["foo", "bar", "baz"]);
747/// # Ok(()) }
748/// ```
749///
750/// This showcases getting raw os arguments using `#[rest]`:
751///
752/// ```rust
753/// use std::ffi::{OsString, OsStr};
754///
755/// argwerk_no_std::define! {
756///     /// A simple test command.
757///     #[usage = "command [-h]"]
758///     struct Args {
759///         rest: Vec<OsString>,
760///     }
761///     [#[rest] args] => {
762///         rest = args;
763///     }
764/// }
765///
766/// # fn main() -> Result<(), argwerk_no_std::Error> {
767/// let args = Args::parse(["foo", "bar", "baz"].iter().copied())?;
768///
769/// assert_eq!(args.rest, &[OsStr::new("foo"), OsStr::new("bar"), OsStr::new("baz")]);
770/// # Ok(()) }
771/// ```
772///
773/// ## Parsing optional arguments with `#[option]`
774///
775/// Switches and positional arguments can be marked with the `#[option]`
776/// attribute. This will cause the argument to take a value of type
777/// `Option<I::Item>` where `I` represents the iterator that is being parsed.
778///
779/// You can get an optional argument in its raw form using `#[option(os)]`.
780///
781/// An optional argument parses to `None` if:
782/// * There are no more arguments to parse.
783/// * The argument is "switch-like" (starts with `-`).
784///
785/// ```rust
786/// use std::ffi::{OsString, OsStr};
787///
788/// argwerk_no_std::define! {
789///     /// A simple test command.
790///     #[usage = "command [-h]"]
791///     struct Args {
792///         foo: Option<String>,
793///         bar: bool,
794///         baz: Option<OsString>,
795///     }
796///     /// A switch taking an optional argument.
797///     ["--foo", #[option] arg] => {
798///         foo = arg;
799///     }
800///     ["--bar"] => {
801///         bar = true;
802///     }
803///     /// A switch taking an optional raw argument.
804///     ["--baz", #[option(os)] arg] => {
805///         baz = arg;
806///     }
807/// }
808///
809/// # fn main() -> Result<(), argwerk_no_std::Error> {
810/// // Argument exists, but looks like a switch.
811/// let args = Args::parse(["--foo", "--bar"].iter().copied())?;
812/// assert_eq!(args.foo.as_deref(), None);
813/// assert!(args.bar);
814///
815/// // Argument does not exist.
816/// let args = Args::parse(["--foo"].iter().copied())?;
817/// assert_eq!(args.foo.as_deref(), None);
818/// assert!(!args.bar);
819///
820/// let args = Args::parse(["--foo", "bar"].iter().copied())?;
821/// assert_eq!(args.foo.as_deref(), Some("bar"));
822/// assert!(!args.bar);
823///
824/// let args = Args::parse(["--baz"].iter().copied())?;
825/// assert_eq!(args.baz.as_deref(), None);
826/// assert!(!args.bar);
827///
828/// let args = Args::parse(["--baz", "bar"].iter().copied())?;
829/// assert_eq!(args.baz.as_deref(), Some(OsStr::new("bar")));
830/// assert!(!args.bar);
831/// # Ok(()) }
832/// ```
833#[macro_export]
834macro_rules! define {
835    (
836        $(#$attr:tt)*
837        $vis:vis struct $name:ident { $($body:tt)* }
838        $($config:tt)*
839    ) => {
840        $crate::__impl! {
841            $(#$attr)*
842            $vis struct $name { $($body)* }
843            $($config)*
844        }
845
846        impl $name {
847            /// Return a formatter that formats to the help string at 80
848            /// characters witdth of this argument structure.
849            $vis fn help() -> &'static $crate::Help {
850                &Self::HELP
851            }
852        }
853    };
854}
855
856/// Filter for docstrings.
857#[doc(hidden)]
858#[macro_export]
859macro_rules! __filter_asg_doc {
860    (#[doc = $d:literal] $(#$other:tt)* $($l:literal)*) => {
861        $crate::__filter_asg_doc!($(#$other)* $($l)* $d)
862    };
863    (#$_:tt $(#$other:tt)* $($l:literal)*) => {
864        $crate::__filter_asg_doc!($(#$other)* $($l)*)
865    };
866    ($($l:literal)*) => {
867        &[$($l),*]
868    };
869}
870
871/// Filter for usage. XXX Stops at first found.
872#[doc(hidden)]
873#[macro_export]
874macro_rules! __filter_asg_usage {
875    ($name:ident, #[usage = $usage:literal] $(#$_:tt)*) => {
876        $usage
877    };
878    ($name:ident, #$_:tt $(#$other:tt)*) => {
879        $crate::__filter_asg_usage!($name, $(#$other)*)
880    };
881    ($name:ident,) => {
882        stringify!($name)
883    };
884}
885
886/// Filter out the stuff we don't want to process ourself.
887#[doc(hidden)]
888#[macro_export]
889macro_rules! __filter_asg_other {
890    (#[doc = $_:literal] $(#$other:tt)*, $(#$done:tt)* $v:vis struct $i:ident $body:tt) => {
891        $crate::__filter_asg_other!{
892            $(#$other)*,
893            $(#$done)*
894            $v struct $i $body
895        }
896    };
897    (#[usage = $_:literal] $(#$other:tt)*, $(#$done:tt)* $v:vis struct $i:ident $body:tt) => {
898        $crate::__filter_asg_other!{
899            $(#$other)*,
900            $(#$done)*
901            $v struct $i $body
902        }
903    };
904    (#$attr:tt $(#$other:tt)*, $(#$done:tt)* $v:vis struct $i:ident $body:tt) => {
905        $crate::__filter_asg_other!{
906            $(#$other)*,
907            $(#$done)*
908            #$attr
909            $v struct $i $body
910        }
911    };
912    (, $(#$done:tt)* $v:vis struct $i:ident $body:tt) => {
913        $(#$done)*
914        $v struct $i $body
915    }
916}
917
918/// Internal implementation details of the [args] macro.
919#[doc(hidden)]
920#[macro_export]
921macro_rules! __impl {
922    // The guts of the parser.
923    (
924        $(#$attr:tt)*
925        $vis:vis struct $name:ident {
926            $( $(#$field_m:tt)* $fvis:vis $field:ident : $ty:ty $(= $expr:expr)? ),* $(,)?
927        }
928        $($config:tt)*
929    ) => {
930        $crate::__filter_asg_other! {
931            $(#$attr)*
932            #[derive(Debug)],
933            $vis struct $name { $($fvis $field: $ty,)* }
934        }
935
936        impl $name {
937            pub const HELP: $crate::Help = $crate::Help {
938                usage: $crate::__filter_asg_usage!($name, $(#$attr)*),
939                docs: $crate::__filter_asg_doc!($(#$attr)*),
940                switches: $crate::__impl!(@switches $($config)*)
941            };
942
943            /// Parse a custom iterator.
944            $vis fn parse<I>(it: I) -> Result<Self, $crate::Error>
945            where
946                I: IntoIterator,
947                I::Item: $crate::TryIntoInput,
948            {
949                static HELP: &$crate::Help = &$name::HELP;
950
951                let mut it = $crate::helpers::Input::new(it.into_iter());
952                $($crate::__impl!(@init $(#$field_m)* $field, $ty $(, $expr)*);)*
953
954                while let Some(__argwerk_item) = it.next()? {
955                    $crate::__impl!(@branches __argwerk_item, it, $($config)*);
956                }
957
958                Ok(Self {
959                    $($field: $crate::__impl!(@assign $(#$field_m)* $field)),*
960                })
961            }
962        }
963    };
964
965    // Argument formatting.
966    (@doc #[rest $($tt:tt)*] $argument:ident) => { concat!("<", stringify!($argument), "..>") };
967    (@doc #[option $($tt:tt)*] $argument:ident) => { concat!("[", stringify!($argument), "]") };
968    (@doc #[os] $argument:ident) => { concat!("<", stringify!($argument), ">") };
969    (@doc $argument:ident) => { concat!("<", stringify!($argument), ">") };
970
971    (@init $field:ident, $ty:ty) => {
972        let mut $field: $ty = Default::default();
973    };
974
975    (@init #[required $(= $reason:literal)?] $field:ident, $ty:ty) => {
976        let mut $field: Option<$ty> = None;
977    };
978
979    (@init $field:ident, $ty:ty, $expr:expr) => {
980        let mut $field: $ty = $expr;
981    };
982
983    (@assign $field:ident) => {
984        $field
985    };
986
987    (@assign #[required $(= $reason:literal)?] $field:ident) => {
988        match $field {
989            Some($field) => $field,
990            None => return Err($crate::Error::new($crate::ErrorKind::MissingRequired {
991                name: stringify!($field),
992                reason: $crate::__impl!(@required $($reason)*),
993            })),
994        }
995    };
996
997    // The missing required argument.
998    (@required) => { None };
999    (@required $reason:literal) => { Some($reason) };
1000
1001    // Generate help for positional branches.
1002    (@switch-help
1003        $($doc:literal)*
1004        [ $(#$first_m:tt)* $first:ident $(, $(#$rest_m:tt)* $rest:ident)* ]
1005    ) => {
1006        $crate::Switch {
1007            usage: concat!(
1008                $crate::__impl!(@doc $(#$first_m)* $first),
1009                $(" ", $crate::__impl!(@doc $(#$rest_m)* $rest),)*
1010            ),
1011            docs: &[$($doc,)*]
1012        }
1013    };
1014
1015    // Generate help for matching branches.
1016    (@switch-help
1017        $($doc:literal)*
1018        [$first:literal $(| $rest:literal)* $(, $(#$arg_m:tt)* $arg:ident)*]
1019    ) => {
1020        $crate::Switch {
1021            usage: concat!(
1022                $first, $(", ", $rest,)*
1023                $(" ", $crate::__impl!(@doc $(#$arg_m)* $arg),)*
1024            ),
1025            docs: &[$($doc,)*]
1026        }
1027    };
1028
1029    // Generate switches help.
1030    (@switches $( $(#[doc = $doc:literal])* [$($branch:tt)*] $(if $cond:expr)? => $block:block)*) => {
1031        &[$($crate::__impl!(@switch-help $($doc)* [$($branch)*])),*]
1032    };
1033
1034    // Expansion for all branches.
1035    (@branches
1036        $switch:ident, $it:ident,
1037        $(#$_pfx_meta:tt)*
1038        $(
1039            [$sw_first_pat:literal $(| $sw_rest_pat:literal)* $(, $(#$sw_arg_m:tt)? $sw_arg:ident)*]
1040            $(if $sw_cond:expr)?
1041            => $sw_block:block
1042            $(#$_sw_meta:tt)*
1043        )*
1044        $(
1045            [$(#$pos_first_m:tt)? $pos_first:ident $(, $(#$pos_rest_m:tt)? $pos_rest:ident)*]
1046            $(if $pos_cond:expr)?
1047            => $pos_block:block
1048            $(#$_pos_meta:tt)*
1049        )*
1050    ) => {
1051        let __argwerk_name = $switch.as_str();
1052
1053        match __argwerk_name {
1054            $($sw_first_pat $(| $sw_rest_pat)* $(if $sw_cond)* => {
1055                $(let $sw_arg = $crate::__var!(switch $switch, $it, $(#$sw_arg_m)* $sw_arg);)*
1056
1057                if let Err(error) = (|| $crate::helpers::into_result($sw_block))() {
1058                    return Err(::argwerk_no_std::Error::new(::argwerk_no_std::ErrorKind::Error {
1059                        name: __argwerk_name.into(),
1060                        error
1061                    }));
1062                }
1063
1064                continue;
1065            })*
1066            _ => {
1067                $(if true $(&& $pos_cond)* {
1068                    let __argwerk_name: Box<str> = __argwerk_name.into();
1069
1070                    let $pos_first = $crate::__var!(first $it, $(#$pos_first_m)* $switch);
1071                    $(let $pos_rest = $crate::__var!(pos $it, $(#$pos_rest_m)* $pos_rest);)*
1072
1073                    if let Err(error) = (|| $crate::helpers::into_result($pos_block))() {
1074                        return Err(::argwerk_no_std::Error::new(::argwerk_no_std::ErrorKind::Error {
1075                            name: __argwerk_name,
1076                            error
1077                        }));
1078                    }
1079
1080                    continue;
1081                })*
1082            },
1083        }
1084
1085        if __argwerk_name.starts_with('-') {
1086            return Err(::argwerk_no_std::Error::new(::argwerk_no_std::ErrorKind::UnsupportedSwitch {
1087                switch: __argwerk_name.into()
1088            }));
1089        } else {
1090            return Err(::argwerk_no_std::Error::new(::argwerk_no_std::ErrorKind::UnsupportedArgument {
1091                argument: __argwerk_name.into()
1092            }));
1093        }
1094    };
1095}
1096
1097/// Helper to decode a variable.
1098#[doc(hidden)]
1099#[macro_export]
1100macro_rules! __var {
1101    (var $var:ident) => { $var };
1102
1103    (rest $it:ident) => { $it.rest()? };
1104
1105    (next_unless_switch $it:ident) => { $it.next_unless_switch()? };
1106
1107    // Various ways of parsing the first argument.
1108    (first $it:ident, #[rest $($tt:tt)*] $var:ident) => {
1109        Some($crate::__var!(var $($tt)* $var))
1110            .into_iter()
1111            .chain($crate::__var!(rest $($tt)* $it))
1112            .collect::<Vec<_>>();
1113    };
1114    (first $it:ident, #[option $($tt:tt)*] $var:ident) => {
1115        Some($crate::__var!(var $($tt)* $var))
1116    };
1117    (first $it:ident, $var:ident) => {
1118        $var
1119    };
1120
1121    // Parse the rest of the available arguments.
1122    (pos $it:ident, #[rest $($tt:tt)*] $_:ident) => {
1123        $crate::__var!(rest $($tt)* $it)
1124    };
1125    // Parse an optional argument.
1126    (pos $it:ident, #[option $($tt:tt)*] $_:ident) => {
1127        $crate::__var!(next_unless_switch $($tt)* $it)
1128    };
1129
1130    // Parse the rest of the arguments.
1131    (pos $it:ident, $var:ident) => {
1132        match $it.next()? {
1133            Some($var) => $var,
1134            None => {
1135                return Err(::argwerk_no_std::Error::new(
1136                    ::argwerk_no_std::ErrorKind::MissingPositional {
1137                        name: stringify!($var),
1138                    },
1139                ))
1140            }
1141        }
1142    };
1143
1144    // Parse the rest of the available arguments.
1145    (switch $switch:ident, $it:ident, #[rest] $arg:ident) => {
1146        $crate::__var!(rest $it)
1147    };
1148    // Parse an optional argument.
1149    (switch $switch:ident, $it:ident, #[option $($tt:tt)*] $arg:ident) => {
1150        $crate::__var!(next_unless_switch $($tt)* $it)
1151    };
1152
1153    // Parse next argument.
1154    (switch $switch:ident, $it:ident, $var:ident) => {
1155        match $it.next()? {
1156            Some($var) => $var,
1157            None => {
1158                return Err(::argwerk_no_std::Error::new(
1159                    ::argwerk_no_std::ErrorKind::MissingSwitchArgument {
1160                        switch: $switch.into(),
1161                        argument: stringify!($var),
1162                    },
1163                ))
1164            }
1165        }
1166    };
1167}