1use std::env;
2
3use coloredpp::Colorize;
4
5#[derive(Clone, Debug)]
6pub struct Opt<'a> {
7 pub name: &'a str,
9 pub short: Option<&'a str>,
11 pub manual: &'a str,
13 pub description: &'a str,
15 pub value: Option<String>,
17}
18
19#[derive(Clone, Debug)]
20pub struct Arg<'a> {
21 pub name: &'a str,
23 pub manual: &'a str,
25 pub description: &'a str,
27 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 pub fn name(&mut self, name: &'a str) -> &mut Self {
53 self.name = Some(name);
54 self
55 }
56
57 pub fn version(&mut self, version: &'a str) -> &mut Self {
59 self.version = Some(version);
60 self
61 }
62
63 pub fn description(&mut self, desc: &'a str) -> &mut Self {
65 self.description = Some(desc);
66 self
67 }
68
69 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 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 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 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 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 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}