1use std::collections::HashMap;
7use std::fmt;
8use std::hash::{Hash};
9use std::str::FromStr;
10
11use slide::{Slider};
12
13#[derive(Debug, Clone, PartialEq)]
15pub enum ArgType {
16 Option,
18 Flag,
20 List,
23 Dict,
26 Positional(u8),
30}
31
32impl ArgType {
33 fn is_positional(&self) -> bool {
34 match self {
35 &ArgType::Positional(_) => true,
36 _ => false,
37 }
38 }
39}
40
41impl fmt::Display for ArgType {
42 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 let msg = match self {
44 &ArgType::Option => "Option",
45 &ArgType::Flag => "Flag",
46 &ArgType::List => "List",
47 &ArgType::Dict => "Dict",
48 &ArgType::Positional(_) => "Positional"
49 };
50
51 write!(f, "{}", msg)
52 }
53}
54
55#[derive(Debug, Clone)]
56struct Arg {
57 val: Option<String>,
58 count: u16,
59 required: bool,
60 flag: char,
61 help: String,
62 type_: ArgType,
63}
64
65#[derive(Debug, Clone)]
66pub struct ArgParser {
69 arguments: HashMap<String, Arg>,
70 name: String,
71 done: bool,
72}
73
74pub type ParseResult = Result<ArgParseResults, String>;
77
78impl ArgParser {
79 pub fn new(name: String) -> ArgParser {
82 let mut me = ArgParser {
83 arguments: HashMap::new(),
84 name: name,
85 done: false,
86 };
87
88 me.add_opt("help", Some("false"), 'h', false,
89 "Show this help message", ArgType::Flag);
90
91 me
92 }
93
94 pub fn add_opt(&mut self, name: &str,
108 default: Option<&str>, flag: char, required: bool,
109 help: &str, type_: ArgType) {
110
111 let o = Arg {
112 val: default.map(|x| x.into()),
113 count: 0,
114 required: required,
115 flag: flag,
116 help: help.into(),
117 type_: type_,
118 };
119
120 self.arguments.insert(name.into(), o);
121 }
122
123 pub fn remove_opt(&mut self, name: &str) -> Result<(), &'static str> {
138
139 self.arguments.remove(name).map(|_| ()).ok_or("No such Option")
140 }
141
142 pub fn parse<'a, I: Iterator<Item = &'a String>> (&self, args: I) -> ParseResult {
165 use std::collections::hash_map::Entry;
166
167 if self.arguments.len() == 0 || self.done {
168 return Err("No arguments given to parse".into());
169 }
170
171 let argvec: Vec<String> = separate_flags(args.map(|s| s.clone()).collect());
172
173 let mut taken_up = Vec::new();
174 let mut new_args = self.arguments.clone();
175
176 for (argname, my_arg) in self.arguments.iter() {
177 for (flag, rest) in argvec.slide().filter(|&(f, _)| {f == &format!("-{}", my_arg.flag) || f == &format!("--{}", argname)}) {
178
179 if let Entry::Occupied(mut e) = new_args.entry(argname.clone()) {
180 let arg = e.get_mut();
181 arg.count = arg.count + 1;
182 taken_up.push(flag);
183
184 match arg.type_ {
185 ArgType::Flag => { arg.val = Some("true".into()); }
186 ArgType::Option => {
187 let err = format!("This option `{}` requires a value you have not provided", argname);
188
189 if let Some(rest) = rest {
190 if is_flag(&rest[0]) || is_long_flag(&rest[0]) {
191 return Err(err);
192 }
193
194 arg.val = Some(rest[0].clone());
195 taken_up.push(&rest[0]);
196 } else {
197 return Err(err);
198 }
199 }
200 ArgType::List | ArgType::Dict => {
201 if let Some(rest) = rest {
202 arg.val = Some(rest.iter()
203 .take_while(|x| !(is_flag(x) || is_long_flag(x)))
204 .fold(String::new(), |mut acc, elem| {
205 acc.push_str(elem);
206 acc.push(' ');
207 acc
208 }));
209
210 taken_up.extend(rest.iter().take_while(|x| !(is_flag(x) || is_long_flag(x))));
211 } else {
212 let err = format!("This option `{}` requires a value you have not provided", argname);
213 return Err(err);
214 }
215 }
216 _ => {}
217 }
218 }
219 }
220 }
221
222 for (_, ref mut v) in new_args.iter_mut().filter(|&(_, ref vv)| vv.val.is_none() && vv.type_.is_positional()) {
223
224 if let Some((_, x)) = argvec.iter().skip(1)
225 .filter(|e| !taken_up.contains(e))
226 .enumerate()
227 .find(|&(i, _)| {
228 if let ArgType::Positional(idx) = v.type_ {
229 idx as usize == i
230 } else {
231 false
232 }
233 }) {
234
235 v.val = Some(x.clone());
236 }
237 }
238
239 if !new_args.iter().all(|(_, v)| !v.required | v.val.is_some()) {
240 return Err("Not all required arguments are found".into());
241 }
242
243 let res = ArgParseResults::new(self.name.clone(), new_args);
244 res.p_args();
245
246 Ok(res)
247 }
248
249 pub fn help(&self) {
271 print!("Usage:\t./{} ", self.name);
272
273 for (argname, info) in self.arguments.iter() {
274 print!("[--{} {}] ", argname, ops(info, argname));
275 }
276 println!("");
277
278 print!("Options:\n\n");
279 for (argname, info) in self.arguments.iter() {
280 print!("--{} (-{})\t", argname, info.flag);
281 print!("Required: {}\t", info.required);
282 print!("Type: {}\n", info.type_);
283 print!("\t");
284
285 let mut i = 0;
286 for c in info.help.chars() {
287 print!("{}", c);
288
289 if i > 60 && c.is_whitespace() {
290 print!("\n\t\t");
291 i = 0;
292 }
293
294 i = i + 1;
295 }
296
297 println!("\n");
298 }
299 }
300}
301
302#[derive(Debug, Clone)]
303pub struct ArgParseResults {
305 arguments: HashMap<String, Arg>,
306 name: String,
307}
308
309impl ArgParseResults {
310
311 fn new(name: String, args: HashMap<String, Arg>) -> ArgParseResults {
312 ArgParseResults { name: name, arguments: args }
313 }
314
315 #[inline]
316 #[cfg(debug_assertions)]
317 fn p_args(&self) {
318 for (k, v) in self.arguments.iter() {
319 println!("{}:{:?}", k, v.val);
320 }
321 }
322
323 #[inline]
324 #[cfg(not(debug_assertions))]
325 fn p_args(&self) {}
326
327 pub fn get<T: FromStr>(&self, name: &str) -> Option<T> {
349 if let Some(ref arg) = self.arguments.get(name.into()) {
350 arg.val.as_ref().and_then(|x| x.parse().ok())
351 } else {
352 None
353 }
354 }
355
356 pub fn get_with<T, P>(&self, name: &str, parser: P) -> Option<T>
383 where P: ArgGetter<T> {
384 if let Some(ref arg) = self.arguments.get(name.into()) {
385 arg.val.as_ref().and_then(|x| parser.get_arg(&x))
386 } else {
387 None
388 }
389 }
390}
391
392pub trait ArgGetter<T> {
398 fn get_arg(self, s: &str) -> Option<T>;
401}
402
403impl<T, F: FnOnce(&str) -> Option<T>> ArgGetter<T> for F {
404 fn get_arg(self, s: &str) -> Option<T> {
405 self(s)
406 }
407}
408
409pub fn vec_parser<T: FromStr>(s: &str) -> Option<Vec<T>> {
413 s.split_whitespace()
414 .map(|x| x.parse())
415 .enumerate()
416 .fold(None, |acc, (idx, elem)| {
417 if let Ok(x) = elem {
418 if idx == 0 {
419 return Some(vec![x]);
420 } else {
421 return acc.map(|mut v| {
422 v.push(x);
423 v
424 });
425 }
426 } else {
427 return None;
428 }
429 })
430}
431
432pub fn hashmap_parser<K, V>(s: &str) -> Option<HashMap<K,V>>
438 where K: FromStr + Hash + Eq,
439 V: FromStr {
440 s.split_whitespace()
441 .map(|x| {
442 let colpos = x.find(':')
443 .expect("No separator found in dict map argument");
444 let (k, v) = x.split_at(colpos);
445 let v = &v[1..];
446 (k, v)
447 })
448 .map(|(k, v)| {
449 k.parse().ok().and_then(|k2|
450 v.parse().ok().map(|v2| (k2, v2)))
451 })
452 .enumerate()
453 .fold(None, |acc, (idx, elem)| {
454 if let Some((k, v)) = elem {
455 if idx == 0 {
456 let mut h = HashMap::new();
457 h.insert(k,v);
458 return Some(h);
459 } else {
460 return acc.map(|mut h| {
461 h.insert(k, v);
462 h
463 });
464 }
465 } else {
466 return None;
467 }
468 })
469}
470
471fn ops(a: &Arg, name: &str) -> String {
472 if a.type_ == ArgType::Option {
473 name.chars().map(|c| c.to_uppercase().next().unwrap_or(c)).collect::<String>()
474 } else if a.type_ == ArgType::List {
475 name.chars().map(|c| c.to_uppercase().next().unwrap_or(c)).chain("...".chars()).collect::<String>()
476 } else if a.type_ == ArgType::Dict {
477 "k:v k2:v2...".into()
478 } else {
479 String::new()
480 }
481}
482
483fn is_flag(s: &str) -> bool {
484 if s.len() < 2 {
485 return false;
486 }
487
488 let v: Vec<char> = s.chars().collect();
489
490 if v[0] == '-' {
491 if v[1].is_alphabetic() {
492 return true;
493 }
494 }
495
496 false
497}
498
499fn is_long_flag(s: &str) -> bool {
500 if s.len() < 3 {
501 return false;
502 }
503
504 let v: Vec<char> = s.chars().collect();
505
506 if v[0] == v[1] && v[1] == '-' {
507 return true;
508 }
509
510 false
511}
512
513fn separate_flags(og: Vec<String>) -> Vec<String> {
514 let mut separated = Vec::new();
515
516 for x in og {
517 if is_long_flag(&x) {
518 separated.push(x);
519 } else if is_flag(&x) {
520 if x.len() == 2 {
521 separated.push(x);
522 } else {
523 for short_flag in x.chars().skip(1) {
524 separated.push(format!("-{}", short_flag));
525 }
526 }
527 } else {
528 separated.push(x);
529 }
530 }
531
532 return separated;
533}
534
535#[cfg(test)]
536mod test {
537 use super::{ArgParser, ArgType, vec_parser, hashmap_parser};
538 use std::collections::HashMap;
539 const LONG_STR: &'static str = r#"Check your proxy settings or contact your network administrator to make sure the proxy server is working. If you don't believe you should be using a proxy server: Go to the Chromium menu > Settings > Show advanced settings... > Change proxy settings... and make sure your configuration is set to "no proxy" or "direct.""#;
540
541 fn setup_1() -> ArgParser {
542 let mut parser = ArgParser::new("ArgParsers".into());
543
544 parser.add_opt("length", None, 'l', true, LONG_STR, ArgType::Option);
545 parser.add_opt("height", None, 'h', true, "Height of user in centimeters", ArgType::Option);
546 parser.add_opt("name", None, 'n', true, "Name of user", ArgType::Option);
547 parser.add_opt("frequencies", None, 'f', false, "User's favorite frequencies", ArgType::List);
548 parser.add_opt("mao", Some("false"), 'm', false, "Is the User Chairman Mao?", ArgType::Flag);
549
550 parser
551 }
552
553 #[test]
554 fn test_parser() {
555 let parser = setup_1();
556
557 let test_1 = "./go -l -60 -h -6001.45e-2 -n Johnny --mao -f 1 2 3 4 5".split_whitespace()
558 .map(|s| s.into())
559 .collect::<Vec<String>>();
560
561 let p_res = parser.parse(test_1.iter()).unwrap();
562
563 assert!(p_res.get("length") == Some(-60));
564 assert_eq!(p_res.get("height"), Some(-6001.45e-2));
565 assert_eq!(p_res.get::<String>("name"), Some("Johnny".into()));
566 assert_eq!(p_res.get_with("frequencies", vec_parser),
567 Some(vec![1,2,3,4,5]));
568 assert_eq!(p_res.get("mao"), Some(true));
569
570 parser.help();
571 }
572
573 #[test]
574 fn test_parser_unrequired() {
575 let parser = setup_1();
576
577 let test_1 = "./go -l -60 -h -6001.45e-2 -n Johnny -f 1 2 3 4 5".split_whitespace()
578 .map(|s| s.into())
579 .collect::<Vec<String>>();
580
581 let p_res = parser.parse(test_1.iter()).unwrap();
582
583 assert!(p_res.get("length") == Some(-60));
584 assert_eq!(p_res.get("height"), Some(-6001.45e-2));
585 assert_eq!(p_res.get::<String>("name"), Some("Johnny".into()));
586 assert_eq!(p_res.get_with("frequencies", vec_parser),
587 Some(vec![1,2,3,4,5]));
588 assert_eq!(p_res.get("mao"), Some(false));
589
590 parser.help();
591 }
592
593 #[test]
594 fn test_parser_unrequired_nodefault() {
595 let parser = setup_1();
596
597 let test_1 = "./go -l -60 -h -6001.45e-2 -n Johnny".split_whitespace()
598 .map(|s| s.into())
599 .collect::<Vec<String>>();
600
601 let p_res = parser.parse(test_1.iter()).unwrap();
602
603 assert!(p_res.get("length") == Some(-60));
604 assert_eq!(p_res.get("height"), Some(-6001.45e-2));
605 assert_eq!(p_res.get::<String>("name"), Some("Johnny".into()));
606 assert_eq!(p_res.get_with::<Vec<u8>, _>("frequencies", vec_parser), None);
607 assert_eq!(p_res.get("mao"), Some(false));
608
609 parser.help();
610 }
611
612 #[test]
613 fn test_parser_dict() {
614 let mut parser = setup_1();
615 parser.add_opt("socks", None, 's', false, "If you wear socks that day", ArgType::Dict);
616
617 let test_1 = "./go -l -60 -h -6001.45e-2 -n Johnny -s Monday:true Friday:false".split_whitespace()
618 .map(|s| s.into())
619 .collect::<Vec<String>>();
620
621 let p_res = parser.parse(test_1.iter()).unwrap();
622
623 assert!(p_res.get("length") == Some(-60));
624 assert_eq!(p_res.get("height"), Some(-6001.45e-2));
625 assert_eq!(p_res.get::<String>("name"), Some("Johnny".into()));
626 assert_eq!(p_res.get_with::<Vec<u8>, _>("frequencies", vec_parser), None);
627 assert_eq!(p_res.get("mao"), Some(false));
628
629 let h = [("Monday", true), ("Friday", false)]
630 .iter()
631 .map(|&(k, v)| (k.into(), v))
632 .collect();
633
634 assert_eq!(p_res.get_with::<HashMap<String, bool>, _>("socks", hashmap_parser),
635 Some(h));
636
637 parser.help();
638 }
639
640 #[test]
641 fn test_parser_positional() {
642 let mut parser = setup_1();
643
644 parser.add_opt("csv", None, 'c', true, "csv input file",
645 ArgType::Positional(0));
646 parser.add_opt("json", None, 'j', true, "json output file",
647 ArgType::Positional(1));
648
649 let test_1 = "./go -l -60 -h -6001.45e-2 -n Johnny crap.csv crap.json".split_whitespace()
650 .map(|s| s.into())
651 .collect::<Vec<String>>();
652
653 let p_res = parser.parse(test_1.iter()).unwrap();
654
655 assert!(p_res.get("length") == Some(-60));
656 assert_eq!(p_res.get("height"), Some(-6001.45e-2));
657 assert_eq!(p_res.get::<String>("name"), Some("Johnny".into()));
658 assert_eq!(p_res.get_with::<Vec<u8>, _>("frequencies", vec_parser), None);
659 assert_eq!(p_res.get("mao"), Some(false));
660 assert_eq!(p_res.get::<String>("csv"), Some("crap.csv".into()));
661 assert_eq!(p_res.get::<String>("json"), Some("crap.json".into()));
662
663 parser.help();
664 }
665}