rgparse/
parser.rs

1use std::env;
2use std::collections::HashMap;
3
4use super::parameter::Parameter;
5use super::args::Args;
6
7#[cfg(test)]
8mod tests {
9    use super::{Parser, Parameter};
10
11    #[test]
12    fn can_create_parser() {
13        let _parser = Parser::new("");
14    }
15
16    #[test]
17    fn can_help() {
18        let mut parser = Parser::new("Test Parser 0");
19        parser.add_parameter(Parameter::param("--test", "A test parameter").alias("-t"));
20        parser.help();
21    }
22
23    #[test]
24    fn can_parse() {
25        let mut parser = Parser::new("Test Parser 0");
26        parser.add_parameter(Parameter::param("--test", "A test parameter").alias("-t"));
27        println!("{:?}", parser.parse_args());
28    }
29}
30
31// Internal information about parameters.
32#[derive(Debug)]
33struct ParamInfo {
34    takes_value: bool,
35    description: String,
36}
37
38impl ParamInfo {
39    pub fn new(takes_value: bool, description: String) -> ParamInfo {
40        return ParamInfo{takes_value: takes_value, description: description};
41    }
42}
43
44#[derive(Debug)]
45pub struct Parser {
46    // Description of the parser.
47    description: String,
48    // Maps long parameter names to their descriptions.
49    parameters: HashMap<String, ParamInfo>,
50    // Maps parameter aliases to their parameter names.
51    aliases: HashMap<String, String>,
52    // Keeps track of default arguments
53    default_args: Args,
54}
55
56impl Parser {
57    pub fn new(description: &str) -> Parser {
58        return Parser{description: String::from(description), parameters: HashMap::new(), aliases: HashMap::new(), default_args: Args::new()};
59    }
60
61    pub fn add_parameter(&mut self, param: Parameter) {
62        if let Some(alias) = param.alias {
63            self.aliases.insert(alias, param.name.clone());
64        }
65        // For non-flags, insert the name into default_args.arguments.
66        // These will be checked during parsing to ensure that they are not None.
67        if param.takes_value {
68            self.default_args.arguments.insert(param.name.clone(), param.default);
69        }
70        self.parameters.insert(param.name, ParamInfo::new(param.takes_value, param.description));
71    }
72
73    pub fn help(&self) {
74        // Create an inverse mapping of long parameter names to short ones.
75        let mut parameter_aliases = HashMap::new();
76        for (key, value) in &self.aliases {
77            parameter_aliases.insert(value, key);
78        }
79        // Usage message will just print parameter names.
80        let mut usage = format!("{}\nUsage: {}", self.description, env::current_exe().unwrap().to_str().unwrap());
81        for (mut param, _) in &self.parameters {
82            let metavar = String::from(param.to_uppercase().trim_start_matches("-"));
83            if let Some(short) = parameter_aliases.get(param) {
84                param = short;
85            }
86            usage += &format!(" [{} {}]", param, metavar);
87        }
88        println!("{}", usage);
89        // Full help message also includes descriptions for each parameter.
90        for (param, info) in &self.parameters {
91            let mut print_arg = String::from("\t");
92            if let Some(short) = parameter_aliases.get(param) {
93                print_arg += &format!("{},", short);
94            }
95            print_arg += &format!("\t{}", param);
96            println!("{}\t\t{}", print_arg, info.description);
97        }
98    }
99
100    pub fn parse_args(&self) -> Args {
101        fn fail(parser: &Parser) -> ! {
102            parser.help();
103            std::process::exit(1);
104        }
105
106        let mut args = self.default_args.clone();
107        // Skip over the executable name.
108        let mut args_iter = env::args().into_iter().skip(1);
109        while let Some(arg) = args_iter.next() {
110            // First check if this parameter is an alias. If so, get the full parameter name.
111            let mut full_arg = arg.clone();
112            if let Some(full_name) = self.aliases.get(&arg) {
113                full_arg = full_name.clone();
114            }
115            // If the argument was found, we will add it to the Args struct.
116            if let Some(info) = self.parameters.get(&full_arg) {
117                if info.takes_value {
118                    match args_iter.next() {
119                        // If the value for this argument is invalid, print help message and exit.
120                        Some(value) => args.arguments.insert(full_arg, Some(value)),
121                        None => fail(&self),
122                    };
123                } else {
124                    args.flags.insert(full_arg);
125                }
126            } else if arg == "-h" || arg == "--help" {
127                // If -h/--help has not been overriden in the parser (i.e. was not found in the
128                // if condition.), then we print and exit here.
129                self.help();
130                std::process::exit(0);
131            } else {
132                // Otherwise we have to assume that this is a positional argument.
133                args.positional.push(arg);
134            }
135        }
136
137        // All arguments should either have a default value, or a user provided one at this point.
138        let mut missing_args = Vec::new();
139        for (arg, value_opt) in &args.arguments {
140            if value_opt.is_none() {
141                missing_args.push(arg);
142            }
143        }
144        if !missing_args.is_empty() {
145            println!("Missing required arguments: {:?}", missing_args);
146            fail(&self);
147        }
148
149        return args;
150    }
151}