src/cli/command.rs: cleanup, make handlers more generic
This commit is contained in:
		| @ -17,27 +17,27 @@ pub const OUTPUT_FORMAT: Schema = | ||||
|     .format(&ApiStringFormat::Enum(&["text", "json", "json-pretty"])) | ||||
|     .schema(); | ||||
|  | ||||
|  | ||||
| fn handle_simple_command( | ||||
| pub fn handle_simple_command( | ||||
|     _top_def: &CommandLineInterface, | ||||
|     prefix: &str, | ||||
|     cli_cmd: &CliCommand, | ||||
|     args: Vec<String>, | ||||
| ) { | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     let (params, rest) = match getopts::parse_arguments( | ||||
|         &args, cli_cmd.arg_param, &cli_cmd.info.parameters) { | ||||
|         Ok((p, r)) => (p, r), | ||||
|         Err(err) => { | ||||
|             print_simple_usage_error(prefix, cli_cmd, err.into()); | ||||
|             std::process::exit(-1); | ||||
|             let err_msg = err.to_string(); | ||||
|             print_simple_usage_error(prefix, cli_cmd, &err_msg); | ||||
|             return Err(format_err!("{}", err_msg)); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     if !rest.is_empty() { | ||||
|         let err = format_err!("got additional arguments: {:?}", rest); | ||||
|         print_simple_usage_error(prefix, cli_cmd, err); | ||||
|         std::process::exit(-1); | ||||
|         let err_msg = format!("got additional arguments: {:?}", rest); | ||||
|         print_simple_usage_error(prefix, cli_cmd, &err_msg); | ||||
|         return Err(format_err!("{}", err_msg)); | ||||
|     } | ||||
|  | ||||
|     let mut rpcenv = CliEnvironment::new(); | ||||
| @ -52,25 +52,27 @@ fn handle_simple_command( | ||||
|                 } | ||||
|                 Err(err) => { | ||||
|                     eprintln!("Error: {}", err); | ||||
|                     std::process::exit(-1); | ||||
|                     return Err(err); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         ApiHandler::AsyncHttp(_) => { | ||||
|             let err = format_err!( | ||||
|                 "CliHandler does not support ApiHandler::AsyncHttp - internal error"); | ||||
|             print_simple_usage_error(prefix, cli_cmd, err); | ||||
|             std::process::exit(-1); | ||||
|             let err_msg = | ||||
|                 "CliHandler does not support ApiHandler::AsyncHttp - internal error"; | ||||
|             print_simple_usage_error(prefix, cli_cmd, err_msg); | ||||
|             return Err(format_err!("{}", err_msg)); | ||||
|         } | ||||
|     }     | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn handle_nested_command( | ||||
| pub fn handle_nested_command( | ||||
|     top_def: &CommandLineInterface, | ||||
|     prefix: &str, | ||||
|     def: &CliCommandMap, | ||||
|     mut args: Vec<String>, | ||||
| ) { | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     if args.len() < 1 { | ||||
|         let mut cmds: Vec<&String> = def.commands.keys().collect(); | ||||
| @ -82,9 +84,9 @@ fn handle_nested_command( | ||||
|             s | ||||
|         }); | ||||
|  | ||||
|         let err = format_err!("no command specified.\nPossible commands: {}", list); | ||||
|         print_nested_usage_error(prefix, def, err); | ||||
|         std::process::exit(-1); | ||||
|         let err_msg = format!("no command specified.\nPossible commands: {}", list); | ||||
|         print_nested_usage_error(prefix, def, &err_msg); | ||||
|         return Err(format_err!("{}", err_msg)); | ||||
|     } | ||||
|  | ||||
|     let command = args.remove(0); | ||||
| @ -92,9 +94,9 @@ fn handle_nested_command( | ||||
|     let sub_cmd = match def.find_command(&command) { | ||||
|         Some(cmd) => cmd, | ||||
|         None => { | ||||
|             let err = format_err!("no such command '{}'", command); | ||||
|             print_nested_usage_error(prefix, def, err); | ||||
|             std::process::exit(-1); | ||||
|             let err_msg = format!("no such command '{}'", command); | ||||
|             print_nested_usage_error(prefix, def, &err_msg); | ||||
|             return Err(format_err!("{}", err_msg)); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
| @ -102,42 +104,47 @@ fn handle_nested_command( | ||||
|  | ||||
|     match sub_cmd { | ||||
|         CommandLineInterface::Simple(cli_cmd) => { | ||||
|             handle_simple_command(top_def, &new_prefix, cli_cmd, args); | ||||
|             handle_simple_command(top_def, &new_prefix, cli_cmd, args)?; | ||||
|         } | ||||
|         CommandLineInterface::Nested(map) => { | ||||
|             handle_nested_command(top_def, &new_prefix, map, args); | ||||
|             handle_nested_command(top_def, &new_prefix, map, args)?; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn print_property_completion( | ||||
| fn get_property_completion( | ||||
|     schema: &Schema, | ||||
|     name: &str, | ||||
|     completion_functions: &HashMap<String, CompletionFunction>, | ||||
|     arg: &str, | ||||
|     param: &HashMap<String, String>, | ||||
| ) { | ||||
| ) -> Vec<String> { | ||||
|  | ||||
|     if let Some(callback) = completion_functions.get(name) { | ||||
|         let list = (callback)(arg, param); | ||||
|         let mut completions = Vec::new(); | ||||
|         for value in list { | ||||
|             if value.starts_with(arg) { | ||||
|                 println!("{}", value); | ||||
|                 completions.push(value); | ||||
|             } | ||||
|         } | ||||
|         return; | ||||
|         return completions; | ||||
|     } | ||||
|  | ||||
|     if let Schema::String(StringSchema { format: Some(format),  ..} ) = schema { | ||||
|         if let ApiStringFormat::Enum(list) = format { | ||||
|             let mut completions = Vec::new(); | ||||
|             for value in list.iter() { | ||||
|                 if value.starts_with(arg) { | ||||
|                     println!("{}", value); | ||||
|                     completions.push(value.to_string()); | ||||
|                 } | ||||
|             } | ||||
|             return; | ||||
|             return completions; | ||||
|         } | ||||
|     } | ||||
|     println!(); | ||||
|     return Vec::new(); | ||||
| } | ||||
|  | ||||
| fn record_done_argument(done: &mut HashMap<String, String>, parameters: &ObjectSchema, key: &str, value: &str) { | ||||
| @ -150,13 +157,13 @@ fn record_done_argument(done: &mut HashMap<String, String>, parameters: &ObjectS | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn print_simple_completion( | ||||
| pub fn get_simple_completion( | ||||
|     cli_cmd: &CliCommand, | ||||
|     done: &mut HashMap<String, String>, | ||||
|     all_arg_param: &[&str], // this is always the full list | ||||
|     arg_param: &[&str], // we remove done arguments | ||||
|     args: &[String], | ||||
| ) { | ||||
| ) -> Vec<String> { | ||||
|     // fixme: arg_param, fixed_param | ||||
|     //eprintln!("COMPL: {:?} {:?} {}", arg_param, args, args.len()); | ||||
|  | ||||
| @ -164,17 +171,16 @@ fn print_simple_completion( | ||||
|         let prop_name = arg_param[0]; | ||||
|         if args.len() > 1 { | ||||
|             record_done_argument(done, cli_cmd.info.parameters, prop_name, &args[0]); | ||||
|             print_simple_completion(cli_cmd, done, arg_param, &arg_param[1..], &args[1..]); | ||||
|             return; | ||||
|             return get_simple_completion(cli_cmd, done, arg_param, &arg_param[1..], &args[1..]); | ||||
|         } else if args.len() == 1 { | ||||
|             record_done_argument(done, cli_cmd.info.parameters, prop_name, &args[0]); | ||||
|             if let Some((_, schema)) = cli_cmd.info.parameters.lookup(prop_name) { | ||||
|                 print_property_completion(schema, prop_name, &cli_cmd.completion_functions, &args[0], done); | ||||
|                 return get_property_completion(schema, prop_name, &cli_cmd.completion_functions, &args[0], done); | ||||
|             } | ||||
|         } | ||||
|         return; | ||||
|         return Vec::new(); | ||||
|     } | ||||
|     if args.is_empty() { return; } | ||||
|     if args.is_empty() { return Vec::new(); } | ||||
|  | ||||
|     // Try to parse all argumnets but last, record args already done | ||||
|     if args.len() > 1 { | ||||
| @ -193,60 +199,70 @@ fn print_simple_completion( | ||||
|         if last.starts_with("--") && last.len() > 2 { | ||||
|             let prop_name = &last[2..]; | ||||
|             if let Some((_, schema)) = cli_cmd.info.parameters.lookup(prop_name) { | ||||
|                 print_property_completion(schema, prop_name, &cli_cmd.completion_functions, &prefix, done); | ||||
|                 return get_property_completion(schema, prop_name, &cli_cmd.completion_functions, &prefix, done); | ||||
|             } | ||||
|             return; | ||||
|             return Vec::new(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let mut completions = Vec::new(); | ||||
|     for (name, _optional, _schema) in cli_cmd.info.parameters.properties { | ||||
|         if done.contains_key(*name) { continue; } | ||||
|         if all_arg_param.contains(name) { continue; } | ||||
|         let option = String::from("--") + name; | ||||
|         if option.starts_with(prefix) { | ||||
|             println!("{}", option); | ||||
|             completions.push(option); | ||||
|         } | ||||
|     } | ||||
|     completions | ||||
| } | ||||
|  | ||||
| fn print_help_completion(def: &CommandLineInterface, help_cmd: &CliCommand, args: &[String]) { | ||||
| pub fn get_help_completion( | ||||
|     def: &CommandLineInterface, | ||||
|     help_cmd: &CliCommand, | ||||
|     args: &[String], | ||||
| ) -> Vec<String> { | ||||
|  | ||||
|     let mut done = HashMap::new(); | ||||
|  | ||||
|     match def { | ||||
|         CommandLineInterface::Simple(_) => { | ||||
|             print_simple_completion(help_cmd, &mut done, help_cmd.arg_param, &help_cmd.arg_param, args); | ||||
|             return get_simple_completion(help_cmd, &mut done, help_cmd.arg_param, &help_cmd.arg_param, args); | ||||
|         } | ||||
|         CommandLineInterface::Nested(map) => { | ||||
|             if args.is_empty() { | ||||
|                 let mut completions = Vec::new(); | ||||
|                 for cmd in map.commands.keys() { | ||||
|                     println!("{}", cmd); | ||||
|                     completions.push(cmd.to_string()); | ||||
|                 } | ||||
|                 return; | ||||
|                 return completions; | ||||
|             } | ||||
|  | ||||
|             let first = &args[0]; | ||||
|  | ||||
|             if first.starts_with("-") { | ||||
|                 print_simple_completion(help_cmd, &mut done, help_cmd.arg_param, &help_cmd.arg_param, args); | ||||
|                 return; | ||||
|                 return get_simple_completion(help_cmd, &mut done, help_cmd.arg_param, &help_cmd.arg_param, args); | ||||
|             } | ||||
|  | ||||
|             if let Some(sub_cmd) = map.commands.get(first) { | ||||
|                 print_help_completion(sub_cmd, help_cmd, &args[1..]); | ||||
|                 return; | ||||
|                 return get_help_completion(sub_cmd, help_cmd, &args[1..]); | ||||
|             } | ||||
|  | ||||
|             let mut completions = Vec::new(); | ||||
|             for cmd in map.commands.keys() { | ||||
|                 if cmd.starts_with(first) { | ||||
|                     println!("{}", cmd); | ||||
|                     completions.push(cmd.to_string()); | ||||
|                 } | ||||
|             } | ||||
|             return completions; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn print_nested_completion(def: &CommandLineInterface, args: &[String]) { | ||||
| pub fn get_nested_completion( | ||||
|     def: &CommandLineInterface, | ||||
|     args: &[String], | ||||
| ) -> Vec<String> { | ||||
|  | ||||
|     match def { | ||||
|         CommandLineInterface::Simple(cli_cmd) => { | ||||
| @ -254,27 +270,29 @@ fn print_nested_completion(def: &CommandLineInterface, args: &[String]) { | ||||
|             cli_cmd.fixed_param.iter().for_each(|(key, value)| { | ||||
|                 record_done_argument(&mut done, &cli_cmd.info.parameters, &key, &value); | ||||
|             }); | ||||
|             print_simple_completion(cli_cmd, &mut done, cli_cmd.arg_param, &cli_cmd.arg_param, args); | ||||
|             return get_simple_completion(cli_cmd, &mut done, cli_cmd.arg_param, &cli_cmd.arg_param, args); | ||||
|         } | ||||
|         CommandLineInterface::Nested(map) => { | ||||
|             if args.is_empty() { | ||||
|                 let mut completions = Vec::new(); | ||||
|                 for cmd in map.commands.keys() { | ||||
|                     println!("{}", cmd); | ||||
|                     completions.push(cmd.to_string()); | ||||
|                 } | ||||
|                 return; | ||||
|                 return completions; | ||||
|             } | ||||
|             let first = &args[0]; | ||||
|             if args.len() > 1 { | ||||
|                 if let Some(sub_cmd) = map.commands.get(first) { | ||||
|                     print_nested_completion(sub_cmd, &args[1..]); | ||||
|                     return; | ||||
|                     return get_nested_completion(sub_cmd, &args[1..]); | ||||
|                 } | ||||
|             } | ||||
|             let mut completions = Vec::new(); | ||||
|             for cmd in map.commands.keys() { | ||||
|                 if cmd.starts_with(first) { | ||||
|                     println!("{}", cmd); | ||||
|                     completions.push(cmd.to_string()); | ||||
|                 } | ||||
|             } | ||||
|             return completions; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -310,10 +328,14 @@ pub fn print_bash_completion(def: &CommandLineInterface) { | ||||
|         args.push("".into()); | ||||
|     } | ||||
|  | ||||
|     if !args.is_empty() && args[0] == "help" { | ||||
|         print_help_completion(def, &help_command_def(), &args[1..]); | ||||
|     let completions = if !args.is_empty() && args[0] == "help" { | ||||
|         get_help_completion(def, &help_command_def(), &args[1..]) | ||||
|     } else { | ||||
|         print_nested_completion(def, &args); | ||||
|         get_nested_completion(def, &args) | ||||
|     }; | ||||
|  | ||||
|     for item in completions { | ||||
|         println!("{}", item); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -325,7 +347,7 @@ const COMMAND_HELP: ObjectSchema = ObjectSchema::new( | ||||
|  | ||||
| const API_METHOD_COMMAND_HELP: ApiMethod = ApiMethod::new_dummy(&COMMAND_HELP); | ||||
|  | ||||
| fn help_command_def() ->  CliCommand { | ||||
| pub fn help_command_def() ->  CliCommand { | ||||
|     CliCommand::new(&API_METHOD_COMMAND_HELP) | ||||
| } | ||||
|  | ||||
| @ -367,8 +389,15 @@ pub fn run_cli_command(def: CommandLineInterface) { | ||||
|     } | ||||
|  | ||||
|     match def { | ||||
|         CommandLineInterface::Simple(ref cli_cmd) => handle_simple_command(top_def, &prefix, &cli_cmd, args), | ||||
|         CommandLineInterface::Nested(ref map) => handle_nested_command(top_def, &prefix, &map, args), | ||||
|         CommandLineInterface::Simple(ref cli_cmd) => { | ||||
|             if let Err(_) = handle_simple_command(top_def, &prefix, &cli_cmd, args) { | ||||
|                 std::process::exit(-1); | ||||
|             } | ||||
|         } | ||||
|         CommandLineInterface::Nested(ref map) => { | ||||
|             if let Err(_) = handle_nested_command(top_def, &prefix, &map, args) { | ||||
|                 std::process::exit(-1); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| use failure::*; | ||||
| use serde_json::Value; | ||||
|  | ||||
| use std::collections::HashSet; | ||||
| @ -119,19 +118,19 @@ pub fn generate_usage_str( | ||||
| pub fn print_simple_usage_error( | ||||
|     prefix: &str, | ||||
|     cli_cmd: &CliCommand, | ||||
|     err: Error, | ||||
|     err_msg: &str, | ||||
| ) { | ||||
|     let usage =  generate_usage_str(prefix, cli_cmd, DocumentationFormat::Long, ""); | ||||
|     eprint!("Error: {}\nUsage: {}", err, usage); | ||||
|     eprint!("Error: {}\nUsage: {}", err_msg, usage); | ||||
| } | ||||
|  | ||||
| pub fn print_nested_usage_error( | ||||
|     prefix: &str, | ||||
|     def: &CliCommandMap, | ||||
|     err: Error, | ||||
|     err_msg: &str, | ||||
| ) { | ||||
|     let usage = generate_nested_usage(prefix, def, DocumentationFormat::Short); | ||||
|     eprintln!("Error: {}\n\nUsage:\n\n{}", err, usage); | ||||
|     eprintln!("Error: {}\n\nUsage:\n\n{}", err_msg, usage); | ||||
| } | ||||
|  | ||||
| pub fn generate_nested_usage( | ||||
|  | ||||
		Reference in New Issue
	
	Block a user