argwerk_no_std/lib.rs
1//! [](https://docs.rs/argwerk)
2//! [](https://crates.io/crates/argwerk)
3//! [](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}