diff --git a/src/lib.rs b/src/lib.rs index 3745178..55c47fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ use std::collections::{HashMap, HashSet}; use std::env::args_os; +use std::ops::RangeBounds; +/// A command line argument parser. #[derive(Debug, Clone)] pub struct Parser { flags: Vec, @@ -12,17 +14,107 @@ pub struct Parser { short_set: HashSet, } +impl Parser { + pub fn new() -> Parser { + Parser { + flags: Vec::new(), + args: Vec::new(), + pos_args: Vec::new(), + long_set: HashSet::new(), + short_set: HashSet::new(), + } + } + + /// Add a flag argument. + pub fn flag(&mut self, long: &str, short: Option<&str>, doc_string: &str) { + self.ensure_not_duplicate(long, short); + + self.flags.push(Flag { + long: long.into(), + short: short.map(|s| s.into()), + doc_string: doc_string.into(), + }); + } + + /// Add a standard argument. + pub fn argument>( + &mut self, + long: &str, + short: Option<&str>, + doc_string: &str, + value_name: &str, + acceptable_count: R, + ) { + self.ensure_not_duplicate(long, short); + + use std::ops::Bound; + let count_start: Option = match acceptable_count.start_bound() { + Bound::Included(&n) => Some(n), + Bound::Excluded(&n) => Some(n + 1), + Bound::Unbounded => None, + }; + let count_end: Option = match acceptable_count.end_bound() { + Bound::Included(&n) => Some(n + 1), + Bound::Excluded(&n) => Some(n), + Bound::Unbounded => None, + }; + + self.args.push(Arg { + long: long.into(), + short: short.map(|s| s.into()), + acceptable_count: (count_start, count_end), + value_name: value_name.into(), + doc_string: doc_string.into(), + }); + } + + /// Add a positional argument. + /// + /// There can only be one positional argument with `count == None`, + /// which represents any left over positional arguments after those + /// with explicit counts have been parsed. + pub fn positional_argument( + &mut self, + doc_string: &str, + value_name: &str, + count: Option, + ) { + self.pos_args.push(PosArg { + count: count, + value_name: value_name.into(), + doc_string: doc_string.into(), + }); + } + + //---------------- + + fn ensure_not_duplicate(&mut self, long: &str, short: Option<&str>) { + if self.long_set.contains(long) { + panic!("Attempted to add duplicate long argument \"--{}\".", long); + } + self.long_set.insert(long.into()); + + if let Some(short) = short { + if self.short_set.contains(short) { + panic!("Attempted to add duplicate short argument \"-{}\".", short); + } + self.short_set.insert(short.into()); + } + } +} + +/// Parsed command line arguments. #[derive(Debug, Clone)] pub struct Arguments { // TODO: allow OsString argument values. - /// Positional arguments, in the order passed. - pub positional: Vec, - - /// The flags that were passed. + /// The flags that were passed, indexed by `long`. pub flags: HashSet, - /// Non-positional arguments that were passed. - pub args: HashMap, + /// Non-positional arguments that were passed, indexed by `long`. + pub args: HashMap>, + + /// Positional arguments, indexed by `value_name`. + pub positional: HashMap>, } //------------------------------------------------------------- @@ -30,7 +122,7 @@ pub struct Arguments { /// Flag spec. #[derive(Debug, Clone)] struct Flag { - long: Option, + long: String, short: Option, // For documentation. @@ -40,9 +132,9 @@ struct Flag { /// Argument spec. #[derive(Debug, Clone)] struct Arg { - long: Option, + long: String, short: Option, - is_required: bool, + acceptable_count: (Option, Option), // For documentation. value_name: String, @@ -52,7 +144,7 @@ struct Arg { /// Positional argument spec. #[derive(Debug, Clone)] struct PosArg { - is_required: bool, + count: Option, // For documentation. value_name: String,