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