arg_fn/
lib.rs

1//! Argument parsing crate that allows the user to specify what to do for each argument.
2//!
3//! # Example
4//!
5//! ```
6//! #[derive(PartialEq, Debug, Default)]
7//! struct Config {
8//!     foo: bool,
9//!     bar: bool,
10//! }
11//!
12//! let cfg = arg_fn::Parser::new(Config::default(), |_cfg, _arg| {})
13//!     .arg("-foo", |cfg| cfg.foo = true)
14//!     .arg("-nofoo", |cfg| cfg.foo = false)
15//!     .arg("-bar", |cfg| cfg.bar = true)
16//!     .arg("-nobar", |cfg| cfg.bar = false)
17//!     .parse(["-bar", "-nofoo", "-foo", "-nobar", "-foo"]);
18//!
19//! assert_eq!(
20//!     cfg,
21//!     Config {
22//!         foo: true,
23//!         bar: false,
24//!     }
25//! )
26//! ```
27
28use std::{borrow::Cow, collections::HashMap};
29
30/// Parser struct containing the config, a map of arguments to functions, and a function that is
31/// called when an argument is not in the map.
32///
33/// # Example
34///
35/// ```
36/// #[derive(PartialEq, Debug, Default)]
37/// struct Config {
38///     foo: bool,
39///     bar: bool,
40/// }
41///
42/// let cfg = arg_fn::Parser::new(Config::default(), |_cfg, _arg| {})
43///     .arg("-foo", |cfg| cfg.foo = true)
44///     .arg("-nofoo", |cfg| cfg.foo = false)
45///     .arg("-bar", |cfg| cfg.bar = true)
46///     .arg("-nobar", |cfg| cfg.bar = false)
47///     .parse(["-bar", "-nofoo", "-foo", "-nobar", "-foo"]);
48///
49/// assert_eq!(
50///     cfg,
51///     Config {
52///         foo: true,
53///         bar: false,
54///     }
55/// )
56/// ```
57#[allow(clippy::type_complexity)]
58#[must_use]
59pub struct Parser<'a, Config: 'a> {
60    config: Config,
61    arguments: HashMap<Cow<'a, str>, Box<dyn Fn(&mut Config) + 'a>>,
62    unknown: Box<dyn Fn(&mut Config, &'a str) + 'a>,
63}
64
65impl<'a, Config: 'a> Parser<'a, Config> {
66    pub fn new(config: Config, unknown: impl Fn(&mut Config, &'a str) + 'a) -> Self {
67        Self {
68            config,
69            arguments: HashMap::new(),
70            unknown: Box::new(unknown),
71        }
72    }
73
74    #[allow(clippy::type_complexity)]
75    pub fn with_arguments(
76        config: Config,
77        arguments: HashMap<Cow<'a, str>, Box<dyn Fn(&mut Config) + 'a>>,
78        unknown: impl Fn(&mut Config, &'a str) + 'a,
79    ) -> Self {
80        Self {
81            config,
82            arguments,
83            unknown: Box::new(unknown),
84        }
85    }
86
87    pub fn arg(
88        mut self,
89        argument: impl Into<Cow<'a, str>>,
90        callback: impl Fn(&mut Config) + 'a,
91    ) -> Self {
92        self.arguments.insert(argument.into(), Box::new(callback));
93        self
94    }
95
96    pub fn parse(mut self, input: impl IntoIterator<Item = &'a str>) -> Config {
97        for arg in input {
98            if let Some(callback) = self.arguments.get(arg) {
99                callback(&mut self.config);
100            } else {
101                (self.unknown)(&mut self.config, arg);
102            };
103        }
104
105        self.config
106    }
107}
108
109impl<'a, Config: Default> Default for Parser<'a, Config> {
110    fn default() -> Self {
111        Self::new(Config::default(), |_, _| {})
112    }
113}