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