2 use lazy_static
::lazy_static
;
6 use std
::collections
::{HashMap, HashSet}
;
8 use crate::api_schema
::*;
9 use crate::api_schema
::router
::*;
10 use crate::api_schema
::format
::*;
11 //use crate::api_schema::config::*;
12 use super::environment
::CliEnvironment
;
18 pub static ref OUTPUT_FORMAT
: Arc
<Schema
> =
19 StringSchema
::new("Output format.")
20 .format(Arc
::new(ApiStringFormat
::Enum(&["text", "json", "json-pretty"])))
25 /// Helper function to format and print result
27 /// This is implemented for machine generatable formats 'json' and
28 /// 'json-pretty'. The 'text' format needs to be handled somewhere
30 pub fn format_and_print_result(result
: &Value
, output_format
: &str) {
32 if output_format
== "json-pretty" {
33 println
!("{}", serde_json
::to_string_pretty(&result
).unwrap());
34 } else if output_format
== "json" {
35 println
!("{}", serde_json
::to_string(&result
).unwrap());
41 fn generate_usage_str(
44 format
: DocumentationFormat
,
45 indent
: &str) -> String
{
47 let arg_param
= &cli_cmd
.arg_param
;
48 let fixed_param
= &cli_cmd
.fixed_param
;
49 let properties
= &cli_cmd
.info
.parameters
.properties
;
50 let description
= &cli_cmd
.info
.parameters
.description
;
52 let mut done_hash
= HashSet
::<&str>::new();
53 let mut args
= String
::new();
55 for positional_arg
in arg_param
{
56 match properties
.get(positional_arg
) {
57 Some((optional
, schema
)) => {
60 let is_array
= if let Schema
::Array(_
) = schema
.as_ref() { true }
else { false }
;
61 if *optional { args.push('['); }
62 if is_array { args.push('{'); }
63 args
.push('
<'
); args
.push_str(positional_arg
); args
.push('
>'
);
64 if is_array { args.push('}'
); }
65 if *optional { args.push(']'); }
67 done_hash
.insert(positional_arg
);
69 None
=> panic
!("no such property '{}' in schema", positional_arg
),
73 let mut arg_descr
= String
::new();
74 for positional_arg
in arg_param
{
75 let (_optional
, schema
) = properties
.get(positional_arg
).unwrap();
76 let param_descr
= get_property_description(
77 positional_arg
, &schema
, ParameterDisplayStyle
::Fixed
, format
);
78 arg_descr
.push_str(¶m_descr
);
81 let mut options
= String
::new();
83 let mut prop_names
: Vec
<&str> = properties
.keys().map(|v
| *v
).collect();
86 for prop
in prop_names
{
87 let (optional
, schema
) = properties
.get(prop
).unwrap();
88 if done_hash
.contains(prop
) { continue; }
89 if fixed_param
.contains_key(&prop
) { continue; }
91 let type_text
= get_schema_type_text(&schema
, ParameterDisplayStyle
::Arg
);
95 if options
.len() > 0 { options.push('\n'); }
96 options
.push_str(&get_property_description(prop
, &schema
, ParameterDisplayStyle
::Arg
, format
));
99 args
.push_str(" --"); args
.push_str(prop
);
101 args
.push_str(&type_text
);
104 done_hash
.insert(prop
);
107 let option_indicator
= if options
.len() > 0 { " [OPTIONS]" }
else { "" }
;
109 let mut text
= match format
{
110 DocumentationFormat
::Short
=> {
111 return format
!("{}{}{}{}\n\n", indent
, prefix
, args
, option_indicator
);
113 DocumentationFormat
::Long
=> {
114 format
!("{}{}{}{}\n\n", indent
, prefix
, args
, option_indicator
)
116 DocumentationFormat
::Full
=> {
117 format
!("{}{}{}{}\n\n{}\n\n", indent
, prefix
, args
, option_indicator
, description
)
119 DocumentationFormat
::ReST
=> {
120 format
!("``{}{}{}``\n\n{}\n\n", prefix
, args
, option_indicator
, description
)
124 if arg_descr
.len() > 0 {
125 text
.push_str(&arg_descr
);
128 if options
.len() > 0 {
129 text
.push_str(&options
);
135 fn print_simple_usage_error(prefix
: &str, cli_cmd
: &CliCommand
, err
: Error
) {
137 let usage
= generate_usage_str(prefix
, cli_cmd
, DocumentationFormat
::Long
, "");
138 eprint
!("Error: {}\nUsage: {}", err
, usage
);
142 top_def
: &CommandLineInterface
,
145 verbose
: Option
<bool
>,
147 let mut iface
= top_def
;
150 if let CommandLineInterface
::Nested(map
) = iface
{
151 if let Some(subcmd
) = find_command(map
, cmd
) {
154 prefix
.push_str(cmd
);
158 eprintln
!("no such command '{}'", cmd
);
159 std
::process
::exit(-1);
162 let format
= match verbose
.unwrap_or(false) {
163 true => DocumentationFormat
::Full
,
164 false => DocumentationFormat
::Short
,
168 CommandLineInterface
::Nested(map
) => {
169 println
!("Usage:\n\n{}", generate_nested_usage(&prefix
, map
, format
));
171 CommandLineInterface
::Simple(cli_cmd
) => {
172 println
!("Usage: {}", generate_usage_str(&prefix
, cli_cmd
, format
, ""));
177 fn handle_simple_command(
178 top_def
: &CommandLineInterface
,
180 cli_cmd
: &CliCommand
,
184 let (params
, rest
) = match getopts
::parse_arguments(
185 &args
, &cli_cmd
.arg_param
, &cli_cmd
.info
.parameters
) {
186 Ok((p
, r
)) => (p
, r
),
188 print_simple_usage_error(prefix
, cli_cmd
, err
.into());
189 std
::process
::exit(-1);
193 if cli_cmd
.info
.handler
.is_none() {
194 let prefix
= prefix
.split(' '
).next().unwrap().to_string();
195 print_help(top_def
, prefix
, &rest
, params
["verbose"].as_bool());
199 if !rest
.is_empty() {
200 let err
= format_err
!("got additional arguments: {:?}", rest
);
201 print_simple_usage_error(prefix
, cli_cmd
, err
);
202 std
::process
::exit(-1);
205 let mut rpcenv
= CliEnvironment
::new();
207 match (cli_cmd
.info
.handler
.as_ref().unwrap())(params
, &cli_cmd
.info
, &mut rpcenv
) {
209 if value
!= Value
::Null
{
210 println
!("Result: {}", serde_json
::to_string_pretty(&value
).unwrap());
214 eprintln
!("Error: {}", err
);
215 std
::process
::exit(-1);
220 fn find_command
<'a
>(def
: &'a CliCommandMap
, name
: &str) -> Option
<&'a CommandLineInterface
> {
222 if let Some(sub_cmd
) = def
.commands
.get(name
) {
223 return Some(sub_cmd
);
226 let mut matches
: Vec
<&str> = vec
![];
228 for cmd
in def
.commands
.keys() {
229 if cmd
.starts_with(name
) {
233 if matches
.len() != 1 { return None; }
235 if let Some(sub_cmd
) = def
.commands
.get(matches
[0]) {
236 return Some(sub_cmd
);
242 fn print_nested_usage_error(prefix
: &str, def
: &CliCommandMap
, err
: Error
) {
244 let usage
= generate_nested_usage(prefix
, def
, DocumentationFormat
::Short
);
246 eprintln
!("Error: {}\n\nUsage:\n\n{}", err
, usage
);
249 fn generate_nested_usage(prefix
: &str, def
: &CliCommandMap
, format
: DocumentationFormat
) -> String
{
251 let mut cmds
: Vec
<&String
> = def
.commands
.keys().collect();
254 let mut usage
= String
::new();
257 let new_prefix
= format
!("{} {}", prefix
, cmd
);
259 match def
.commands
.get(cmd
).unwrap() {
260 CommandLineInterface
::Simple(cli_cmd
) => {
261 if usage
.len() > 0 && format
== DocumentationFormat
::ReST
{
262 usage
.push_str("----\n\n");
264 usage
.push_str(&generate_usage_str(&new_prefix
, cli_cmd
, format
, ""));
266 CommandLineInterface
::Nested(map
) => {
267 usage
.push_str(&generate_nested_usage(&new_prefix
, map
, format
));
275 fn handle_nested_command(
276 top_def
: &CommandLineInterface
,
279 mut args
: Vec
<String
>,
283 let mut cmds
: Vec
<&String
> = def
.commands
.keys().collect();
286 let list
= cmds
.iter().fold(String
::new(),|mut s
,item
| {
287 if !s
.is_empty() { s+= ", "; }
292 let err
= format_err
!("no command specified.\nPossible commands: {}", list
);
293 print_nested_usage_error(prefix
, def
, err
);
294 std
::process
::exit(-1);
297 let command
= args
.remove(0);
299 let sub_cmd
= match find_command(def
, &command
) {
302 let err
= format_err
!("no such command '{}'", command
);
303 print_nested_usage_error(prefix
, def
, err
);
304 std
::process
::exit(-1);
308 let new_prefix
= format
!("{} {}", prefix
, command
);
311 CommandLineInterface
::Simple(cli_cmd
) => {
312 handle_simple_command(top_def
, &new_prefix
, cli_cmd
, args
);
314 CommandLineInterface
::Nested(map
) => {
315 handle_nested_command(top_def
, &new_prefix
, map
, args
);
320 fn print_property_completion(
323 completion_functions
: &HashMap
<String
, CompletionFunction
>,
325 param
: &HashMap
<String
, String
>,
327 if let Some(callback
) = completion_functions
.get(name
) {
328 let list
= (callback
)(arg
, param
);
330 if value
.starts_with(arg
) {
331 println
!("{}", value
);
337 if let Schema
::String(StringSchema { format: Some(format), ..}
) = schema
{
338 if let ApiStringFormat
::Enum(list
) = *format
.as_ref() {
340 if value
.starts_with(arg
) {
341 println
!("{}", value
);
350 fn record_done_argument(done
: &mut HashMap
<String
, String
>, parameters
: &ObjectSchema
, key
: &str, value
: &str) {
352 if let Some((_
, schema
)) = parameters
.properties
.get
::<str>(key
) {
353 match schema
.as_ref() {
354 Schema
::Array(_
) => { /* do nothing ?? */ }
355 _
=> { done.insert(key.to_owned(), value.to_owned()); }
360 fn print_simple_completion(
361 cli_cmd
: &CliCommand
,
362 done
: &mut HashMap
<String
, String
>,
363 all_arg_param
: &[&str], // this is always the full list
364 arg_param
: &[&str], // we remove done arguments
367 // fixme: arg_param, fixed_param
368 //eprintln!("COMPL: {:?} {:?} {}", arg_param, args, args.len());
370 if !arg_param
.is_empty() {
371 let prop_name
= arg_param
[0];
373 record_done_argument(done
, &cli_cmd
.info
.parameters
, prop_name
, &args
[0]);
374 print_simple_completion(cli_cmd
, done
, arg_param
, &arg_param
[1..], &args
[1..]);
376 } else if args
.len() == 1 {
377 record_done_argument(done
, &cli_cmd
.info
.parameters
, prop_name
, &args
[0]);
378 if let Some((_
, schema
)) = cli_cmd
.info
.parameters
.properties
.get(prop_name
) {
379 print_property_completion(schema
, prop_name
, &cli_cmd
.completion_functions
, &args
[0], done
);
384 if args
.is_empty() { return; }
386 // Try to parse all argumnets but last, record args already done
388 let mut errors
= ParameterError
::new(); // we simply ignore any parsing errors here
389 let (data
, _rest
) = getopts
::parse_argument_list(&args
[0..args
.len()-1], &cli_cmd
.info
.parameters
, &mut errors
);
390 for (key
, value
) in &data
{
391 record_done_argument(done
, &cli_cmd
.info
.parameters
, key
, value
);
395 let prefix
= &args
[args
.len()-1]; // match on last arg
397 // complete option-name or option-value ?
398 if !prefix
.starts_with("-") && args
.len() > 1 {
399 let last
= &args
[args
.len()-2];
400 if last
.starts_with("--") && last
.len() > 2 {
401 let prop_name
= &last
[2..];
402 if let Some((_
, schema
)) = cli_cmd
.info
.parameters
.properties
.get(prop_name
) {
403 print_property_completion(schema
, prop_name
, &cli_cmd
.completion_functions
, &prefix
, done
);
409 for (name
, (_optional
, _schema
)) in &cli_cmd
.info
.parameters
.properties
{
410 if done
.contains_key(*name
) { continue; }
411 if all_arg_param
.contains(name
) { continue; }
412 let option
= String
::from("--") + name
;
413 if option
.starts_with(prefix
) {
414 println
!("{}", option
);
419 fn print_help_completion(def
: &CommandLineInterface
, help_cmd
: &CliCommand
, args
: &[String
]) {
421 let mut done
= HashMap
::new();
424 CommandLineInterface
::Simple(_
) => {
425 print_simple_completion(help_cmd
, &mut done
, &help_cmd
.arg_param
, &help_cmd
.arg_param
, args
);
427 CommandLineInterface
::Nested(map
) => {
429 for cmd
in map
.commands
.keys() {
435 let first
= &args
[0];
437 if first
.starts_with("-") {
438 print_simple_completion(help_cmd
, &mut done
, &help_cmd
.arg_param
, &help_cmd
.arg_param
, args
);
442 if let Some(sub_cmd
) = map
.commands
.get(first
) {
443 print_help_completion(sub_cmd
, help_cmd
, &args
[1..]);
447 for cmd
in map
.commands
.keys() {
448 if cmd
.starts_with(first
) {
456 fn print_nested_completion(def
: &CommandLineInterface
, args
: &[String
]) {
459 CommandLineInterface
::Simple(cli_cmd
) => {
460 let mut done
: HashMap
<String
, String
> = HashMap
::new();
461 cli_cmd
.fixed_param
.iter().for_each(|(key
, value
)| {
462 record_done_argument(&mut done
, &cli_cmd
.info
.parameters
, &key
, &value
);
464 print_simple_completion(cli_cmd
, &mut done
, &cli_cmd
.arg_param
, &cli_cmd
.arg_param
, args
);
466 CommandLineInterface
::Nested(map
) => {
468 for cmd
in map
.commands
.keys() {
473 let first
= &args
[0];
475 if let Some(sub_cmd
) = map
.commands
.get(first
) {
476 print_nested_completion(sub_cmd
, &args
[1..]);
480 for cmd
in map
.commands
.keys() {
481 if cmd
.starts_with(first
) {
489 pub fn print_bash_completion(def
: &CommandLineInterface
) {
491 let comp_point
: usize = match std
::env
::var("COMP_POINT") {
493 match usize::from_str_radix(&val
, 10) {
501 let cmdline
= match std
::env
::var("COMP_LINE") {
502 Ok(val
) => val
[0..comp_point
].to_owned(),
506 let mut args
= match shellwords
::split(&cmdline
) {
511 if args
.len() == 0 { return; }
513 args
.remove(0); //no need for program name
515 if cmdline
.ends_with(char::is_whitespace
) {
516 //eprintln!("CMDLINE {:?}", cmdline);
517 args
.push("".into());
520 if !args
.is_empty() && args
[0] == "help" {
521 print_help_completion(def
, &help_command_def(), &args
[1..]);
523 print_nested_completion(def
, &args
);
527 fn help_command_def() -> CliCommand
{
529 ApiMethod
::new_dummy(
530 ObjectSchema
::new("Get help about specified command.")
531 .optional("verbose", BooleanSchema
::new("Verbose help."))
536 pub fn run_cli_command(def
: CommandLineInterface
) {
538 let def
= match def
{
539 CommandLineInterface
::Simple(cli_cmd
) => CommandLineInterface
::Simple(cli_cmd
),
540 CommandLineInterface
::Nested(map
) =>
541 CommandLineInterface
::Nested(map
.insert("help", help_command_def().into())),
544 let top_def
= &def
; // we pass this to the help function ...
546 let mut args
= std
::env
::args();
548 let prefix
= args
.next().unwrap();
549 let prefix
= prefix
.rsplit('
/'
).next().unwrap(); // without path
551 let args
: Vec
<String
> = args
.collect();
553 if !args
.is_empty() {
554 if args
[0] == "bashcomplete" {
555 print_bash_completion(&def
);
559 if args
[0] == "printdoc" {
560 let usage
= match def
{
561 CommandLineInterface
::Simple(cli_cmd
) => {
562 generate_usage_str(&prefix
, &cli_cmd
, DocumentationFormat
::ReST
, "")
564 CommandLineInterface
::Nested(map
) => {
565 generate_nested_usage(&prefix
, &map
, DocumentationFormat
::ReST
)
568 println
!("{}", usage
);
574 CommandLineInterface
::Simple(ref cli_cmd
) => handle_simple_command(top_def
, &prefix
, &cli_cmd
, args
),
575 CommandLineInterface
::Nested(ref map
) => handle_nested_command(top_def
, &prefix
, &map
, args
),
579 pub type CompletionFunction
= fn(&str, &HashMap
<String
, String
>) -> Vec
<String
>;
581 pub struct CliCommand
{
583 pub arg_param
: Vec
<&'
static str>,
584 pub fixed_param
: HashMap
<&'
static str, String
>,
585 pub completion_functions
: HashMap
<String
, CompletionFunction
>,
590 pub fn new(info
: ApiMethod
) -> Self {
592 info
, arg_param
: vec
![],
593 fixed_param
: HashMap
::new(),
594 completion_functions
: HashMap
::new(),
598 pub fn arg_param(mut self, names
: Vec
<&'
static str>) -> Self {
599 self.arg_param
= names
;
603 pub fn fixed_param(mut self, key
: &'
static str, value
: String
) -> Self {
604 self.fixed_param
.insert(key
, value
);
608 pub fn completion_cb(mut self, param_name
: &str, cb
: CompletionFunction
) -> Self {
609 self.completion_functions
.insert(param_name
.into(), cb
);
614 pub struct CliCommandMap
{
615 pub commands
: HashMap
<String
, CommandLineInterface
>,
620 pub fn new() -> Self {
621 Self { commands: HashMap:: new() }
624 pub fn insert
<S
: Into
<String
>>(mut self, name
: S
, cli
: CommandLineInterface
) -> Self {
625 self.commands
.insert(name
.into(), cli
);
630 pub enum CommandLineInterface
{
632 Nested(CliCommandMap
),
635 impl From
<CliCommand
> for CommandLineInterface
{
636 fn from(cli_cmd
: CliCommand
) -> Self {
637 CommandLineInterface
::Simple(cli_cmd
)
641 impl From
<CliCommandMap
> for CommandLineInterface
{
642 fn from(list
: CliCommandMap
) -> Self {
643 CommandLineInterface
::Nested(list
)