1use std::{collections::HashMap, fmt};
2#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3pub enum OptValue {
4 STRING(String),
5 INT64(i64),
6 UINT64(u64),
7 BOOL(bool),
8}
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct Opt {
12 short: String,
13 long: String,
14 value: OptValue,
15 description: String,
16}
17
18impl Opt {
19 pub fn new(short: &str, long: &str, value: OptValue, description: &str) -> Opt {
20 Opt {
21 short: short.to_string(),
22 long: long.to_string(),
23 value: value,
24 description: description.to_string(),
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
30pub struct CommandError {}
31
32impl CommandError {
33 pub fn new() -> CommandError {
34 CommandError {}
35 }
36}
37
38impl fmt::Display for CommandError {
39 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40 write!(f, "CommandError")
41 }
42}
43
44pub trait Command {
45 fn opts(&self) -> Vec<Opt> {
46 vec![]
47 }
48 fn run(&self, args: Vec<String>, opts: HashMap<Opt, OptValue>) -> Result<(), CommandError>;
49}
50
51pub struct CommandManager {
52 commands: HashMap<Vec<String>, Box<dyn Command + 'static>>,
53}
54
55pub fn find_opt(opts: HashMap<Opt, OptValue>, key: String) -> Option<OptValue> {
56 for (opt, value) in opts.iter() {
57 if key.eq(format!("--{}", opt.long).as_str()) {
58 return Some(value.clone());
59 }
60
61 if key.eq(format!("-{}", opt.short).as_str()) {
62 return Some(value.clone());
63 }
64 }
65 None
66}
67
68impl CommandManager {
69 pub fn new() -> CommandManager {
70 CommandManager {
71 commands: HashMap::new(),
72 }
73 }
74
75 pub fn add_command(&mut self, name: String, command: Box<dyn Command>) {
76 let name = name.split_whitespace().map(|s| s.to_string()).collect();
77 self.commands.insert(name, command);
78 }
79
80 fn get_command(&self, args: Vec<String>) -> Option<&dyn Command> {
81 let keys: Vec<&Vec<String>> = self.commands.keys().collect();
83
84 for key in keys.iter().copied() {
85 if key.len() > args.len() {
86 println!("Key is longer than args");
87 continue;
88 }
89
90 let mut matched = true;
91
92 let args_without_options: Vec<String> = args
93 .iter()
94 .filter(|arg| !arg.starts_with("-"))
95 .map(|arg| arg.clone())
96 .collect();
97
98 for i in 0..key.len() {
99 if !key[i].eq(&args_without_options[i]) {
100 println!(
101 "Key does not match {:?} != {:?}",
102 key[i], args_without_options[i]
103 );
104 matched = false;
105 break;
106 }
107 }
108
109 if matched {
110 println!("Matched key: {:?}", key);
111 return Some(self.commands.get(key).unwrap().as_ref());
112 }
113 }
114
115 println!("No command matched");
116
117 None
118 }
119
120 fn print_help(&self) {
121 println!("Available commands:");
122 for (key, command) in self.commands.iter() {
123 println!("{}", key.join(" "));
124
125 for opt in command.opts() {
126 println!(
127 " -{}, --{}: {}",
128 opt.short, opt.long, opt.description
129 );
130 }
131 }
132 }
133
134 fn parse_opts(&self, args: Vec<String>, opts: Vec<Opt>) -> HashMap<Opt, OptValue> {
135 let mut result = HashMap::new();
136
137 for (pos, arg) in args.iter().enumerate() {
138 if !arg.starts_with("-") {
139 continue;
140 }
141
142
143 let opt_result = opts.iter().find(|opt| {
144 arg.eq(format!("-{}", opt.short).as_str())
145 || arg.eq(format!("--{}", opt.long).as_str())
146 });
147
148 if opt_result.is_none() {
149 println!("No opt found for {:?}", arg);
150 continue;
151 }
152
153 let opt = opt_result.unwrap();
154
155 match &opt.value {
156 OptValue::INT64(default_val) => {
157 let value = args
158 .get(pos + 1)
159 .unwrap_or(&"".to_string())
160 .parse::<i64>()
161 .unwrap_or(*default_val);
162 result.insert(opt.clone(), OptValue::INT64(value));
163 }
164 OptValue::UINT64(default_value) => {
165 let value = args
166 .get(pos + 1)
167 .unwrap_or(&"".to_string())
168 .parse::<u64>()
169 .unwrap_or(*default_value);
170 result.insert(opt.clone(), OptValue::UINT64(value));
171 }
172 OptValue::STRING(default_value) => {
173 let value = args.get(pos + 1).unwrap_or(&default_value).clone();
174 result.insert(opt.clone(), OptValue::STRING(value.clone()));
175 }
176 OptValue::BOOL(_) => {
177 let value = args
178 .get(pos + 1)
179 .unwrap_or(&"".to_string())
180 .parse::<bool>()
181 .unwrap_or(true);
182 result.insert(opt.clone(), OptValue::BOOL(value));
183 }
184 }
185 }
186
187 result
188 }
189
190 pub fn run(&self, args: Vec<String>) -> Result<(), CommandError> {
191 if args.len() == 0 {
192 self.print_help();
193 return Ok(());
194 }
195
196 if args[0].eq("--help") || args[0].eq("-h") {
197 self.print_help();
198 return Ok(());
199 }
200
201 let command = self.get_command(args.clone()).ok_or(CommandError::new())?;
202
203 let opts = self.parse_opts(args.clone(), command.opts());
204
205 return command.run(args, opts);
206 }
207}