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}