argwerk/
lib.rs

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