argparse_rs/
argparser.rs

1//! This module defines and contains all the important
2//! argument parsing functionality. The requisite types
3//! and functions are re-exported at the top-level of
4//! the crate.
5
6use std::collections::HashMap;
7use std::fmt;
8use std::hash::{Hash};
9use std::str::FromStr;
10
11use slide::{Slider};
12
13/// This enum represents the different types of arguments supported
14#[derive(Debug, Clone, PartialEq)]
15pub enum ArgType {
16    /// An argument that takes a value, as in `./go --pic lol.jpg`
17    Option,
18    /// An argument that is a simple flag, as in `rustc --version`
19    Flag,
20    /// Like an `Option`, but takes multiple values, as in 
21    /// `./go --pics 1.png 2.png 3.png`
22    List,
23    /// Like a `List` but takes colon-split key-value pairs, as in
24    /// `./go --pics Monday:1.jpg Tuesday:2.jpg`
25    Dict,
26    /// A positional argument, as in `rustc lib.rs`. The u8 indicates
27    /// The relative position of the position argument (i.e. `Positional(0)`
28    /// indicates that this is the first positional argument
29    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)]
66/// This type represents the state and methods for parsing arguments.
67/// A new parser must be created for every set of arguments you want to parse.
68pub struct ArgParser {
69    arguments: HashMap<String, Arg>,
70    name: String,
71    done: bool,
72}
73
74/// Simple type alias to reduce typing. The return type of
75/// `ArgParser::parse`.
76pub type ParseResult = Result<ArgParseResults, String>;
77
78impl ArgParser {
79    /// Constructs a new `ArgParser`, given the name of the program
80    /// that you want to be printed in help messages
81    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    /// Add another option to parse.
95    /// # Example
96    /// ```
97    /// // add an option that is a `Flag`, with no default value, with
98    /// // a long form of `--verbose`, short form of `v`, that is not
99    /// // required to be passed, and has a default value of `false`
100    ///
101    /// use argparse::{ArgParser, ArgType};
102    ///
103    /// let mut parser = ArgParser::new("runner".into());
104    /// parser.add_opt("verbose", Some("false"), 'v', false,
105    ///     "Whether to produce verbose output", ArgType::Flag);
106    /// ```
107    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    /// Remove an option from parsing consideration.
124    /// # Example
125    /// ```
126    /// // add an option that is a `Flag`, with no default value, with
127    /// // a long form of `--verbose`, short form of `v`, that is not
128    /// // required to be passed, and has a default value of `false`
129    ///
130    /// use argparse::{ArgParser, ArgType};
131    ///
132    /// let mut parser = ArgParser::new("runner".into());
133    /// parser.add_opt("verbose", Some("false"), 'v', false,
134    ///     "Whether to produce verbose output", ArgType::Flag);
135    /// assert!(parser.remove_opt("verbose").is_ok())
136    /// ```
137    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    /// Parse a set of arguments, given the previous configuration
143    /// # Example
144    /// ```
145    /// // add an option that is a `Flag`, with no default value, with
146    /// // a long form of `--verbose`, short form of `v`, that is not
147    /// // required to be passed, and has a default value of `false`
148    ///
149    /// use argparse::{ArgParser, ArgType};
150    ///
151    /// let mut parser = ArgParser::new("runner".into());
152    /// parser.add_opt("verbose", Some("false"), 'v', false,
153    ///     "Whether to produce verbose output", ArgType::Flag);
154    ///
155    /// // Normally you'd get this from std::env::args().iter()
156    /// let test_1 = "./runner --verbose".split_whitespace()
157    ///     .map(|s| s.into())
158    ///     .collect::<Vec<String>>();
159    /// 
160    /// if let Ok(p_res) = parser.parse(test_1.iter()) {
161    ///     // do stuff here
162    /// }
163    /// ```
164    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    /// Prints the help message, which is constructed based on the options
250    /// used
251    /// # Example
252    /// ```
253    /// use argparse::{ArgParser, ArgType};
254    ///
255    /// let mut parser = ArgParser::new("runner".into());
256    /// parser.add_opt("verbose", Some("false"), 'v', false,
257    ///     "Whether to produce verbose output", ArgType::Flag);
258    ///
259    /// // Normally you'd get this from std::env::args().iter()
260    /// let test_1 = "./runner --help".split_whitespace()
261    ///     .map(|s| s.into())
262    ///     .collect::<Vec<String>>();
263    /// 
264    /// if let Ok(p_res) = parser.parse(test_1.iter()) {
265    ///     if let Some(true) = p_res.get("help") {
266    ///         parser.help();
267    ///     }
268    /// }
269    /// ```
270    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)]
303/// This type represents the result ofparsing arguments.
304pub 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    /// Extracts the argument, as long is the value type implements
328    /// `FromStr`
329    /// # Example
330    /// ```
331    /// use argparse::{ArgParser, ArgType};
332    ///
333    /// let mut parser = ArgParser::new("runner".into());
334    /// parser.add_opt("verbose", Some("false"), 'v', false,
335    ///     "Whether to produce verbose output", ArgType::Flag);
336    ///
337    /// // Normally you'd get this from std::env::args().iter()
338    /// let test_1 = "./runner -v".split_whitespace()
339    ///     .map(|s| s.into())
340    ///     .collect::<Vec<String>>();
341    /// 
342    /// if let Ok(p_res) = parser.parse(test_1.iter()) {
343    ///     if let Some(true) = p_res.get::<bool>("verbose") {
344    ///         // be verbose
345    ///     }
346    /// }
347    /// ```
348    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    /// Extracts the argument, using the `ArgGetter<T>` that you provided
357    ///
358    /// # Note
359    /// See documentation for the trait [`ArgGetter`](./trait.ArgGetter.html) for more information
360    /// 
361    /// # Example
362    /// ```
363    /// use argparse::{ArgParser, ArgType};
364    ///
365    /// let mut parser = ArgParser::new("runner".into());
366    /// parser.add_opt("verbose", Some("false"), 'v', false,
367    ///     "Whether to produce verbose output", ArgType::Flag);
368    ///
369    /// // Normally you'd get this from std::env::args().iter()
370    /// let test_1 = "./runner -v".split_whitespace()
371    ///     .map(|s| s.into())
372    ///     .collect::<Vec<String>>();
373    /// 
374    /// let dumb_closure = |_: &str| { Some(true) };
375    /// 
376    /// if let Ok(p_res) = parser.parse(test_1.iter()) {
377    ///     if let Some(true) = p_res.get_with::<bool, _>("verbose", dumb_closure) {
378    ///         // be verbose
379    ///     }
380    /// }
381    /// ```
382    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
392/// Represents something capable of turning a `&str` in the value
393/// type of your choice. Implement this to use with `ArgParseResults::get_with`
394///
395/// # Note
396/// An implementation is provided for all closures of type `F: FnOnce(&str) -> Option<T>`
397pub trait ArgGetter<T> {
398    /// This is the key function that converts from a string 
399    /// to the required value tpe
400    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
409/// Function that parses `List` arguments into `Vec`s.
410/// Provided for user convenience and use as an implementor of
411/// [`ArgGetter`](./trait.ArgGetter.html).
412pub 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
432/// Function that parses `Dict` arguments into `HashMap`s.
433/// Provided for user convenience and use as an implementor of
434/// [`ArgGetter`](./trait.ArgGetter.html).
435/// # Panics
436/// Panics if improper or no separator is found (expects `key:value key2:value2...`)
437pub 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}