]>
Commit | Line | Data |
---|---|---|
b7329c8a DM |
1 | use failure::*; |
2 | use std::collections::HashMap; | |
f46403cc | 3 | use std::collections::HashSet; |
698d9d44 | 4 | use serde_json::Value; |
4968bc3a | 5 | |
ef2f2efb | 6 | use crate::api_schema::*; |
dc9a007b DM |
7 | use crate::api_schema::router::*; |
8 | //use crate::api_schema::config::*; | |
0f253593 | 9 | use super::environment::CliEnvironment; |
6049b71f | 10 | |
4de0e142 | 11 | use super::getopts; |
6049b71f | 12 | |
6adb40ee DM |
13 | #[derive(Copy, Clone)] |
14 | enum ParameterDisplayStyle { | |
15 | //Config, | |
16 | //SonfigSub, | |
17 | Arg, | |
18 | Fixed, | |
19 | } | |
20 | ||
2f3f2bb7 DM |
21 | /// CLI usage information format |
22 | #[derive(Copy, Clone, PartialEq)] | |
23 | enum 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 | ||
34 | fn 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 | ||
48 | fn 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 |
112 | fn 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(¶m_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 | ||
198 | fn 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 |
204 | fn 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 | ||
240 | fn 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 | 280 | fn 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 |
302 | fn 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 | 309 | fn 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 |
335 | fn 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 |
380 | fn 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 |
409 | fn 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 | ||
424 | fn 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 |
474 | fn 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 |
503 | fn 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 | ||
534 | pub 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 |
571 | fn 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 | ||
581 | pub 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 | 624 | pub type CompletionFunction = fn(&str) -> Vec<String>; |
30d2e99c | 625 | |
b7329c8a DM |
626 | pub 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 |
633 | impl 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 |
659 | pub struct CliCommandMap { |
660 | pub commands: HashMap<String, CommandLineInterface>, | |
661 | } | |
662 | ||
698d9d44 DM |
663 | fn 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 |
667 | impl 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 | 679 | pub enum CommandLineInterface { |
b7329c8a | 680 | Simple(CliCommand), |
6460764d | 681 | Nested(CliCommandMap), |
b7329c8a | 682 | } |
8f62336b DM |
683 | |
684 | impl From<CliCommand> for CommandLineInterface { | |
685 | fn from(cli_cmd: CliCommand) -> Self { | |
686 | CommandLineInterface::Simple(cli_cmd) | |
687 | } | |
688 | } | |
689 | ||
6460764d DM |
690 | impl From<CliCommandMap> for CommandLineInterface { |
691 | fn from(list: CliCommandMap) -> Self { | |
692 | CommandLineInterface::Nested(list) | |
8f62336b DM |
693 | } |
694 | } |