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}