miniarg/lib.rs
1//! A minimal argument parser, with support for no-std and no-alloc
2//!
3//! It mostly supports cmdlines in the form of `program -foo value -bar value`.
4//! That means:
5//!
6//! * values are strings
7//! * keys start with a single dash
8//! * keys can occur multiple times
9//!
10//! The last parameter can also be just a key without a value.
11//! (This can be useful for `-help`.)
12//!
13//! # Usage
14//!
15//! Add this to your `Cargo.toml`:
16//! ```toml
17//! [dependencies]
18//! miniarg = "0.5"
19//! ```
20//! The feature `std` is enabled by default and `alloc` and `derive` are optional.
21//!
22//! # Examples
23//!
24//! A minimal example looks like this:
25//! ```
26//! let cmdline = "executable -key value";
27//! let mut args = miniarg::parse(&cmdline, &["key"]);
28//! assert_eq!(args.next(), Some(Ok((&"key", "value"))));
29//! assert_eq!(args.next(), None);
30//! ```
31//!
32//! If you don't want to pass a cmdline, you can use an iterator instead:
33//!
34//! ```
35//! let iter = vec!["executable", "-key", "value"].into_iter();
36//! let mut args = miniarg::parse_from_iter(iter, &["key"]);
37//! assert_eq!(args.next(), Some(Ok((&"key", "value"))));
38//! assert_eq!(args.next(), None);
39//! ```
40//!
41//! You can use `collect::<Result<Vec<_>, _>>()` to get a `Vec`:
42//! ```
43//! let cmdline = "executable -key value";
44//! let args = miniarg::parse(&cmdline, &["key"]).collect::<Result<Vec<_>, _>>()?;
45//! assert_eq!(args, vec![(&"key", "value")]);
46//! # Ok::<(), miniarg::ParseError<'static>>(())
47//! ```
48//!
49//! If you compile with `std` or `alloc`, it also supports passing [`ToString`] instead of strings,
50//! for example your own enum:
51//! ```
52//! #[derive(Debug, PartialEq)]
53//! enum MyKeys {
54//! Foo,
55//! Bar,
56//! }
57//! impl std::fmt::Display for MyKeys {
58//! fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59//! std::fmt::Debug::fmt(self, f)
60//! }
61//! }
62//! let cmdline = "executable -foo value -bar value";
63//! let args = miniarg::parse(&cmdline, &[MyKeys::Foo, MyKeys::Bar])
64//! .collect::<Result<Vec<_>, _>>()?;
65//! assert_eq!(args, vec![(&MyKeys::Foo, "value"), (&MyKeys::Bar, "value")]);
66//! # Ok::<(), miniarg::ParseError<'static>>(())
67//! ```
68//! As you can see, the first character of the enum kinds is converted to lowercase.
69//!
70//! If you compile with `derive`, you can use a custom derive instead:
71//! ```ignore
72//! #[derive(Debug, Key, PartialEq)]
73//! enum MyKeys {
74//! Foo,
75//! Bar,
76//! }
77//! let cmdline = "executable -foo value -bar value";
78//! let args = MyKeys::parse(&cmdline).collect::<Result<Vec<_>, _>>()?;
79//! assert_eq!(args, vec![(&MyKeys::Foo, "value"), (&MyKeys::Bar, "value")]);
80//! # Ok::<(), miniarg::ParseError<'static>>(())
81//! ```
82//!
83//! In this case a help text is generated from the documentation comments on your enum kinds,
84//! `help_text()` retrieves it.
85//!
86//! The code never panics, but the returned iterator will contain [`ParseError`]s
87//! if anything goes wrong.
88//!
89//! You might also want to take a look at the [`split_args`] module for lower level access.
90//!
91//! [`ToString`]: https://doc.rust-lang.org/nightly/alloc/string/trait.ToString.html
92//! [`ParseError`]: enum.ParseError.html
93//! [`split_args`]: split_args/index.html
94#![doc(html_root_url = "https://docs.rs/miniarg/0.5.0")]
95#![cfg_attr(not(feature = "std"), no_std)]
96#[cfg(feature = "alloc")]
97extern crate alloc;
98#[cfg(feature = "alloc")]
99use alloc::string::{String, ToString};
100use core::fmt;
101use core::iter::Skip;
102#[cfg(feature = "std")]
103use std::error::Error;
104
105use cfg_if::cfg_if;
106
107mod parse;
108
109pub mod split_args;
110use split_args::SplitArgs;
111
112// This is a bit of a hack to allow building without std and without alloc.
113#[cfg(not(feature = "alloc"))]
114pub trait ToString {
115 fn to_string(&self) -> &str;
116}
117#[cfg(not(feature = "alloc"))]
118impl<'b> ToString for &str {
119 fn to_string(&self) -> &str {
120 self
121 }
122}
123#[cfg(not(feature = "std"))]
124trait Error {}
125
126/// Parse the command line.
127///
128/// See the main crate documentation for more details and examples.
129pub fn parse<'a, 'b, T>(
130 cmdline: &'a str,
131 options: &'b [T],
132) -> ArgumentIterator<'a, 'b, T, SplitArgs<'a>>
133where
134 T: ToString,
135{
136 let args = SplitArgs::new(cmdline);
137 ArgumentIterator::<'a, 'b, T, SplitArgs>::new(args, options)
138}
139
140/// Parse from a custom iterator.
141///
142/// It's like [`parse`] but instead of taking a string and splitting it using [`SplitArgs`]
143/// it takes the options from a custom iterator.
144///
145/// See the main crate documentation for more details and examples.
146///
147/// [`parse`]: fn.parse.html
148/// [`SplitArgs`]: split_args/struct.SplitArgs.html
149pub fn parse_from_iter<'a, 'b, T, S>(args: S, options: &'b [T]) -> ArgumentIterator<'a, 'b, T, S>
150where
151 T: ToString,
152 S: Iterator<Item = &'a str>,
153{
154 ArgumentIterator::<'a, 'b, T, S>::new(args, options)
155}
156
157/// The iterator returned by [`parse`] and [`parse_from_iter`].
158///
159/// [`parse`]: fn.parse.html
160/// [`parse_from_iter`]: fn.parse_from_iter.html
161pub struct ArgumentIterator<'a, 'b, T, S>
162where
163 T: ToString,
164 S: Iterator<Item = &'a str>,
165{
166 args: Skip<S>,
167 options: &'b [T],
168 last: Option<&'b T>,
169}
170
171impl<'a, 'b, T, S> ArgumentIterator<'a, 'b, T, S>
172where
173 T: ToString,
174 S: Iterator<Item = &'a str>,
175{
176 fn new(args: S, options: &'b [T]) -> Self {
177 // skip argv[0]
178 ArgumentIterator {
179 args: args.skip(1),
180 options,
181 last: None,
182 }
183 }
184}
185
186impl<'a, 'b, T, S> Iterator for ArgumentIterator<'a, 'b, T, S>
187where
188 T: ToString,
189 S: Iterator<Item = &'a str>,
190{
191 type Item = Result<(&'b T, &'a str), ParseError<'a>>;
192
193 /// Get the next key pair or an error.
194 fn next(&mut self) -> Option<Self::Item> {
195 loop {
196 let Some(arg) = self.args.next() else {
197 return match self.last {
198 Some(l) => {
199 self.last = None;
200 Some(Ok((l, "")))
201 }
202 None => None,
203 };
204 };
205 if let Some(l) = self.last {
206 // the last element was a key
207 self.last = None;
208 return Some(Ok((l, arg)));
209 }
210 // the next element has to be a key
211 if let Some(a) = arg.strip_prefix("-") {
212 self.last = self.options.iter().find(|o| {
213 cfg_if! {
214 if #[cfg(any(feature = "alloc", feature = "std"))] {
215 first_lower(&o.to_string())
216 } else {
217 o.to_string()
218 }
219 }
220 } == a);
221 if self.last.is_none() {
222 return Some(Err(ParseError::UnknownKey(a)));
223 }
224 } else {
225 return Some(Err(ParseError::NotAKey(arg)));
226 }
227 }
228 }
229}
230
231#[derive(Debug, PartialEq, Eq, Hash, Clone)]
232#[non_exhaustive]
233/// Errors occurred during parsing the command line.
234pub enum ParseError<'a> {
235 /// expected a key, but argument didn't start with a dash
236 NotAKey(&'a str),
237 /// key is not accepted
238 UnknownKey(&'a str),
239 // the default error
240 _Unknown,
241}
242
243impl fmt::Display for ParseError<'_> {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
245 match self {
246 Self::NotAKey(s) => write!(f, "expected '{s}' to start with a dash"),
247 Self::UnknownKey(s) => write!(f, "'{s}' is not a known key"),
248 _ => write!(f, "unknown parse error"),
249 }
250 }
251}
252impl Error for ParseError<'_> {}
253
254#[cfg(all(feature = "derive", not(feature = "alloc")))]
255compile_error!("at least the `alloc` feature is currently required to get the derive feature");
256
257/// The main trait.
258///
259/// Derive this with an enum to get the functionality.
260/// Each kind represents a `-key value` option (starts with lowercase).
261/// They all have a string as a value and may occur multiple times.
262///
263/// The crate needs to be compiled with `derive` and either `std` or `alloc`.
264///
265/// # Example
266/// ```
267/// # #[macro_use] use miniarg::*;
268/// use std::fmt;
269/// #[derive(Debug, Key, PartialEq, Eq, Hash)]
270/// enum MyKeys {
271/// Foo,
272/// Bar,
273/// }
274/// # fn main() -> Result<(), miniarg::ParseError<'static>> {
275/// let cmdline = "executable -foo value -bar value";
276/// let args = MyKeys::parse(&cmdline).collect::<Result<Vec<_>, _>>()?;
277/// assert_eq!(args, vec![(&MyKeys::Foo, "value"), (&MyKeys::Bar, "value")]);
278/// # Ok(())
279/// # }
280#[cfg(feature = "derive")]
281pub trait Key {
282 /// Parse the cmdline.
283 ///
284 /// You'll get an iterator yielding key value pairs.
285 fn parse(cmdline: &str) -> ArgumentIterator<Self, SplitArgs>
286 where
287 Self: ToString + Sized;
288
289 /// Get a help text.
290 ///
291 /// This is being created from the enum kinds and their documentation comments.
292 fn help_text() -> &'static str;
293}
294
295/// custom derive for the [`Key`] trait
296///
297/// [`Key`]: trait.Key.html
298#[cfg(feature = "derive")]
299pub use miniarg_derive::Key;
300
301/// Turn the first character into lowercase.
302#[cfg(feature = "alloc")]
303fn first_lower(input: &str) -> String {
304 // taken from https://stackoverflow.com/a/38406885/2192464
305 let mut c = input.chars();
306 match c.next() {
307 None => String::new(),
308 Some(f) => f.to_lowercase().collect::<String>() + c.as_str(),
309 }
310}