diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index a7b10d22..309681ee 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -22,6 +22,7 @@ use output::fmt::ColorWhen; use output::{Help, Usage}; use parse::errors::Result as ClapResult; use parse::{ArgMatcher, ArgMatches, Parser}; +use mkeymap::{MKeyMap, KeyType}; #[doc(hidden)] #[allow(dead_code)] @@ -105,7 +106,7 @@ where #[doc(hidden)] pub g_settings: AppFlags, #[doc(hidden)] - pub args: Vec>, + pub args: MKeyMap, #[doc(hidden)] pub subcommands: Vec>, #[doc(hidden)] @@ -637,6 +638,7 @@ impl<'a, 'b> App<'a, 'b> { None }; let arg = a.into().help_heading(help_heading); + //TODO add push functionality to MKeyMap self.args.push(arg); self } @@ -1439,12 +1441,13 @@ impl<'a, 'b> App<'a, 'b> { } // Perform expensive debug assertions debug_assert!({ - for a in &self.args { + for a in self.args.values() { self._arg_debug_asserts(a); } true }); - for a in &mut self.args { + //TODO add .values_mut() for MKeyMap + for a in self.args.values_mut() { // Fill in the groups if let Some(ref grps) = a.groups { for g in grps { diff --git a/src/lib.rs b/src/lib.rs index 2e39607f..7220d504 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -581,7 +581,11 @@ use std::result::Result as StdResult; #[macro_use] mod macros; +<<<<<<< HEAD mod build; +======= +mod mkeymap; +>>>>>>> WIP changing macros into MKeyMap calls mod completions; mod output; mod parse; diff --git a/src/macros.rs b/src/macros.rs index 45255919..dc19995d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1024,29 +1024,29 @@ macro_rules! find { } } -macro_rules! find_by_long { - ($app:expr, $long:expr, $what:ident) => {{ - $what!($app) - .filter(|a| a.long.is_some()) - .find(|a| match_alias!(a, $long, a.long.unwrap())) - }}; - ($app:expr, $long:expr) => {{ - $app.args.iter() - .filter(|a| a.long.is_some()) - .find(|a| match_alias!(a, $long, a.long.unwrap())) - }}; -} +// macro_rules! find_by_long { +// ($app:expr, $long:expr, $what:ident) => {{ +// $what!($app) +// .filter(|a| a.long.is_some()) +// .find(|a| match_alias!(a, $long, a.long.unwrap())) +// }}; +// ($app:expr, $long:expr) => {{ +// $app.args.iter() +// .filter(|a| a.long.is_some()) +// .find(|a| match_alias!(a, $long, a.long.unwrap())) +// }}; +// } -macro_rules! find_by_short { - ($app:expr, $short:expr, $what:ident) => {{ - $what!($app) - .find(|a| a.short == Some($short)) - }}; - ($app:expr, $short:expr) => {{ - $app.args.iter() - .find(|a| a.short == Some($short)) - }} -} +// macro_rules! find_by_short { +// ($app:expr, $short:expr, $what:ident) => {{ +// $what!($app) +// .find(|a| a.short == Some($short)) +// }}; +// ($app:expr, $short:expr) => {{ +// $app.args.iter() +// .find(|a| a.short == Some($short)) +// }} +// } macro_rules! find_subcmd_cloned { ($_self:expr, $sc:expr) => {{ @@ -1062,27 +1062,42 @@ macro_rules! find_subcmd { }}; } -macro_rules! shorts { - ($app:expr) => {{ - _shorts_longs!($app, short) - }}; -} +// macro_rules! shorts { +// ($app:expr) => {{ +// _shorts_longs!($app, short) +// }}; +// } +// macro_rules! longs { +// ($app:expr) => {{ +// $app.args.iter() +// .filter(|a| a.long.is_some()) +// .map(|a| a.long.unwrap()) +// .chain($app.args.iter() +// .filter(|a| a.aliases.is_some()) +// .flat_map(|a| a.aliases.as_ref().unwrap().iter().map(|als| als.0))) +// }}; +// } + +// macro_rules! _shorts_longs { +// ($app:expr, $what:ident) => {{ +// $app.args.iter().filter_map(|a| a.$what) +// }}; +// } + +//TODO change into one macro (repeated structure) macro_rules! longs { - ($app:expr) => {{ - $app.args.iter() - .filter(|a| a.long.is_some()) - .map(|a| a.long.unwrap()) - .chain($app.args.iter() - .filter(|a| a.aliases.is_some()) - .flat_map(|a| a.aliases.as_ref().unwrap().iter().map(|als| als.0))) - }}; + ($app:expr) => ({ + use mkeymap::KeyType; + $app.args.keys().filter_map(|a| if let KeyType::Long(v) = a {Some(v)} else {None}) + }); } -macro_rules! _shorts_longs { - ($app:expr, $what:ident) => {{ - $app.args.iter().filter_map(|a| a.$what) - }}; +macro_rules! shorts { + ($app:expr) => ({ + use mkeymap::KeyType; + $app.args.keys().filter_map(|a| if let KeyType::Short(v) = a {Some(v)} else {None}) + }) } macro_rules! _names { @@ -1113,15 +1128,15 @@ macro_rules! sc_names { }}; } -macro_rules! match_alias { - ($a:expr, $to:expr, $what:expr) => {{ - $what == $to || - ($a.aliases.is_some() && - $a.aliases - .as_ref() - .unwrap() - .iter() - .any(|alias| alias.0 == $to)) +// macro_rules! match_alias { +// ($a:expr, $to:expr, $what:expr) => {{ +// $what == $to || +// ($a.aliases.is_some() && +// $a.aliases +// .as_ref() +// .unwrap() +// .iter() +// .any(|alias| alias.0 == $to)) - }} -} +// }} +// } diff --git a/src/mkeymap.rs b/src/mkeymap.rs new file mode 100644 index 00000000..e9b5302d --- /dev/null +++ b/src/mkeymap.rs @@ -0,0 +1,327 @@ +#![feature(nll)] + +use std::collections::hash_map; +use std::collections::hash_map::DefaultHasher; +use std::collections::{HashMap, HashSet}; +use std::ffi::OsStr; +use std::hash::{Hash, Hasher}; +use std::slice; +use build::Arg; +// ! rustdoc + +#[derive(Default, PartialEq, Debug)] +pub struct MKeyMap<'a, 'b> +where + 'a: 'b, +{ + keys: HashMap, usize>, + value_index: Vec>, + values: HashMap>, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum KeyType<'a> { + Short(char), + Long(&'a OsStr), + Position(usize), +} + +impl<'a, 'b> MKeyMap<'a, 'b> { + pub fn new() -> Self { + MKeyMap::default() + } + //TODO ::from(x), ::with_capacity(n) etc + //? set theory ops? + + pub fn insert(&mut self, key: KeyType<'a>, value: Arg<'a, 'b>) -> usize { + let index = self.push(value); + self.keys.insert(key, index); + index + } + + pub fn push(&mut self, value: Arg<'a, 'b>) -> usize { + let index; + let mut hasher = DefaultHasher::new(); + + value.hash(&mut hasher); + + let hash = hasher.finish(); + + if let Some((idx, _)) = self.values.get(&hash).and_then(|ids| { + ids.iter() + .map(|&x| (x, &self.value_index[x])) + .find(|(_i, x)| x == &&value) + }) { + index = idx; + } else { + self.value_index.push(value); + index = self.value_index.len() - 1; + self.values + .entry(hash) + .and_modify(|x| { + x.insert(index); + }) + .or_insert({ + let mut set = HashSet::new(); + set.insert(index); + set + }); + } + } + //TODO ::push_many([x, y]) + + pub fn insert_key(&mut self, key: KeyType<'a>, index: usize) { + if index >= self.values.len() { + panic!("Index out of bounds"); + } + + self.keys.insert(key, index); + } + //TODO ::insert_keyset([Long, Key2]) + + pub fn insert_key_by_name(&mut self, key: KeyType<'a>, name: &str) { + let index = self + .value_index + .iter() + .position(|x| x.name == name) + .expect("No such name found"); + + self.keys.insert(key, index); + } + + pub fn get(&self, key: KeyType<'a>) -> &Arg<'a, 'b> { + self.keys + .get(&key) + .and_then(|&idx| self.value_index.get(idx)) + .expect(&format!("No entry for the key: {:?}", key)) + } + //TODO ::get_first([KeyA, KeyB]) + + pub fn get_mut(&mut self, key: KeyType<'a>) -> &mut Arg<'a, 'b> { + let idx = *self + .keys + .get(&key) + .expect(&format!("No entry for the key: {:?}", key)); + + self.value_index.get_mut(idx).unwrap() + } + + pub fn is_empty(&self) -> bool { + self.keys.is_empty() && self.values.is_empty() + } + + pub fn remove(&mut self, key: KeyType) -> Option { + unimplemented!() + } + //TODO ::remove_many([KeyA, KeyB]) + //? probably shouldn't add a possibility for removal? + //? or remove by replacement by some dummy object, so the order is preserved + + pub fn remove_key(&mut self, key: KeyType) { + unimplemented!() + } + //TODO ::remove_keys([KeyA, KeyB]) + + pub fn keys(&'a self) -> Keys<'a, usize> { + Keys { + iter: self.keys.keys(), + } + } + + pub fn values(&'a self) -> Values<'a, Arg> { + Values { + iter: self.value_index.iter(), + } + } + + pub fn values_mut(&'a mut self) -> ValuesMut<'a, Arg> { + ValuesMut { + iter: self.value_index.iter_mut(), + } + } +} + +pub struct Keys<'a, V: 'a> { + iter: hash_map::Keys<'a, KeyType<'a>, V>, +} + +impl<'a, V> Iterator for Keys<'a, V> { + type Item = &'a KeyType<'a>; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +pub struct Values<'a, V: 'a> { + iter: slice::Iter<'a, V>, +} + +impl<'a, V> Iterator for Values<'a, V> { + type Item = &'a V; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +pub struct ValuesMut<'a, V: 'a> { + iter: slice::IterMut<'a, V>, +} + +impl<'a, V> Iterator for ValuesMut<'a, V> { + type Item = &'a V; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::OsStr; + use self::KeyType::*; + + #[test] + fn get_some_value() { + let mut map: MKeyMap = MKeyMap::new(); + + { + map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + } + + assert_eq!( + map.get(Long(&OsStr::new("One"))), + &Arg::with_name("Value1") + ); + } + + #[test] + #[should_panic] + fn get_none_value() { + let mut map: MKeyMap = MKeyMap::new(); + + map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + map.get(Long(&OsStr::new("Two"))); + } + + // #[test] + // fn insert_delete_value() { + // let mut map = MKeyMap::new(); + // map.insert("One", clap::Arg::with_name("Value1")); + // assert_eq!(map.remove("One"), Some(clap::Arg::with_name("Value1"))); + // assert!(map.is_empty()); + // } + + #[test] + fn insert_duplicate_key() { + let mut map: MKeyMap = MKeyMap::new(); + + map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + + assert_eq!( + map.insert(Long(&OsStr::new("One")), Arg::with_name("Value2")), + 1 + ); + } + + #[test] + fn insert_duplicate_value() { + let mut map: MKeyMap = MKeyMap::new(); + + map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + + let orig_len = map.values.len(); + + map.insert(Long(&OsStr::new("Two")), Arg::with_name("Value1")); + + assert_eq!(map.values.len(), orig_len); + assert_eq!( + map.get(Long(&OsStr::new("One"))), + map.get(Long(&OsStr::new("Two"))) + ); + } + + // #[test] + // fn insert_delete_none() { + // let mut map = MKeyMap::new(); + // map.insert("One", clap::Arg::with_name("Value1")); + // assert_eq!(map.remove("Two"), None); + // assert!(!map.is_empty()); + // assert_eq!(map.get("One"), Some(clap::Arg::with_name("Value1"))); + // } + + #[test] + fn insert_multiple_keys() { + let mut map: MKeyMap = MKeyMap::new(); + let index = map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + + map.insert_key(Long(&OsStr::new("Two")), index); + + assert_eq!( + map.get(Long(&OsStr::new("One"))), + map.get(Long(&OsStr::new("Two"))) + ); + assert_eq!(map.values.len(), 1); + } + + #[test] + fn insert_by_name() { + let mut map: MKeyMap = MKeyMap::new(); + let index = map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + + map.insert_key_by_name(Long(&OsStr::new("Two")), "Value1"); + + assert_eq!( + map.get(Long(&OsStr::new("One"))), + map.get(Long(&OsStr::new("Two"))) + ); + assert_eq!(map.values.len(), 1); + } + + #[test] + fn get_mutable(){ + let mut map: MKeyMap = MKeyMap::new(); + + map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + + assert_eq!( + map.get_mut(Long(&OsStr::new("One"))), + &mut Arg::with_name("Value1") + ); + } + + #[test] + fn remove_key() { + let mut map: MKeyMap = MKeyMap::new(); + let index = map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + + map.insert_key(Long(&OsStr::new("Two")), index); + map.remove_key(Long(&OsStr::new("One"))); + + assert_eq!(map.keys.len(), 1); + assert_eq!(map.values.len(), 1); + } + + #[test] + fn iter_keys() { + let mut map: MKeyMap = MKeyMap::new(); + + map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); + map.insert(Long(&OsStr::new("Two")), Arg::with_name("Value2")); + map.insert(Position(1), Arg::with_name("Value1")); + + let iter = map.keys().cloned(); + let mut ground_truth = HashSet::new(); + + ground_truth.insert(Long(&OsStr::new("One"))); + ground_truth.insert(Long(&OsStr::new("Two"))); + ground_truth.insert(Position(1)); + + assert_eq!( + ground_truth.symmetric_difference(&iter.collect()).count(), + 0 + ); + } +} diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 33cda764..60dfb329 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -38,6 +38,9 @@ use parse::{ArgMatcher, SubCommand}; use util::OsStrExt2; use INVALID_UTF8; use INTERNAL_ERROR_MSG; +use parse::features::suggestions; +use output::Usage; +use mkeymap::KeyType; #[derive(Debug, PartialEq, Copy, Clone)] #[doc(hidden)] @@ -303,7 +306,22 @@ where pub(crate) fn _build(&mut self) { debugln!("Parser::_build;"); - for a in &mut self.app.args { + for (i, a) in self.app.args.values_mut().enumerate() { + if let Some(index) = a.index { + self.app.args.insert_key(KeyType::Positional(index), i); + } else { + if let Some(c) = a.short { + self.app.args.insert_key(KeyType::Short(c), i); + } + if let Some(l) = a.long { + self.app.args.insert_key(KeyType::Long(&OsStr::new(l)), i); + } + if let Some(v) = a.aliases { + for (item, _) in &v { + self.app.args.insert_key(KeyType::Long(&OsStr::new(item)), i); + } + } + } // Add conditional requirements if let Some(ref r_ifs) = a.r_ifs { for &(arg, val) in r_ifs { @@ -1012,8 +1030,9 @@ where sdebugln!("No"); full_arg.trim_left_matches(b'-') }; - - if let Some(opt) = find_by_long!(self.app, arg, opts) { +// opts?? Should probably now check once, then check whether it's opt or flag, or sth else + if let Some(opt) = self.app.args.get(KeyType::Long(arg)) + { debugln!( "Parser::parse_long_arg: Found valid opt '{}'", opt.to_string() @@ -1025,7 +1044,8 @@ where } return Ok(ret); - } else if let Some(flag) = find_by_long!(self.app, arg, flags) { +//flags?? + } else if let Some(flag) = self.app.args.get(KeyType::Long(arg)) { debugln!( "Parser::parse_long_arg: Found valid flag '{}'", flag.to_string() @@ -1092,7 +1112,7 @@ where // concatenated value: -oval // Option: -o // Value: val - if let Some(opt) = find_by_short!(self.app, c, opts) { + if let Some(opt) = self.app.args.get(KeyType::Short(c)) { debugln!("Parser::parse_short_arg:iter:{}: Found valid opt", c); self.app.settings.set(AS::ValidArgFound); // Check for trailing concatenated value @@ -1124,7 +1144,7 @@ where } return Ok(ret); - } else if let Some(flag) = find_by_short!(self.app, c, flags) { + } else if let Some(flag) = self.app.args.get(KeyType::Short(c)) { debugln!("Parser::parse_short_arg:iter:{}: Found valid flag", c); self.app.settings.set(AS::ValidArgFound); // Only flags can be help or version @@ -1510,11 +1530,11 @@ where // Add the arg to the matches to build a proper usage string if let Some(name) = suffix.1 { - if let Some(opt) = find_by_long!(self.app, name) { + if let Some(opt) = self.app.args.get(KeyType::Long(name)) { self.groups_for_arg(&*opt.name) .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); matcher.insert(&*opt.name); - } else if let Some(flg) = find_by_long!(self.app, name) { + } else if let Some(flg) = self.app.args.get(KeyType::Long(name)) { self.groups_for_arg(&*flg.name) .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); matcher.insert(&*flg.name);