easy_args/lib.rs
1//! Utility for simple and declarative style command line argument parsing.
2//!
3//! easy-args is meant to be used to set up simple command-line argumenst for
4//! your applications and give them back in an easy to process way.
5//!
6//! # Getting Started
7//!
8//! ```
9//! // First you must define an [`ArgSpec`] which will determine what the
10//! // command-line arguments are for your program and will be used by the parser to
11//! // do some simple checks.
12//!
13//! // You make an [`ArgSpec`] with the builder pattern.
14//!
15//! use easy_args::{arg_spec, ArgSpec};
16//!
17//! let spec = ArgSpec::build()
18//! .boolean("fullscreen")
19//! .uinteger_array(2, "size")
20//! .done()
21//! .unwrap();
22//!
23//! // There is an `arg_spec!` macro which provides a nicer syntax.
24//! let spec = arg_spec! {
25//! fullscreen: bool,
26//! size: [u64; 2],
27//! };
28//!
29//! // Second you call [`ArgSpecs`]'s [`parse()`] method to retrieve the command-line
30//! // arguments in a processed form.
31//!
32//! let args = spec.parse().unwrap();
33//! if args.boolean("fullscreen") == Some(&true) {
34//! // Put application into windowed mode
35//! }
36//! ```
37//!
38//! And that's it! The arguments have been parsed and processed and can be
39//! accessed via [`Args`]'s getter methods.
40//!
41//! [`ArgSpec`] also has a [`parse()`] method so you don't have to make a
42//! throwaway variable.
43//!
44//! ```
45//! use easy_args::ArgSpec;
46//!
47//! let args = ArgSpec::build()
48//! .boolean("windowed")
49//! .string("mode")
50//! .parse()
51//! .unwrap();
52//! ```
53
54use std::collections::HashMap;
55use std::fmt::{Debug, Formatter};
56
57/// Specifies the valid arguments of the program and is used to parse
58/// the command-line arguments into an [`Arg`].
59pub struct ArgSpec {
60 args: HashMap<String, ArgType>,
61}
62
63type ArgIter = std::iter::Peekable<std::iter::Skip<std::env::Args>>;
64
65impl ArgSpec {
66 /// Creates an [`ArgSpecBuilder`] that can be used to build the [`ArgSpec`].
67 ///
68 /// # Example
69 ///
70 /// ```
71 /// use easy_args::ArgSpec;
72 ///
73 /// let spec = ArgSpec::build()
74 /// .boolean("arg1")
75 /// .done()
76 /// .unwrap();
77 /// ```
78 pub fn build() -> ArgSpecBuilder {
79 ArgSpecBuilder {
80 args: HashMap::new(),
81 err: None,
82 }
83 }
84
85 /// Determines if an argument of a given name and [`ArgType`] exists
86 /// within the [`ArgSpec`].
87 ///
88 /// # Example
89 ///
90 /// ```
91 /// use easy_args::{arg_spec, ArgType};
92 ///
93 /// let spec = arg_spec!(username: String);
94 /// if spec.has_arg("username", ArgType::String) {
95 /// let args = spec.parse().unwrap();
96 /// if let Some(username) = args.string("username") {
97 /// // do something with username
98 /// }
99 /// }
100 /// ```
101 pub fn has_arg(&self, name: impl Into<String>, ty: ArgType) -> bool {
102 if let Some(_t) = self.args.get(&name.into()) {
103 matches!(ty, _t)
104 } else {
105 false
106 }
107 }
108
109 /// Parses the command-line arguments and Returns [`Ok(Args)`] if there
110 /// were no parse errors and [`Err(Error)`] if otherwise.
111 ///
112 /// # Example
113 ///
114 /// ```
115 /// use easy_args::arg_spec;
116 ///
117 /// let spec = arg_spec!(vsync: bool);
118 /// match spec.parse() {
119 /// Ok(args) => {
120 /// // do stuff with the arguments
121 /// }
122 /// Err(err) => eprintln!("{:?}", err),
123 /// }
124 /// ```
125 pub fn parse(&self) -> Result<Args> {
126 let mut bools: HashMap<String, bool> = HashMap::new();
127 let mut ints: HashMap<String, i64> = HashMap::new();
128 let mut uints: HashMap<String, u64> = HashMap::new();
129 let mut floats: HashMap<String, f64> = HashMap::new();
130 let mut strs: HashMap<String, String> = HashMap::new();
131 let mut bool_arrays: HashMap<String, Box<[bool]>> = HashMap::new();
132 let mut int_arrays: HashMap<String, Box<[i64]>> = HashMap::new();
133 let mut uint_arrays: HashMap<String, Box<[u64]>> = HashMap::new();
134 let mut float_arrays: HashMap<String, Box<[f64]>> = HashMap::new();
135 let mut str_arrays: HashMap<String, Box<[String]>> = HashMap::new();
136 let mut free_args: Vec<String> = Vec::new();
137 let mut args = std::env::args().skip(1).peekable();
138
139 while let Some(arg) = args.next() {
140 let mut chars = arg.chars().peekable();
141 if chars.peek() == Some(&'-') {
142 chars.next();
143 if chars.peek() == Some(&'-') {
144 chars.next();
145 }
146 let arg_name: String = chars.collect();
147 let arg_type = *self
148 .args
149 .get(&arg_name)
150 .ok_or(Error::UnknownArgument(arg_name.clone()))?;
151 match arg_type {
152 ArgType::Boolean => parse_bool(arg_name, &mut args, &mut bools)?,
153 ArgType::Integer => parse_arg(arg_name, arg_type, &mut args, &mut ints)?,
154 ArgType::UInteger => parse_arg(arg_name, arg_type, &mut args, &mut uints)?,
155 ArgType::Float => parse_arg(arg_name, arg_type, &mut args, &mut floats)?,
156 ArgType::String => parse_arg(arg_name, arg_type, &mut args, &mut strs)?,
157 ArgType::BooleanArray(n) => {
158 parse_array_arg(arg_name, arg_type, n, &mut args, &mut bool_arrays)?
159 }
160 ArgType::IntegerArray(n) => {
161 parse_array_arg(arg_name, arg_type, n, &mut args, &mut int_arrays)?
162 }
163 ArgType::UIntegerArray(n) => {
164 parse_array_arg(arg_name, arg_type, n, &mut args, &mut uint_arrays)?
165 }
166 ArgType::FloatArray(n) => {
167 parse_array_arg(arg_name, arg_type, n, &mut args, &mut float_arrays)?
168 }
169 ArgType::StringArray(n) => {
170 parse_array_arg(arg_name, arg_type, n, &mut args, &mut str_arrays)?
171 }
172 }
173 } else {
174 free_args.push(chars.collect());
175 }
176 }
177
178 Ok(Args::new(
179 bools,
180 ints,
181 uints,
182 floats,
183 strs,
184 bool_arrays,
185 int_arrays,
186 uint_arrays,
187 float_arrays,
188 str_arrays,
189 free_args,
190 ))
191 }
192}
193
194trait HashMapExt<V> {
195 fn insert_arg(&mut self, name: String, v: V) -> Result<()>;
196}
197
198impl<V> HashMapExt<V> for HashMap<String, V> {
199 fn insert_arg(&mut self, name: String, v: V) -> Result<()> {
200 if self.contains_key(&name) {
201 Err(Error::RepeatedArgument(name))
202 } else {
203 self.insert(name, v);
204 Ok(())
205 }
206 }
207}
208
209trait StringExt {
210 fn is_valid_arg_name(&self) -> bool;
211}
212
213impl StringExt for String {
214 fn is_valid_arg_name(&self) -> bool {
215 !(self.starts_with('-')
216 || self.contains(|c: char| c.is_whitespace())
217 || self.contains(|c: char| c.is_control()))
218 }
219}
220
221fn parse_arg<T: std::str::FromStr>(
222 arg_name: String,
223 arg_type: ArgType,
224 args: &mut ArgIter,
225 dict: &mut HashMap<String, T>,
226) -> Result<()> {
227 let arg_str = args
228 .next()
229 .ok_or(Error::MissingParameter(arg_name.clone(), arg_type))?;
230 dict.insert_arg(
231 arg_name.clone(),
232 arg_str.parse::<T>().or(Err(Error::InvalidParameter(
233 arg_name.clone(),
234 arg_type,
235 arg_str,
236 )))?,
237 )?;
238 Ok(())
239}
240
241fn parse_bool(
242 arg_name: String,
243 args: &mut ArgIter,
244 bools: &mut HashMap<String, bool>,
245) -> Result<()> {
246 let value = if args.peek() == Some(&"false".to_string()) {
247 args.next();
248 false
249 } else if args.peek() == Some(&"true".to_string()) {
250 args.next();
251 true
252 } else {
253 true
254 };
255 bools.insert_arg(arg_name, value)
256}
257
258fn parse_array_arg<T: std::str::FromStr>(
259 arg_name: String,
260 arg_type: ArgType,
261 array_size: usize,
262 args: &mut ArgIter,
263 dict: &mut HashMap<String, Box<[T]>>,
264) -> Result<()> {
265 let mut params: Vec<T> = Vec::with_capacity(array_size);
266 for _ in 0..array_size {
267 let arg_str = args
268 .next()
269 .ok_or(Error::MissingParameter(arg_name.clone(), arg_type))?;
270 params.push(arg_str.parse::<T>().or(Err(Error::InvalidParameter(
271 arg_name.clone(),
272 arg_type,
273 arg_str,
274 )))?);
275 }
276 dict.insert_arg(arg_name, params.into_boxed_slice())
277}
278
279impl std::fmt::Debug for ArgSpec {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 f.debug_map().entries(self.args.iter()).finish()
282 }
283}
284
285/// Enumerates all data types that are handled by [`ArgSpec::parse()`].
286#[derive(Copy, Clone, Debug)]
287pub enum ArgType {
288 Boolean,
289 BooleanArray(usize),
290 Integer,
291 IntegerArray(usize),
292 UInteger,
293 UIntegerArray(usize),
294 Float,
295 FloatArray(usize),
296 String,
297 StringArray(usize),
298}
299
300/// Builder type for [`ArgSpec`].
301pub struct ArgSpecBuilder {
302 args: HashMap<String, ArgType>,
303 err: Option<Error>,
304}
305
306impl ArgSpecBuilder {
307 /// Completes building the [`ArgSpec`]. Returns [`Ok(ArgSpec)`] if no build
308 /// errors and [`Err(Error)`] if otherwise.
309 ///
310 /// # Example
311 ///
312 /// ```
313 /// use easy_args::ArgSpec;
314 ///
315 /// let spec = ArgSpec::build()
316 /// .uinteger("chickens")
317 /// .done().
318 /// unwrap();
319 /// ```
320 pub fn done(self) -> Result<ArgSpec> {
321 if let Some(err) = self.err {
322 Err(err)
323 } else {
324 Ok(ArgSpec { args: self.args })
325 }
326 }
327
328 /// Little Wrapper function that will complete building the [`ArgSpec`]
329 /// and immediately call [`parse()`].
330 ///
331 /// # Example
332 ///
333 /// ```
334 /// use easy_args::arg_spec;
335 ///
336 /// let args = arg_spec! {
337 /// fullscreen: bool,
338 /// vsync: bool,
339 /// username: String
340 /// }
341 /// .parse()
342 /// .unwrap();
343 /// ```
344 pub fn parse(self) -> Result<Args> {
345 self.done()?.parse()
346 }
347
348 /// Adds an argument the the [`ArgSpec`] with a given name and type.
349 ///
350 /// # Example
351 ///
352 /// ```
353 /// use easy_args::{ArgSpec, ArgType};
354 ///
355 /// let spec = ArgSpec::build()
356 /// .arg("flag", ArgType::Boolean)
357 /// .done()
358 /// .unwrap();
359 /// ```
360 pub fn arg(mut self, name: impl Into<String>, ty: ArgType) -> ArgSpecBuilder {
361 let name_str = name.into();
362 if self.err.is_none() && !self.args.contains_key(&name_str) {
363 if name_str.is_valid_arg_name() {
364 self.args.insert(name_str, ty);
365 } else {
366 self.err = Some(Error::InvalidArgumentName(name_str));
367 }
368 } else {
369 self.err = Some(Error::RedeclaredArgument(name_str));
370 }
371 self
372 }
373
374 /// Adds a boolean argument to the [`ArgSpec`].
375 ///
376 /// # Example
377 ///
378 /// ```
379 /// use easy_args::{ArgSpec, ArgType};
380 ///
381 /// let spec = ArgSpec::build()
382 /// .boolean("vsync")
383 /// .done()
384 /// .unwrap();
385 /// assert_eq!(spec.has_arg("vsync", ArgType::Boolean), true);
386 /// ```
387 pub fn boolean(self, name: impl Into<String>) -> ArgSpecBuilder {
388 self.arg(name, ArgType::Boolean)
389 }
390
391 /// Adds an i64 argument to the [`ArgSpec`].
392 ///
393 /// # Exmaple
394 ///
395 /// ```
396 /// use easy_args::{ArgSpec, ArgType};
397 ///
398 /// let spec = ArgSpec::build()
399 /// .integer("num-bananas")
400 /// .done()
401 /// .unwrap();
402 /// assert_eq!(spec.has_arg("num-bananas", ArgType::Integer), true);
403 /// ```
404 pub fn integer(self, name: impl Into<String>) -> ArgSpecBuilder {
405 self.arg(name, ArgType::Integer)
406 }
407
408 /// Adds a u64 argument to the [`ArgSpec`].
409 ///
410 /// # Exmaple
411 ///
412 /// ```
413 /// use easy_args::{ArgSpec, ArgType};
414 ///
415 /// let spec = ArgSpec::build()
416 /// .uinteger("screen-width")
417 /// .done()
418 /// .unwrap();
419 /// assert_eq!(spec.has_arg("screen-width", ArgType::UInteger), true);
420 /// ```
421 pub fn uinteger(self, name: impl Into<String>) -> ArgSpecBuilder {
422 self.arg(name, ArgType::UInteger)
423 }
424
425 /// Adds an f64 argument to the [`ArgSpec`].
426 ///
427 /// # Exmaple
428 ///
429 /// ```
430 /// use easy_args::{ArgSpec, ArgType};
431 ///
432 /// let spec = ArgSpec::build()
433 /// .float("gravity")
434 /// .done()
435 /// .unwrap();
436 /// assert_eq!(spec.has_arg("gravity", ArgType::Float), true);
437 /// ```
438 pub fn float(self, name: impl Into<String>) -> ArgSpecBuilder {
439 self.arg(name, ArgType::Float)
440 }
441
442 /// Adds a String argument to the [`ArgSpec`].
443 ///
444 /// # Exmaple
445 ///
446 /// ```
447 /// use easy_args::{ArgSpec, ArgType};
448 ///
449 /// let spec = ArgSpec::build()
450 /// .string("MOTD")
451 /// .done()
452 /// .unwrap();
453 /// assert_eq!(spec.has_arg("MOTD", ArgType::String), true);
454 /// ```
455 pub fn string(self, name: impl Into<String>) -> ArgSpecBuilder {
456 self.arg(name, ArgType::String)
457 }
458
459 /// Adds a boolean array argument to the [`ArgSpec`].
460 ///
461 /// # Example
462 ///
463 /// ```
464 /// use easy_args::{ArgSpec, ArgType};
465 ///
466 /// let spec = ArgSpec::build()
467 /// .boolean_array(2, "bools")
468 /// .done()
469 /// .unwrap();
470 /// assert_eq!(spec.has_arg("bools", ArgType::BooleanArray(2)), true);
471 /// ```
472 pub fn boolean_array(self, size: usize, name: impl Into<String>) -> ArgSpecBuilder {
473 self.arg(name, ArgType::BooleanArray(size))
474 }
475
476 /// Adds an i64 array argument to the [`ArgSpec`].
477 ///
478 /// # Exmaple
479 ///
480 /// ```
481 /// use easy_args::{ArgSpec, ArgType};
482 ///
483 /// let spec = ArgSpec::build()
484 /// .integer_array(3, "position")
485 /// .done()
486 /// .unwrap();
487 /// assert_eq!(spec.has_arg("position", ArgType::IntegerArray(3)), true);
488 /// ```
489 pub fn integer_array(self, size: usize, name: impl Into<String>) -> ArgSpecBuilder {
490 self.arg(name, ArgType::IntegerArray(size))
491 }
492
493 /// Adds a u64 array argument to the [`ArgSpec`].
494 ///
495 /// # Exmaple
496 ///
497 /// ```
498 /// use easy_args::{ArgSpec, ArgType};
499 ///
500 /// let spec = ArgSpec::build()
501 /// .uinteger_array(2, "size")
502 /// .done()
503 /// .unwrap();
504 /// assert_eq!(spec.has_arg("size", ArgType::UIntegerArray(2)), true);
505 /// ```
506 pub fn uinteger_array(self, size: usize, name: impl Into<String>) -> ArgSpecBuilder {
507 self.arg(name, ArgType::UIntegerArray(size))
508 }
509
510 /// Adds a f64 array argument to the [`ArgSpec`].
511 ///
512 /// # Exmaple
513 ///
514 /// ```
515 /// use easy_args::{ArgSpec, ArgType};
516 ///
517 /// let spec = ArgSpec::build()
518 /// .float_array(3, "position")
519 /// .done()
520 /// .unwrap();
521 /// assert_eq!(spec.has_arg("position", ArgType::FloatArray(2)), true);
522 /// ```
523 pub fn float_array(self, size: usize, name: impl Into<String>) -> ArgSpecBuilder {
524 self.arg(name, ArgType::FloatArray(size))
525 }
526
527 /// Adds a String array argument to the [`ArgSpec`].
528 ///
529 /// # Exmaple
530 ///
531 /// ```
532 /// use easy_args::{ArgSpec, ArgType};
533 ///
534 /// let spec = ArgSpec::build()
535 /// .string_array(2, "name")
536 /// .done()
537 /// .unwrap();
538 /// assert_eq!(spec.has_arg("name", ArgType::StringArray(2)), true);
539 /// ```
540 pub fn string_array(self, size: usize, name: impl Into<String>) -> ArgSpecBuilder {
541 self.arg(name, ArgType::StringArray(size))
542 }
543}
544
545/// Macro that lets uers write [`ArgSpec`]s in a nicer way.
546///
547/// # Example
548///
549/// ```
550/// use easy_args::{arg_spec, ArgType};
551///
552/// let spec = arg_spec!(vsync: bool, size: [u64; 2]);
553/// assert_eq!(spec.has_arg("vsync", ArgType::Boolean), true);
554/// assert_eq!(spec.has_arg("size", ArgType::UIntegerArray(2)), true);
555/// ```
556#[macro_export]
557macro_rules! arg_spec {
558 ($($arg_name:ident : $arg_type:tt),*$(,)?) => {{
559 let b = $crate::ArgSpec::build();
560 $(let b = arg_spec!(b, $arg_name, $arg_type);)*
561 b.done().unwrap()
562 }};
563
564 ($builder:expr, $arg_name:ident, bool) => {
565 $builder.boolean(stringify!($arg_name))
566 };
567 ($builder:expr, $arg_name:ident, i64) => {
568 $builder.integer(stringify!($arg_name))
569 };
570 ($builder:expr, $arg_name:ident, u64) => {
571 $builder.uinteger(stringify!($arg_name))
572 };
573 ($builder:expr, $arg_name:ident, f64) => {
574 $builder.float(stringify!($arg_name))
575 };
576 ($builder:expr, $arg_name:ident, String) => {
577 $builder.string(stringify!($arg_name))
578 };
579 ($builder:expr, $arg_name:ident, [bool; $array_size:literal]) => {
580 $builder.boolean_array($array_size, stringify!($arg_name))
581 };
582 ($builder:expr, $arg_name:ident, [i64; $array_size:literal]) => {
583 $builder.integer_array($array_size, stringify!($arg_name))
584 };
585 ($builder:expr, $arg_name:ident, [u64; $array_size:literal]) => {
586 $builder.uinteger_array($array_size, stringify!($arg_name))
587 };
588 ($builder:expr, $arg_name:ident, [f64; $array_size:literal]) => {
589 $builder.float_array($array_size, stringify!($arg_name))
590 };
591 ($builder:expr, $arg_name:ident, [String; $array_size:literal]) => {
592 $builder.string_array($array_size, stringify!($arg_name))
593 };
594 ($builder:expr, $arg_name:ident, $arg_type:tt) => {
595 compile_error!(concat!("`", stringify!($arg_name), "` cannot be of type `", stringify!($arg_type), "` because `ArgSpec` doesn't support it"));
596 };
597}
598
599
600/// Holds all the command-line arguments given by the user.
601///
602/// Each argument is contained within a [`HashMap`] that can be index by the
603/// argument's name.
604#[derive(Debug)]
605pub struct Args {
606 bools: HashMap<String, bool>,
607 ints: HashMap<String, i64>,
608 uints: HashMap<String, u64>,
609 floats: HashMap<String, f64>,
610 strs: HashMap<String, String>,
611 bool_arrays: HashMap<String, Box<[bool]>>,
612 int_arrays: HashMap<String, Box<[i64]>>,
613 uint_arrays: HashMap<String, Box<[u64]>>,
614 float_arrays: HashMap<String, Box<[f64]>>,
615 str_arrays: HashMap<String, Box<[String]>>,
616 free_args: Vec<String>,
617}
618
619impl Args {
620 pub(crate) fn new(
621 bools: HashMap<String, bool>,
622 ints: HashMap<String, i64>,
623 uints: HashMap<String, u64>,
624 floats: HashMap<String, f64>,
625 strs: HashMap<String, String>,
626 bool_arrays: HashMap<String, Box<[bool]>>,
627 int_arrays: HashMap<String, Box<[i64]>>,
628 uint_arrays: HashMap<String, Box<[u64]>>,
629 float_arrays: HashMap<String, Box<[f64]>>,
630 str_arrays: HashMap<String, Box<[String]>>,
631 free_args: Vec<String>,
632 ) -> Self {
633 Args {
634 bools,
635 ints,
636 uints,
637 floats,
638 strs,
639 bool_arrays,
640 int_arrays,
641 uint_arrays,
642 float_arrays,
643 str_arrays,
644 free_args,
645 }
646 }
647
648 /// Determines if an argument of a given name was set by the user in the
649 /// command-line.
650 ///
651 /// # Example
652 ///
653 /// ```
654 /// use easy_args::arg_spec;
655 ///
656 /// let args = arg_spec!(size: [u64; 2]).parse().unwrap();
657 /// if args.is_set("size") {
658 /// // resize the window height
659 /// }
660 /// ```
661 pub fn is_set(&self, name: impl Into<String>) -> bool {
662 let n = name.into();
663 self
664 .bools
665 .keys()
666 .chain(self.ints.keys())
667 .chain(self.uints.keys())
668 .chain(self.strs.keys())
669 .chain(self.bool_arrays.keys())
670 .chain(self.int_arrays.keys())
671 .chain(self.uint_arrays.keys())
672 .chain(self.str_arrays.keys())
673 .find(|&k| *k == n)
674 .is_some()
675 }
676
677 /// Returns a reference to the boolean value that corresponds with the
678 /// given argument name.
679 ///
680 /// # Example
681 ///
682 /// ```
683 /// use easy_args::arg_spec;
684 ///
685 /// let args = arg_spec!(fullscreen: bool).parse().unwrap();
686 /// if args.boolean("fullscreen") == Some(&true) {
687 /// // go fullscreen
688 /// }
689 /// ```
690 pub fn boolean(&self, name: impl Into<String>) -> Option<&bool> {
691 self.bools.get(&name.into())
692 }
693
694 /// Returns a reference to the i64 value that corresponds with the given
695 /// argument name.
696 ///
697 /// # Example
698 ///
699 /// ```
700 /// use easy_args::arg_spec;
701 ///
702 /// let args = arg_spec!(leaves: i64).parse().unwrap();
703 /// let num_leaves_in_pile = *args.integer("leaves").unwrap_or(&0);
704 /// ```
705 pub fn integer(&self, name: impl Into<String>) -> Option<&i64> {
706 self.ints.get(&name.into())
707 }
708
709 /// Returns a reference to the u64 value that corresponds with the given
710 /// argument name.
711 ///
712 /// # Example
713 ///
714 /// ```
715 /// use easy_args::arg_spec;
716 ///
717 /// let args = arg_spec!(size: u64).parse().unwrap();
718 /// let size = *args.uinteger("size").unwrap_or(&0);
719 /// ```
720 pub fn uinteger(&self, name: impl Into<String>) -> Option<&u64> {
721 self.uints.get(&name.into())
722 }
723
724 /// Returns a reference to the f64 value that corresponds with the given
725 /// argument name.
726 ///
727 /// # Example
728 ///
729 /// ```
730 /// use easy_args::arg_spec;
731 ///
732 /// let args = arg_spec!(gravity: f64).parse().unwrap();
733 /// let size = *args.float("gravity").unwrap_or(&9.81);
734 /// ```
735 pub fn float(&self, name: impl Into<String>) -> Option<&f64> {
736 self.floats.get(&name.into())
737 }
738
739 /// Returns a reference to the String value that corresponds with the
740 /// given argument name.
741 ///
742 /// # Exmaple
743 ///
744 /// ```
745 /// use easy_args::arg_spec;
746 ///
747 /// let args = arg_spec!(username: String).parse().unwrap();
748 /// let username = args.string("username").unwrap_or(&"Guest".to_string());
749 /// ```
750 pub fn string(&self, name: impl Into<String>) -> Option<&String> {
751 self.strs.get(&name.into())
752 }
753
754 /// Returns a reference to the boolean slice that corresponds with the
755 /// given argument name.
756 ///
757 /// # Example
758 ///
759 /// ```
760 /// use easy_args::arg_spec;
761 ///
762 /// let args = arg_spec!(flags: [bool; 5]).parse().unwrap();
763 /// if let Some(flags) = args.boolean_array("flags") {
764 /// // do something with flags
765 /// }
766 /// ```
767 pub fn boolean_array(&self, name: impl Into<String>) -> Option<&[bool]> {
768 Some(self.bool_arrays.get(&name.into())?.as_ref())
769 }
770
771 /// Returns a reference to the i64 slice that corresponds with the given
772 /// argument name.
773 ///
774 /// # Example
775 ///
776 /// ```
777 /// use easy_args::arg_spec;
778 ///
779 /// let args = arg_spec!(position: [i64; 3]).parse().unwrap();
780 /// if let Some([x, y, z]) = args.integer_array("position") {
781 /// // do something with the position
782 /// }
783 /// ```
784 pub fn integer_array(&self, name: impl Into<String>) -> Option<&[i64]> {
785 Some(self.int_arrays.get(&name.into())?.as_ref())
786 }
787
788 /// Returns a reference to the u64 slice that corresponds with the given
789 /// argument name.
790 ///
791 /// # Example
792 ///
793 /// ```
794 /// use easy_args::arg_spec;
795 ///
796 /// let args = arg_spec!(size: [u64; 2]).parse().unwrap();
797 /// if let Some([width, height]) = args.uinteger_array("size") {
798 /// // do something with screen size
799 /// }
800 /// ```
801 pub fn uinteger_array(&self, name: impl Into<String>) -> Option<&[u64]> {
802 Some(self.uint_arrays.get(&name.into())?.as_ref())
803 }
804
805 /// Returns a reference to the f64 slice that corresponds with the given
806 /// argument name.
807 ///
808 /// # Example
809 ///
810 /// ```
811 /// use easy_args::arg_spec;
812 ///
813 /// let args = arg_spec!(position: [f64; 3]).parse().unwrap();
814 /// if let Some([x, y, z]) = args.float_array("position") {
815 /// // do something with position
816 /// }
817 /// ```
818 pub fn float_array(&self, name: impl Into<String>) -> Option<&[f64]> {
819 Some(self.float_arrays.get(&name.into())?.as_ref())
820 }
821
822 /// Returns a reference to the String slice that corresponds with the
823 /// given argument name.
824 ///
825 /// # Exmaple
826 ///
827 /// ```
828 /// use easy_args::arg_spec;
829 ///
830 /// let args = arg_spec!(login_details: [String; 2]).parse().unwrap();
831 /// if let Some([username, password]) = args.string_array("login_details") {
832 /// // do something with username and password
833 /// }
834 /// ```
835 pub fn string_array(&self, name: impl Into<String>) -> Option<&[String]> {
836 Some(self.str_arrays.get(&name.into())?.as_ref())
837 }
838
839 /// A Vector of all strings passed as command-line arguments that weren't
840 /// arguments of the [`ArgSpec`].
841 ///
842 /// # Example
843 ///
844 /// ```
845 /// use easy_args::arg_spec;
846 ///
847 /// let args = arg_spec!().parse().unwrap();
848 /// for arg in args.free_args() {
849 /// println!("What the heck is this? '{}'.", arg);
850 /// }
851 /// ```
852 pub fn free_args(&self) -> &Vec<String> {
853 &self.free_args
854 }
855}
856
857
858/// The error types for argument parsing.
859///
860/// If user inputs arguments incorrectly, [`ArgSpec::parse()`] will return [`Err(Error)`].
861pub enum Error {
862 UnknownArgument(String),
863 MissingParameter(String, ArgType),
864 InvalidParameter(String, ArgType, String),
865 RedeclaredArgument(String),
866 RepeatedArgument(String),
867 InvalidArgumentName(String),
868}
869
870impl Debug for Error {
871 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
872 match self {
873 Error::UnknownArgument(name) => write!(f, "Unknown argument {}.", name),
874 Error::MissingParameter(name, ty) => write!(
875 f,
876 "Missing {:?} parameter value for argument '{}'.",
877 ty, name
878 ),
879 Error::InvalidParameter(name, ty, given) => write!(
880 f,
881 "Expected {:?} value for argument '{}' but found '{}'.",
882 ty, name, given
883 ),
884 Error::RedeclaredArgument(name) => {
885 write!(f, "Argument '{}' has already been declared.", name)
886 }
887 Error::RepeatedArgument(name) => {
888 write!(f, "Argument '{}' has already been assigned a value.", name)
889 }
890 Error::InvalidArgumentName(name) => write!(
891 f,
892 "'{}' is not a valid argument name. Argument names must not begin with a dash or contain any whitespace or control characters.",
893 name),
894 }
895 }
896}
897
898/// Convient [`Result`] type where [`E`] is [`Error`].
899pub type Result<T> = std::result::Result<T, Error>;
900
901
902#[cfg(test)]
903mod tests {
904 use super::*;
905
906 #[test]
907 fn spec_builder() -> Result<()> {
908 let spec = arg_spec! {
909 b: bool,
910 n: i64,
911 u: u64,
912 name: String,
913 };
914
915 assert!(spec.has_arg("b", ArgType::Boolean));
916 assert!(spec.has_arg("n", ArgType::Integer));
917 assert!(spec.has_arg("u", ArgType::UInteger));
918 assert!(spec.has_arg("name", ArgType::String));
919 assert!(!spec.has_arg("none", ArgType::Boolean));
920
921 Ok(())
922 }
923
924 #[test]
925 fn parse() -> Result<()> {
926 let args = arg_spec! {
927 b: bool,
928 n: i64,
929 u: u64,
930 name: String,
931 }
932 .parse()?;
933 assert_eq!(args.free_args().len(), 0);
934 Ok(())
935 }
936}