abs_cli/
lib.rs

1use std::env;
2
3use coloredpp::Colorize;
4
5#[derive(Clone, Debug)]
6pub struct Opt<'a> {
7    /// option name (--run)
8    pub name: &'a str,
9    /// option short name (-r)
10    pub short: Option<&'a str>,
11    /// (-l, --long)
12    pub manual: &'a str,
13    /// option description
14    pub description: &'a str,
15    /// option value
16    pub value: Option<String>,
17}
18
19#[derive(Clone, Debug)]
20pub struct Arg<'a> {
21    // argument name (run)
22    pub name: &'a str,
23    // argument name (run [options])
24    pub manual: &'a str,
25    // argument description
26    pub description: &'a str,
27    /// option values
28    pub values: Vec<String>,
29}
30
31#[derive(Debug)]
32pub struct CLI<'a> {
33    pub name: Option<&'a str>,
34    pub version: Option<&'a str>,
35    pub description: Option<&'a str>,
36    pub options: Vec<Opt<'a>>,
37    pub args: Vec<Arg<'a>>,
38}
39
40impl<'a> CLI<'a> {
41    pub fn new() -> Self {
42        Self {
43            name: None,
44            version: None,
45            description: None,
46            options: vec![],
47            args: vec![],
48        }
49    }
50
51    /// set the name of the app
52    pub fn name(&mut self, name: &'a str) -> &mut Self {
53        self.name = Some(name);
54        self
55    }
56
57    /// set the app version
58    pub fn version(&mut self, version: &'a str) -> &mut Self {
59        self.version = Some(version);
60        self
61    }
62
63    /// set the app description
64    pub fn description(&mut self, desc: &'a str) -> &mut Self {
65        self.description = Some(desc);
66        self
67    }
68
69    /// add new arg
70    pub fn arg(&mut self, name: &'a str, manual: &'a str, description: &'a str) -> &mut Self {
71        self.args.push(Arg {
72            name,
73            manual,
74            description,
75            values: vec![],
76        });
77        self
78    }
79
80    /// add new option
81    pub fn option(&mut self, manual: &'a str, description: &'a str) -> &mut Self {
82        let mut opt = Opt {
83            name: "",
84            description,
85            short: None,
86            manual,
87            value: None,
88        };
89        if manual.contains(',') {
90            let opts: Vec<&str> = manual.trim().split(',').collect();
91            // handle short command
92            if !opts
93                .get(0)
94                .expect("expected a short option")
95                .trim()
96                .starts_with("-")
97            {
98                panic!(
99                    "short option should start with '-' ({})",
100                    opts.get(0).unwrap()
101                );
102            }
103            opt.short = opts.get(0).cloned();
104
105            // handle long command
106            if !opts
107                .get(1)
108                .expect("expected an option")
109                .trim()
110                .starts_with("--")
111            {
112                panic!("option should start with '--'({})", opts.get(1).unwrap());
113            }
114            opt.name = opts.get(1).unwrap().trim();
115        } else {
116            if manual.trim().starts_with("--") {
117                opt.name = manual.trim();
118            } else {
119                panic!("option should start with '--'({})", manual);
120            }
121        }
122        self.options.push(opt);
123        self
124    }
125
126    /// Parse the CL arguments
127    pub fn parse(&mut self) {
128        let args: Vec<String> = env::args().collect();
129        let mut iter = args.iter().peekable();
130        iter.next();
131
132        while let Some(arg) = iter.next() {
133            match arg.as_str() {
134                "--help" | "-h" => {
135                    self.print_help();
136                }
137                "--version" | "-v" => {
138                    self.print_version();
139                }
140                other => {
141                    if let Some(opt) = self.options.iter_mut().find(|o| {
142                        o.name == other || {
143                            let n: Vec<char> = other.chars().skip(1).collect();
144                            let c: Vec<char> = o.short.unwrap_or("").chars().skip(1).collect();
145                            o.short == Some(other) || n.contains(c.get(0).unwrap_or(&' '))
146                        }
147                    }) {
148                        opt.value = Some("".to_string());
149                        if let Some(value) = iter.peek() {
150                            if !value.starts_with('-') {
151                                opt.value = Some(value.to_string());
152                                iter.next();
153                            }
154                        }
155                    } else if let Some(arg_def) = self.args.iter_mut().find(|a| a.name == other) {
156                        while let Some(value) = iter.peek() {
157                            if value.starts_with('-') {
158                                break;
159                            }
160                            arg_def.values.push(value.to_string());
161                            iter.next();
162                        }
163                        arg_def.values.push("".to_string());
164                    }
165                }
166            }
167        }
168    }
169
170    /// Get the value of a specific option by name
171    pub fn get(&self, name: &str) -> Option<&[String]> {
172        if let Some(opt) = self.options.iter().find(|opt| opt.name == name) {
173            if let Some(ref v) = opt.value {
174                Some(std::slice::from_ref(v))
175            } else {
176                None
177            }
178        } else if let Some(arg) = self.args.iter().find(|arg| arg.name == name) {
179            if !arg.values.is_empty() {
180                Some(&arg.values)
181            } else {
182                None
183            }
184        } else {
185            None
186        }
187    }
188
189    fn print_help(&self) {
190        println!(
191            "\n{}, {}{}",
192            self.name.unwrap_or("My Program").red().bold(),
193            "v".cyan().bold(),
194            self.version.unwrap_or("0.0.1").cyan().bold()
195        );
196        println!("{}", self.description.unwrap_or("").italic());
197        println!("\n{}", "Options:".yellow().bold());
198        println!(
199            "\t{}, {} \t{}",
200            "--help".blue(),
201            "-h".blue(),
202            "Print this message"
203        );
204        println!(
205            "\t{}, {} \t{}",
206            "--version".blue(),
207            "-v".blue(),
208            "Print the application version"
209        );
210        self.options.iter().for_each(|opt| {
211            println!(
212                "\t{}, {} \t{}",
213                opt.name.blue(),
214                opt.short.unwrap_or("").blue(),
215                opt.description
216            )
217        });
218        println!("\n{}", "Arguments:".yellow().bold());
219        self.args
220            .iter()
221            .for_each(|args| println!("\t{} \t{}", args.manual.blue(), args.description));
222    }
223
224    fn print_version(&mut self) {
225        println!(
226            "{}{}",
227            "v".blue().bold(),
228            self.version.unwrap_or("0.0.1").blue().bold()
229        );
230    }
231}