]>
Commit | Line | Data |
---|---|---|
b7329c8a | 1 | use failure::*; |
25500cfa DM |
2 | use serde_json::Value; |
3 | ||
4 | use std::collections::{HashMap, HashSet}; | |
4968bc3a | 5 | |
ef2f2efb | 6 | use crate::api_schema::*; |
255f378a | 7 | use crate::api_schema::api_handler::*; |
dc9a007b | 8 | use crate::api_schema::router::*; |
339ddfcb | 9 | use crate::api_schema::format::*; |
dc9a007b | 10 | //use crate::api_schema::config::*; |
0f253593 | 11 | use super::environment::CliEnvironment; |
6049b71f | 12 | |
4de0e142 | 13 | use super::getopts; |
6049b71f | 14 | |
255f378a DM |
15 | pub const OUTPUT_FORMAT: Schema = |
16 | StringSchema::new("Output format.") | |
17 | .format(&ApiStringFormat::Enum(&["text", "json", "json-pretty"])) | |
18 | .schema(); | |
25500cfa DM |
19 | |
20 | /// Helper function to format and print result | |
21 | /// | |
22 | /// This is implemented for machine generatable formats 'json' and | |
23 | /// 'json-pretty'. The 'text' format needs to be handled somewhere | |
24 | /// else. | |
f6ede796 | 25 | pub fn format_and_print_result(result: &Value, output_format: &str) { |
25500cfa DM |
26 | |
27 | if output_format == "json-pretty" { | |
28 | println!("{}", serde_json::to_string_pretty(&result).unwrap()); | |
29 | } else if output_format == "json" { | |
30 | println!("{}", serde_json::to_string(&result).unwrap()); | |
31 | } else { | |
32 | unimplemented!(); | |
33 | } | |
34 | } | |
6adb40ee | 35 | |
2f3f2bb7 DM |
36 | fn generate_usage_str( |
37 | prefix: &str, | |
38 | cli_cmd: &CliCommand, | |
39 | format: DocumentationFormat, | |
40 | indent: &str) -> String { | |
6adb40ee DM |
41 | |
42 | let arg_param = &cli_cmd.arg_param; | |
43 | let fixed_param = &cli_cmd.fixed_param; | |
255f378a DM |
44 | let schema = cli_cmd.info.parameters; |
45 | ||
6adb40ee DM |
46 | let mut done_hash = HashSet::<&str>::new(); |
47 | let mut args = String::new(); | |
48 | ||
49 | for positional_arg in arg_param { | |
255f378a DM |
50 | match schema.lookup(positional_arg) { |
51 | Some((optional, param_schema)) => { | |
f33fa273 | 52 | args.push(' '); |
9bc3ddb8 | 53 | |
255f378a DM |
54 | let is_array = if let Schema::Array(_) = param_schema { true } else { false }; |
55 | if optional { args.push('['); } | |
9bc3ddb8 | 56 | if is_array { args.push('{'); } |
f33fa273 | 57 | args.push('<'); args.push_str(positional_arg); args.push('>'); |
9bc3ddb8 | 58 | if is_array { args.push('}'); } |
255f378a | 59 | if optional { args.push(']'); } |
f33fa273 DM |
60 | |
61 | done_hash.insert(positional_arg); | |
62 | } | |
63 | None => panic!("no such property '{}' in schema", positional_arg), | |
64 | } | |
6adb40ee DM |
65 | } |
66 | ||
2f3f2bb7 DM |
67 | let mut arg_descr = String::new(); |
68 | for positional_arg in arg_param { | |
255f378a | 69 | let (_optional, param_schema) = schema.lookup(positional_arg).unwrap(); |
2f3f2bb7 | 70 | let param_descr = get_property_description( |
255f378a | 71 | positional_arg, param_schema, ParameterDisplayStyle::Fixed, format); |
2f3f2bb7 DM |
72 | arg_descr.push_str(¶m_descr); |
73 | } | |
74 | ||
6adb40ee DM |
75 | let mut options = String::new(); |
76 | ||
255f378a | 77 | for (prop, optional, param_schema) in schema.properties { |
6adb40ee | 78 | if done_hash.contains(prop) { continue; } |
255f378a | 79 | if fixed_param.contains_key(prop) { continue; } |
b7329c8a | 80 | |
255f378a | 81 | let type_text = get_schema_type_text(param_schema, ParameterDisplayStyle::Arg); |
6adb40ee DM |
82 | |
83 | if *optional { | |
84 | ||
b55cee92 | 85 | if options.len() > 0 { options.push('\n'); } |
255f378a | 86 | options.push_str(&get_property_description(prop, param_schema, ParameterDisplayStyle::Arg, format)); |
6adb40ee DM |
87 | |
88 | } else { | |
79bc7345 | 89 | args.push_str(" --"); args.push_str(prop); |
6adb40ee DM |
90 | args.push(' '); |
91 | args.push_str(&type_text); | |
92 | } | |
93 | ||
94 | done_hash.insert(prop); | |
95 | } | |
96 | ||
698d9d44 DM |
97 | let option_indicator = if options.len() > 0 { " [OPTIONS]" } else { "" }; |
98 | ||
8b6dd224 | 99 | let mut text = match format { |
2f3f2bb7 | 100 | DocumentationFormat::Short => { |
698d9d44 | 101 | return format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator); |
2f3f2bb7 DM |
102 | } |
103 | DocumentationFormat::Long => { | |
698d9d44 | 104 | format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator) |
2f3f2bb7 DM |
105 | } |
106 | DocumentationFormat::Full => { | |
255f378a | 107 | format!("{}{}{}{}\n\n{}\n\n", indent, prefix, args, option_indicator, schema.description) |
2f3f2bb7 | 108 | } |
8b6dd224 | 109 | DocumentationFormat::ReST => { |
255f378a | 110 | format!("``{}{}{}``\n\n{}\n\n", prefix, args, option_indicator, schema.description) |
2f3f2bb7 | 111 | } |
8b6dd224 DM |
112 | }; |
113 | ||
114 | if arg_descr.len() > 0 { | |
8b6dd224 | 115 | text.push_str(&arg_descr); |
698d9d44 | 116 | text.push('\n'); |
8b6dd224 DM |
117 | } |
118 | if options.len() > 0 { | |
8b6dd224 | 119 | text.push_str(&options); |
698d9d44 | 120 | text.push('\n'); |
2f3f2bb7 | 121 | } |
8b6dd224 | 122 | text |
6adb40ee DM |
123 | } |
124 | ||
125 | fn print_simple_usage_error(prefix: &str, cli_cmd: &CliCommand, err: Error) { | |
126 | ||
2f3f2bb7 | 127 | let usage = generate_usage_str(prefix, cli_cmd, DocumentationFormat::Long, ""); |
8b6dd224 | 128 | eprint!("Error: {}\nUsage: {}", err, usage); |
b7329c8a DM |
129 | } |
130 | ||
255f378a | 131 | pub fn print_help( |
698d9d44 DM |
132 | top_def: &CommandLineInterface, |
133 | mut prefix: String, | |
134 | args: &Vec<String>, | |
135 | verbose: Option<bool>, | |
136 | ) { | |
137 | let mut iface = top_def; | |
138 | ||
139 | for cmd in args { | |
140 | if let CommandLineInterface::Nested(map) = iface { | |
141 | if let Some(subcmd) = find_command(map, cmd) { | |
142 | iface = subcmd; | |
143 | prefix.push(' '); | |
144 | prefix.push_str(cmd); | |
145 | continue; | |
146 | } | |
147 | } | |
148 | eprintln!("no such command '{}'", cmd); | |
149 | std::process::exit(-1); | |
150 | } | |
151 | ||
152 | let format = match verbose.unwrap_or(false) { | |
153 | true => DocumentationFormat::Full, | |
154 | false => DocumentationFormat::Short, | |
155 | }; | |
156 | ||
157 | match iface { | |
158 | CommandLineInterface::Nested(map) => { | |
159 | println!("Usage:\n\n{}", generate_nested_usage(&prefix, map, format)); | |
160 | } | |
161 | CommandLineInterface::Simple(cli_cmd) => { | |
162 | println!("Usage: {}", generate_usage_str(&prefix, cli_cmd, format, "")); | |
163 | } | |
164 | } | |
165 | } | |
166 | ||
167 | fn handle_simple_command( | |
255f378a | 168 | _top_def: &CommandLineInterface, |
698d9d44 DM |
169 | prefix: &str, |
170 | cli_cmd: &CliCommand, | |
171 | args: Vec<String>, | |
172 | ) { | |
4968bc3a | 173 | |
33256db6 DM |
174 | let (params, rest) = match getopts::parse_arguments( |
175 | &args, &cli_cmd.arg_param, &cli_cmd.info.parameters) { | |
176 | Ok((p, r)) => (p, r), | |
177 | Err(err) => { | |
6adb40ee | 178 | print_simple_usage_error(prefix, cli_cmd, err.into()); |
33256db6 DM |
179 | std::process::exit(-1); |
180 | } | |
181 | }; | |
b7329c8a DM |
182 | |
183 | if !rest.is_empty() { | |
33256db6 | 184 | let err = format_err!("got additional arguments: {:?}", rest); |
6adb40ee | 185 | print_simple_usage_error(prefix, cli_cmd, err); |
33256db6 | 186 | std::process::exit(-1); |
b7329c8a DM |
187 | } |
188 | ||
33256db6 DM |
189 | let mut rpcenv = CliEnvironment::new(); |
190 | ||
255f378a DM |
191 | match cli_cmd.info.handler { |
192 | ApiHandler::Sync(handler) => { | |
193 | match (handler)(params, &cli_cmd.info, &mut rpcenv) { | |
194 | Ok(value) => { | |
195 | if value != Value::Null { | |
196 | println!("Result: {}", serde_json::to_string_pretty(&value).unwrap()); | |
197 | } | |
198 | } | |
199 | Err(err) => { | |
200 | eprintln!("Error: {}", err); | |
201 | std::process::exit(-1); | |
202 | } | |
25500cfa | 203 | } |
33256db6 | 204 | } |
255f378a DM |
205 | ApiHandler::Async(_) => { |
206 | //fixme | |
207 | unimplemented!(); | |
33256db6 | 208 | } |
255f378a | 209 | } |
b7329c8a DM |
210 | } |
211 | ||
6460764d | 212 | fn find_command<'a>(def: &'a CliCommandMap, name: &str) -> Option<&'a CommandLineInterface> { |
baed30b7 | 213 | |
6460764d | 214 | if let Some(sub_cmd) = def.commands.get(name) { |
baed30b7 DM |
215 | return Some(sub_cmd); |
216 | }; | |
217 | ||
218 | let mut matches: Vec<&str> = vec![]; | |
219 | ||
6460764d | 220 | for cmd in def.commands.keys() { |
baed30b7 DM |
221 | if cmd.starts_with(name) { |
222 | matches.push(cmd); } | |
223 | } | |
224 | ||
225 | if matches.len() != 1 { return None; } | |
226 | ||
6460764d | 227 | if let Some(sub_cmd) = def.commands.get(matches[0]) { |
baed30b7 DM |
228 | return Some(sub_cmd); |
229 | }; | |
230 | ||
231 | None | |
232 | } | |
233 | ||
6adb40ee DM |
234 | fn print_nested_usage_error(prefix: &str, def: &CliCommandMap, err: Error) { |
235 | ||
698d9d44 | 236 | let usage = generate_nested_usage(prefix, def, DocumentationFormat::Short); |
6adb40ee | 237 | |
698d9d44 | 238 | eprintln!("Error: {}\n\nUsage:\n\n{}", err, usage); |
6adb40ee | 239 | } |
33256db6 | 240 | |
8b6dd224 | 241 | fn generate_nested_usage(prefix: &str, def: &CliCommandMap, format: DocumentationFormat) -> String { |
6adb40ee DM |
242 | |
243 | let mut cmds: Vec<&String> = def.commands.keys().collect(); | |
244 | cmds.sort(); | |
245 | ||
8b6dd224 DM |
246 | let mut usage = String::new(); |
247 | ||
6adb40ee DM |
248 | for cmd in cmds { |
249 | let new_prefix = format!("{} {}", prefix, cmd); | |
250 | ||
251 | match def.commands.get(cmd).unwrap() { | |
252 | CommandLineInterface::Simple(cli_cmd) => { | |
8b6dd224 DM |
253 | if usage.len() > 0 && format == DocumentationFormat::ReST { |
254 | usage.push_str("----\n\n"); | |
255 | } | |
256 | usage.push_str(&generate_usage_str(&new_prefix, cli_cmd, format, "")); | |
6adb40ee DM |
257 | } |
258 | CommandLineInterface::Nested(map) => { | |
8b6dd224 | 259 | usage.push_str(&generate_nested_usage(&new_prefix, map, format)); |
6adb40ee DM |
260 | } |
261 | } | |
262 | } | |
33256db6 | 263 | |
8b6dd224 | 264 | usage |
33256db6 DM |
265 | } |
266 | ||
698d9d44 DM |
267 | fn handle_nested_command( |
268 | top_def: &CommandLineInterface, | |
269 | prefix: &str, | |
270 | def: &CliCommandMap, | |
271 | mut args: Vec<String>, | |
272 | ) { | |
b7329c8a DM |
273 | |
274 | if args.len() < 1 { | |
6460764d | 275 | let mut cmds: Vec<&String> = def.commands.keys().collect(); |
b7329c8a DM |
276 | cmds.sort(); |
277 | ||
278 | let list = cmds.iter().fold(String::new(),|mut s,item| { | |
279 | if !s.is_empty() { s+= ", "; } | |
280 | s += item; | |
281 | s | |
282 | }); | |
283 | ||
33256db6 | 284 | let err = format_err!("no command specified.\nPossible commands: {}", list); |
6adb40ee | 285 | print_nested_usage_error(prefix, def, err); |
33256db6 | 286 | std::process::exit(-1); |
b7329c8a DM |
287 | } |
288 | ||
289 | let command = args.remove(0); | |
290 | ||
baed30b7 | 291 | let sub_cmd = match find_command(def, &command) { |
b7329c8a | 292 | Some(cmd) => cmd, |
33256db6 DM |
293 | None => { |
294 | let err = format_err!("no such command '{}'", command); | |
6adb40ee | 295 | print_nested_usage_error(prefix, def, err); |
33256db6 DM |
296 | std::process::exit(-1); |
297 | } | |
b7329c8a DM |
298 | }; |
299 | ||
33256db6 DM |
300 | let new_prefix = format!("{} {}", prefix, command); |
301 | ||
b7329c8a | 302 | match sub_cmd { |
9f6ab1fc | 303 | CommandLineInterface::Simple(cli_cmd) => { |
698d9d44 | 304 | handle_simple_command(top_def, &new_prefix, cli_cmd, args); |
b7329c8a | 305 | } |
9f6ab1fc | 306 | CommandLineInterface::Nested(map) => { |
698d9d44 | 307 | handle_nested_command(top_def, &new_prefix, map, args); |
b7329c8a DM |
308 | } |
309 | } | |
b7329c8a DM |
310 | } |
311 | ||
30d2e99c DM |
312 | fn print_property_completion( |
313 | schema: &Schema, | |
314 | name: &str, | |
315 | completion_functions: &HashMap<String, CompletionFunction>, | |
496a6784 DM |
316 | arg: &str, |
317 | param: &HashMap<String, String>, | |
318 | ) { | |
30d2e99c | 319 | if let Some(callback) = completion_functions.get(name) { |
496a6784 | 320 | let list = (callback)(arg, param); |
30d2e99c DM |
321 | for value in list { |
322 | if value.starts_with(arg) { | |
323 | println!("{}", value); | |
324 | } | |
325 | } | |
326 | return; | |
327 | } | |
328 | ||
38555b50 | 329 | if let Schema::String(StringSchema { format: Some(format), ..} ) = schema { |
255f378a DM |
330 | if let ApiStringFormat::Enum(list) = format { |
331 | for value in list.iter() { | |
38555b50 DM |
332 | if value.starts_with(arg) { |
333 | println!("{}", value); | |
334 | } | |
335 | } | |
336 | return; | |
337 | } | |
338 | } | |
653b1ca1 | 339 | println!(); |
f46403cc | 340 | } |
b6e8dd39 | 341 | |
496a6784 | 342 | fn record_done_argument(done: &mut HashMap<String, String>, parameters: &ObjectSchema, key: &str, value: &str) { |
f46403cc | 343 | |
255f378a DM |
344 | if let Some((_, schema)) = parameters.lookup(key) { |
345 | match schema { | |
496a6784 DM |
346 | Schema::Array(_) => { /* do nothing ?? */ } |
347 | _ => { done.insert(key.to_owned(), value.to_owned()); } | |
f46403cc DM |
348 | } |
349 | } | |
350 | } | |
351 | ||
352 | fn print_simple_completion( | |
353 | cli_cmd: &CliCommand, | |
496a6784 | 354 | done: &mut HashMap<String, String>, |
1a71509a DM |
355 | all_arg_param: &[&str], // this is always the full list |
356 | arg_param: &[&str], // we remove done arguments | |
6949d915 | 357 | args: &[String], |
f46403cc DM |
358 | ) { |
359 | // fixme: arg_param, fixed_param | |
360 | //eprintln!("COMPL: {:?} {:?} {}", arg_param, args, args.len()); | |
361 | ||
362 | if !arg_param.is_empty() { | |
363 | let prop_name = arg_param[0]; | |
f46403cc | 364 | if args.len() > 1 { |
255f378a | 365 | record_done_argument(done, cli_cmd.info.parameters, prop_name, &args[0]); |
1a71509a | 366 | print_simple_completion(cli_cmd, done, arg_param, &arg_param[1..], &args[1..]); |
f46403cc | 367 | return; |
7f4e639b | 368 | } else if args.len() == 1 { |
255f378a DM |
369 | record_done_argument(done, cli_cmd.info.parameters, prop_name, &args[0]); |
370 | if let Some((_, schema)) = cli_cmd.info.parameters.lookup(prop_name) { | |
496a6784 | 371 | print_property_completion(schema, prop_name, &cli_cmd.completion_functions, &args[0], done); |
b6e8dd39 | 372 | } |
f46403cc DM |
373 | } |
374 | return; | |
375 | } | |
376 | if args.is_empty() { return; } | |
377 | ||
496a6784 DM |
378 | // Try to parse all argumnets but last, record args already done |
379 | if args.len() > 1 { | |
380 | let mut errors = ParameterError::new(); // we simply ignore any parsing errors here | |
9e26abf1 | 381 | let (data, _rest) = getopts::parse_argument_list(&args[0..args.len()-1], &cli_cmd.info.parameters, &mut errors); |
496a6784 DM |
382 | for (key, value) in &data { |
383 | record_done_argument(done, &cli_cmd.info.parameters, key, value); | |
384 | } | |
385 | } | |
2f025895 | 386 | |
6949d915 | 387 | let prefix = &args[args.len()-1]; // match on last arg |
f46403cc | 388 | |
2f025895 | 389 | // complete option-name or option-value ? |
6949d915 DM |
390 | if !prefix.starts_with("-") && args.len() > 1 { |
391 | let last = &args[args.len()-2]; | |
2f025895 | 392 | if last.starts_with("--") && last.len() > 2 { |
ca9caffa | 393 | let prop_name = &last[2..]; |
255f378a | 394 | if let Some((_, schema)) = cli_cmd.info.parameters.lookup(prop_name) { |
496a6784 | 395 | print_property_completion(schema, prop_name, &cli_cmd.completion_functions, &prefix, done); |
2f025895 DM |
396 | } |
397 | return; | |
398 | } | |
399 | } | |
f46403cc | 400 | |
255f378a | 401 | for (name, _optional, _schema) in cli_cmd.info.parameters.properties { |
496a6784 | 402 | if done.contains_key(*name) { continue; } |
1a71509a | 403 | if all_arg_param.contains(name) { continue; } |
2f025895 | 404 | let option = String::from("--") + name; |
6949d915 | 405 | if option.starts_with(prefix) { |
2f025895 | 406 | println!("{}", option); |
f46403cc DM |
407 | } |
408 | } | |
409 | } | |
410 | ||
9d78d579 DM |
411 | fn print_help_completion(def: &CommandLineInterface, help_cmd: &CliCommand, args: &[String]) { |
412 | ||
496a6784 | 413 | let mut done = HashMap::new(); |
793b0f4d | 414 | |
9d78d579 DM |
415 | match def { |
416 | CommandLineInterface::Simple(_) => { | |
1a71509a | 417 | print_simple_completion(help_cmd, &mut done, &help_cmd.arg_param, &help_cmd.arg_param, args); |
9d78d579 DM |
418 | } |
419 | CommandLineInterface::Nested(map) => { | |
420 | if args.is_empty() { | |
421 | for cmd in map.commands.keys() { | |
422 | println!("{}", cmd); | |
423 | } | |
424 | return; | |
425 | } | |
793b0f4d | 426 | |
9d78d579 | 427 | let first = &args[0]; |
793b0f4d DM |
428 | |
429 | if first.starts_with("-") { | |
1a71509a | 430 | print_simple_completion(help_cmd, &mut done, &help_cmd.arg_param, &help_cmd.arg_param, args); |
793b0f4d DM |
431 | return; |
432 | } | |
433 | ||
9d78d579 DM |
434 | if let Some(sub_cmd) = map.commands.get(first) { |
435 | print_help_completion(sub_cmd, help_cmd, &args[1..]); | |
436 | return; | |
437 | } | |
793b0f4d | 438 | |
9d78d579 DM |
439 | for cmd in map.commands.keys() { |
440 | if cmd.starts_with(first) { | |
441 | println!("{}", cmd); | |
442 | } | |
443 | } | |
444 | } | |
445 | } | |
446 | } | |
447 | ||
6949d915 | 448 | fn print_nested_completion(def: &CommandLineInterface, args: &[String]) { |
f46403cc DM |
449 | |
450 | match def { | |
451 | CommandLineInterface::Simple(cli_cmd) => { | |
496a6784 | 452 | let mut done: HashMap<String, String> = HashMap::new(); |
9e26abf1 | 453 | cli_cmd.fixed_param.iter().for_each(|(key, value)| { |
496a6784 DM |
454 | record_done_argument(&mut done, &cli_cmd.info.parameters, &key, &value); |
455 | }); | |
1a71509a | 456 | print_simple_completion(cli_cmd, &mut done, &cli_cmd.arg_param, &cli_cmd.arg_param, args); |
b6e8dd39 DM |
457 | } |
458 | CommandLineInterface::Nested(map) => { | |
459 | if args.is_empty() { | |
460 | for cmd in map.commands.keys() { | |
461 | println!("{}", cmd); | |
462 | } | |
463 | return; | |
464 | } | |
6949d915 | 465 | let first = &args[0]; |
f4e4d583 DM |
466 | if args.len() > 1 { |
467 | if let Some(sub_cmd) = map.commands.get(first) { | |
468 | print_nested_completion(sub_cmd, &args[1..]); | |
469 | return; | |
470 | } | |
b6e8dd39 DM |
471 | } |
472 | for cmd in map.commands.keys() { | |
6949d915 | 473 | if cmd.starts_with(first) { |
b6e8dd39 DM |
474 | println!("{}", cmd); |
475 | } | |
476 | } | |
477 | } | |
478 | } | |
479 | } | |
480 | ||
481 | pub fn print_bash_completion(def: &CommandLineInterface) { | |
482 | ||
483 | let comp_point: usize = match std::env::var("COMP_POINT") { | |
484 | Ok(val) => { | |
485 | match usize::from_str_radix(&val, 10) { | |
486 | Ok(i) => i, | |
a27a3ee4 | 487 | Err(_) => return, |
b6e8dd39 DM |
488 | } |
489 | } | |
a27a3ee4 | 490 | Err(_) => return, |
b6e8dd39 DM |
491 | }; |
492 | ||
493 | let cmdline = match std::env::var("COMP_LINE") { | |
494 | Ok(val) => val[0..comp_point].to_owned(), | |
a27a3ee4 | 495 | Err(_) => return, |
b6e8dd39 DM |
496 | }; |
497 | ||
498 | let mut args = match shellwords::split(&cmdline) { | |
499 | Ok(v) => v, | |
500 | Err(_) => return, | |
501 | }; | |
502 | ||
25e77d38 DM |
503 | if args.len() == 0 { return; } |
504 | ||
b6e8dd39 DM |
505 | args.remove(0); //no need for program name |
506 | ||
f46403cc DM |
507 | if cmdline.ends_with(char::is_whitespace) { |
508 | //eprintln!("CMDLINE {:?}", cmdline); | |
509 | args.push("".into()); | |
510 | } | |
511 | ||
9d78d579 DM |
512 | if !args.is_empty() && args[0] == "help" { |
513 | print_help_completion(def, &help_command_def(), &args[1..]); | |
514 | } else { | |
6949d915 | 515 | print_nested_completion(def, &args); |
9d78d579 | 516 | } |
b6e8dd39 DM |
517 | } |
518 | ||
255f378a DM |
519 | const VERBOSE_HELP_SCHEMA: Schema = BooleanSchema::new("Verbose help.").schema(); |
520 | const COMMAND_HELP: ObjectSchema = ObjectSchema::new( | |
521 | "Get help about specified command.", | |
522 | &[ ("verbose", true, &VERBOSE_HELP_SCHEMA) ] | |
523 | ); | |
524 | ||
525 | const API_METHOD_COMMAND_HELP: ApiMethod = ApiMethod::new_dummy(&COMMAND_HELP); | |
526 | ||
9d78d579 | 527 | fn help_command_def() -> CliCommand { |
255f378a | 528 | CliCommand::new(&API_METHOD_COMMAND_HELP) |
9d78d579 DM |
529 | } |
530 | ||
531 | pub fn run_cli_command(def: CommandLineInterface) { | |
698d9d44 DM |
532 | |
533 | let def = match def { | |
534 | CommandLineInterface::Simple(cli_cmd) => CommandLineInterface::Simple(cli_cmd), | |
9d78d579 DM |
535 | CommandLineInterface::Nested(map) => |
536 | CommandLineInterface::Nested(map.insert("help", help_command_def().into())), | |
698d9d44 DM |
537 | }; |
538 | ||
539 | let top_def = &def; // we pass this to the help function ... | |
33256db6 DM |
540 | |
541 | let mut args = std::env::args(); | |
b7329c8a | 542 | |
33256db6 DM |
543 | let prefix = args.next().unwrap(); |
544 | let prefix = prefix.rsplit('/').next().unwrap(); // without path | |
545 | ||
546 | let args: Vec<String> = args.collect(); | |
b7329c8a | 547 | |
8b6dd224 DM |
548 | if !args.is_empty() { |
549 | if args[0] == "bashcomplete" { | |
698d9d44 | 550 | print_bash_completion(&def); |
8b6dd224 DM |
551 | return; |
552 | } | |
553 | ||
554 | if args[0] == "printdoc" { | |
555 | let usage = match def { | |
556 | CommandLineInterface::Simple(cli_cmd) => { | |
698d9d44 | 557 | generate_usage_str(&prefix, &cli_cmd, DocumentationFormat::ReST, "") |
8b6dd224 DM |
558 | } |
559 | CommandLineInterface::Nested(map) => { | |
698d9d44 | 560 | generate_nested_usage(&prefix, &map, DocumentationFormat::ReST) |
8b6dd224 DM |
561 | } |
562 | }; | |
563 | println!("{}", usage); | |
564 | return; | |
565 | } | |
c21de379 DM |
566 | } |
567 | ||
33256db6 | 568 | match def { |
698d9d44 DM |
569 | CommandLineInterface::Simple(ref cli_cmd) => handle_simple_command(top_def, &prefix, &cli_cmd, args), |
570 | CommandLineInterface::Nested(ref map) => handle_nested_command(top_def, &prefix, &map, args), | |
4968bc3a | 571 | }; |
b7329c8a DM |
572 | } |
573 | ||
496a6784 | 574 | pub type CompletionFunction = fn(&str, &HashMap<String, String>) -> Vec<String>; |
30d2e99c | 575 | |
b7329c8a | 576 | pub struct CliCommand { |
255f378a | 577 | pub info: &'static ApiMethod, |
b7329c8a | 578 | pub arg_param: Vec<&'static str>, |
e39efdbd | 579 | pub fixed_param: HashMap<&'static str, String>, |
30d2e99c | 580 | pub completion_functions: HashMap<String, CompletionFunction>, |
b7329c8a DM |
581 | } |
582 | ||
bf7f1039 DM |
583 | impl CliCommand { |
584 | ||
255f378a | 585 | pub fn new(info: &'static ApiMethod) -> Self { |
30d2e99c DM |
586 | Self { |
587 | info, arg_param: vec![], | |
e39efdbd | 588 | fixed_param: HashMap::new(), |
30d2e99c DM |
589 | completion_functions: HashMap::new(), |
590 | } | |
bf7f1039 DM |
591 | } |
592 | ||
593 | pub fn arg_param(mut self, names: Vec<&'static str>) -> Self { | |
594 | self.arg_param = names; | |
595 | self | |
596 | } | |
597 | ||
e39efdbd DM |
598 | pub fn fixed_param(mut self, key: &'static str, value: String) -> Self { |
599 | self.fixed_param.insert(key, value); | |
bf7f1039 DM |
600 | self |
601 | } | |
30d2e99c DM |
602 | |
603 | pub fn completion_cb(mut self, param_name: &str, cb: CompletionFunction) -> Self { | |
604 | self.completion_functions.insert(param_name.into(), cb); | |
605 | self | |
606 | } | |
bf7f1039 DM |
607 | } |
608 | ||
6460764d DM |
609 | pub struct CliCommandMap { |
610 | pub commands: HashMap<String, CommandLineInterface>, | |
611 | } | |
612 | ||
613 | impl CliCommandMap { | |
614 | ||
615 | pub fn new() -> Self { | |
616 | Self { commands: HashMap:: new() } | |
617 | } | |
618 | ||
619 | pub fn insert<S: Into<String>>(mut self, name: S, cli: CommandLineInterface) -> Self { | |
620 | self.commands.insert(name.into(), cli); | |
621 | self | |
622 | } | |
623 | } | |
624 | ||
9f6ab1fc | 625 | pub enum CommandLineInterface { |
b7329c8a | 626 | Simple(CliCommand), |
6460764d | 627 | Nested(CliCommandMap), |
b7329c8a | 628 | } |
8f62336b DM |
629 | |
630 | impl From<CliCommand> for CommandLineInterface { | |
631 | fn from(cli_cmd: CliCommand) -> Self { | |
632 | CommandLineInterface::Simple(cli_cmd) | |
633 | } | |
634 | } | |
635 | ||
6460764d DM |
636 | impl From<CliCommandMap> for CommandLineInterface { |
637 | fn from(list: CliCommandMap) -> Self { | |
638 | CommandLineInterface::Nested(list) | |
8f62336b DM |
639 | } |
640 | } |