kern/
cli.rs

1//! Command-line interface utilities
2
3use std::collections::HashMap;
4use std::str::FromStr;
5
6/// Builder for command-line parsing (Command)
7///
8/// # Example
9/// ```
10/// use kern::CliBuilder;
11/// use std::env;
12///
13/// let args: Vec<String> = env::args().collect();
14/// let command = CliBuilder::new().options(&["option"]).build(&args);
15/// assert_eq!(command.command().contains("rustdoctest"), true);
16/// ```
17#[derive(Clone, Debug, Default)]
18pub struct CliBuilder<'a> {
19    // raw: &'a str,
20    options: &'a [&'a str],
21    paramopts: &'a [&'a str],
22}
23
24impl<'a> CliBuilder<'a> {
25    /// Empty configuration for command-line parsing
26    pub fn new() -> Self {
27        Self {
28            // raw: "",
29            options: &[],
30            paramopts: &[],
31        }
32    }
33
34    /// Set options list
35    pub fn options(mut self, options: &'a [&str]) -> Self {
36        self.options = options;
37        self
38    }
39
40    /// Set list for parameter or option (not a parameter if value starts with "-")
41    pub fn paramopts(mut self, paramopts: &'a [&str]) -> Self {
42        self.paramopts = paramopts;
43        self
44    }
45
46    /// Create a new Command from raw command line arguments
47    pub fn build(self, raw: &'a [String]) -> Command<'a> {
48        // define command nam
49        let command = if raw.is_empty() { "" } else { &raw[0] };
50
51        // define variables
52        let mut parameters: HashMap<&str, &str> = HashMap::new();
53        let mut options: Vec<&str> = Vec::new();
54        let mut arguments: Vec<&str> = Vec::new();
55
56        // define iteration parameters
57        let mut parameter = "";
58        let mut is_parameter = false;
59
60        // iterate through raw arguments
61        for (index, argument) in raw.iter().enumerate() {
62            // check if first argument (command name)
63            if index == 0 {
64                // skip
65                continue;
66            }
67
68            // check if previous argument is a parameter
69            if is_parameter {
70                // insert parameter into map
71                parameters.insert(parameter, argument);
72
73                // empty parameter, compile safe
74                parameter = "";
75
76                // next is not a parameter
77                is_parameter = false;
78            } else {
79                // closure to process parameters using equal sign
80                let process_split = |parameters: &mut HashMap<&'a str, &'a str>,
81                                     parameter: &mut &'a str,
82                                     is_parameter: &mut bool,
83                                     argument: &'a str| {
84                    // split argument
85                    let splits = argument.splitn(2, '=');
86
87                    // loop through one or two splitted parameters
88                    for split in splits {
89                        // check if second
90                        if *is_parameter {
91                            // insert parameter into map
92                            parameters.insert(parameter, split);
93
94                            // proceed with next argument
95                            *is_parameter = false;
96                        } else {
97                            // store parameter name
98                            *parameter = split;
99
100                            // next on is a parameter
101                            *is_parameter = true;
102                        }
103                    }
104                };
105
106                // check if argument is a parameter
107                if let Some(cut) = argument.strip_prefix("--") {
108                    // check if option
109                    if self.options.contains(&cut)
110                        || self.paramopts.contains(&cut)
111                            && raw
112                                .get(index + 1)
113                                .unwrap_or(&String::new())
114                                .starts_with('-')
115                    {
116                        // add to options
117                        options.push(cut);
118
119                        // continue with next argument
120                        continue;
121                    }
122
123                    // process parameter
124                    process_split(&mut parameters, &mut parameter, &mut is_parameter, cut);
125                // check if argument is an option or short parameter
126                } else if let Some(cut) = argument.strip_prefix('-') {
127                    // check if option
128                    if self.options.contains(&cut)
129                        || self.paramopts.contains(&cut)
130                            && raw
131                                .get(index + 1)
132                                .unwrap_or(&String::new())
133                                .starts_with('-')
134                    {
135                        // add to options
136                        options.push(cut);
137
138                        // continue with next argument
139                        continue;
140                    } else if cut.len() >= 2 && !cut.contains('=') {
141                        // add all options to options
142                        for i in 0..cut.len() {
143                            // add only one character
144                            options.push(match cut.get(i..=i) {
145                                Some(option) => option,
146                                None => continue,
147                            });
148                        }
149
150                        // continue with next argument
151                        continue;
152                    } else if cut == "-" {
153                        // process as argument
154                        arguments.push(cut);
155                        continue;
156                    }
157
158                    // process parameter
159                    process_split(&mut parameters, &mut parameter, &mut is_parameter, cut);
160                } else {
161                    // add to arguments
162                    arguments.push(argument);
163                }
164            }
165        }
166
167        // last parameter without value must be option
168        if is_parameter {
169            // add parameter to options
170            options.push(parameter);
171        }
172
173        // return Command
174        Command::create(command, parameters, options, arguments)
175    }
176}
177
178/// Command represents a command parsed from the command-line
179/// Use CliBuilder to build a parsed Command
180///
181/// # Example
182/// ```
183/// use kern::CliBuilder;
184/// use std::env;
185///
186/// let args: Vec<String> = env::args().collect();
187/// let command = CliBuilder::new().options(&["option"]).build(&args);
188/// assert_eq!(command.command().contains("rustdoctest"), true);
189/// ```
190#[derive(Clone, Debug)]
191pub struct Command<'a> {
192    /// Command name
193    command: &'a str,
194
195    /// Map of parameters
196    parameters: HashMap<&'a str, &'a str>,
197
198    /// List of options
199    options: Vec<&'a str>,
200
201    /// List of arguments
202    arguments: Vec<&'a str>,
203}
204
205// Command implementation
206impl<'a> Command<'a> {
207    /// Get command name
208    pub fn command(&self) -> &'a str {
209        // return comand name
210        self.command
211    }
212
213    /// Get all parameters
214    pub fn parameters(&self) -> &HashMap<&'a str, &'a str> {
215        // return map of parameters
216        &self.parameters
217    }
218
219    /// Get all arguments
220    pub fn arguments(&self) -> &Vec<&'a str> {
221        // return arguments list
222        &self.arguments
223    }
224
225    /// Get specific parameter or default
226    pub fn parameter<T: FromStr>(&self, name: &str, default: T) -> T {
227        // return specific parameter or default
228        match self.parameters.get(name) {
229            Some(parameter) => parameter.parse().unwrap_or(default),
230            None => default,
231        }
232    }
233
234    /// Get specific parameter or default as &str
235    pub fn param(&self, name: &str, default: &'a str) -> &str {
236        // return specific parameter or default as &str
237        match self.parameters.get(name) {
238            Some(parameter) => parameter,
239            None => default,
240        }
241    }
242
243    /// Get all options
244    pub fn options(&self) -> &Vec<&str> {
245        // return options list
246        &self.options
247    }
248
249    /// Check if option provided
250    pub fn option(&self, name: &str) -> bool {
251        // return whether the option is provided
252        self.options.contains(&name)
253    }
254
255    /// Get argument at specific index or default
256    pub fn argument<T: FromStr>(&self, index: usize, default: T) -> T {
257        // return argument at specific index or default
258        match self.arguments.get(index) {
259            Some(argument) => argument.parse().unwrap_or(default),
260            None => default,
261        }
262    }
263
264    /// Get argument at specific index or default as &str
265    pub fn arg(&self, index: usize, default: &'a str) -> &str {
266        // return argument at specific index as &str
267        match self.arguments.get(index) {
268            Some(argument) => argument,
269            None => default,
270        }
271    }
272
273    /// Create a new Command from raw command line arguments without options
274    #[deprecated]
275    pub fn without_options(raw: &'a [String]) -> Self {
276        // return Command
277        CliBuilder::new().build(raw)
278    }
279
280    /// Create a new Command from raw command line arguments
281    /// Provide the options list as &[&str]
282    #[deprecated]
283    pub fn from(raw: &'a [String], options: &'a [&str]) -> Self {
284        // return Command
285        CliBuilder::new().options(options).build(raw)
286    }
287
288    /// Create Command from given values
289    fn create(
290        command: &'a str,
291        parameters: HashMap<&'a str, &'a str>,
292        options: Vec<&'a str>,
293        arguments: Vec<&'a str>,
294    ) -> Self {
295        Self {
296            command,
297            parameters,
298            options,
299            arguments,
300        }
301    }
302}