From 698d9d440257b829d90bb5f77eb43ea4907f07fd Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Sat, 23 Feb 2019 15:10:48 +0100 Subject: [PATCH] src/cli/command.rs: add help command --- src/bin/catar.rs | 2 +- src/bin/proxmox-backup-client.rs | 2 +- src/bin/proxmox-backup-manager.rs | 2 +- src/cli/command.rs | 125 +++++++++++++++++++++++------- 4 files changed, 102 insertions(+), 29 deletions(-) diff --git a/src/bin/catar.rs b/src/bin/catar.rs index f6639cb7..6a2f8c40 100644 --- a/src/bin/catar.rs +++ b/src/bin/catar.rs @@ -185,5 +185,5 @@ fn main() { .into() ); - run_cli_command(&cmd_def.into()); + run_cli_command(cmd_def.into()); } diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index eaafb2de..26354395 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -236,5 +236,5 @@ fn main() { .insert("garbage-collect".to_owned(), garbage_collect_cmd_def.into()) .insert("list".to_owned(), list_cmd_def.into()); - run_cli_command(&cmd_def.into()); + run_cli_command(cmd_def.into()); } diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index 05e2af06..0c315e2b 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -51,5 +51,5 @@ fn main() { .insert("datastore".to_owned(), datastore_commands()) .insert("garbage-collection".to_owned(), garbage_collection_commands()); - run_cli_command(&cmd_def.into()); + run_cli_command(cmd_def.into()); } diff --git a/src/cli/command.rs b/src/cli/command.rs index 0c6ed10c..dc61e023 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -1,7 +1,7 @@ use failure::*; use std::collections::HashMap; use std::collections::HashSet; -//use serde_json::Value; +use serde_json::Value; use crate::api_schema::*; use crate::api_schema::router::*; @@ -63,14 +63,19 @@ fn get_property_description( Schema::Array(ref schema) => (schema.description, None), }; + let default_text = match default { + Some(text) => format!(" (default={})", text), + None => String::new(), + }; + if format == DocumentationFormat::ReST { let mut text = match style { ParameterDisplayStyle::Arg => { - format!(":``--{} {}``: ", name, type_text) + format!(":``--{} {}{}``: ", name, type_text, default_text) } ParameterDisplayStyle::Fixed => { - format!(":``<{}> {}``: ", name, type_text) + format!(":``<{}> {}{}``: ", name, type_text, default_text) } }; @@ -82,11 +87,6 @@ fn get_property_description( } else { - let default_text = match default { - Some(text) => format!(" (default={})", text), - None => String::new(), - }; - let display_name = match style { ParameterDisplayStyle::Arg => { format!("--{}", name) @@ -167,28 +167,30 @@ fn generate_usage_str( done_hash.insert(prop); } + let option_indicator = if options.len() > 0 { " [OPTIONS]" } else { "" }; + let mut text = match format { DocumentationFormat::Short => { - return format!("{}{}{}", indent, prefix, args); + return format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator); } DocumentationFormat::Long => { - format!("{}{}{}\n", indent, prefix, args) + format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator) } DocumentationFormat::Full => { - format!("{}{}{}\n\n{}\n", indent, prefix, args, description) + format!("{}{}{}{}\n\n{}\n\n", indent, prefix, args, option_indicator, description) } DocumentationFormat::ReST => { - format!("``{} {}``\n\n{}\n\n", prefix, args.trim(), description) + format!("``{} {}{}``\n\n{}\n\n", prefix, args.trim(), option_indicator, description) } }; if arg_descr.len() > 0 { - text.push('\n'); text.push_str(&arg_descr); + text.push('\n'); } if options.len() > 0 { - text.push('\n'); text.push_str(&options); + text.push('\n'); } text } @@ -199,7 +201,48 @@ fn print_simple_usage_error(prefix: &str, cli_cmd: &CliCommand, err: Error) { eprint!("Error: {}\nUsage: {}", err, usage); } -fn handle_simple_command(prefix: &str, cli_cmd: &CliCommand, args: Vec) { +fn print_help( + top_def: &CommandLineInterface, + mut prefix: String, + args: &Vec, + verbose: Option, +) { + let mut iface = top_def; + + for cmd in args { + if let CommandLineInterface::Nested(map) = iface { + if let Some(subcmd) = find_command(map, cmd) { + iface = subcmd; + prefix.push(' '); + prefix.push_str(cmd); + continue; + } + } + eprintln!("no such command '{}'", cmd); + std::process::exit(-1); + } + + let format = match verbose.unwrap_or(false) { + true => DocumentationFormat::Full, + false => DocumentationFormat::Short, + }; + + match iface { + CommandLineInterface::Nested(map) => { + println!("Usage:\n\n{}", generate_nested_usage(&prefix, map, format)); + } + CommandLineInterface::Simple(cli_cmd) => { + println!("Usage: {}", generate_usage_str(&prefix, cli_cmd, format, "")); + } + } +} + +fn handle_simple_command( + top_def: &CommandLineInterface, + prefix: &str, + cli_cmd: &CliCommand, + args: Vec, +) { let (params, rest) = match getopts::parse_arguments( &args, &cli_cmd.arg_param, &cli_cmd.info.parameters) { @@ -210,6 +253,12 @@ fn handle_simple_command(prefix: &str, cli_cmd: &CliCommand, args: Vec) } }; + if (cli_cmd.info.handler as *const fn()) == (dummy_help as *const fn()) { + let prefix = prefix.split(' ').next().unwrap().to_string(); + print_help(top_def, prefix, &rest, params["verbose"].as_bool()); + return; + } + if !rest.is_empty() { let err = format_err!("got additional arguments: {:?}", rest); print_simple_usage_error(prefix, cli_cmd, err); @@ -252,9 +301,9 @@ fn find_command<'a>(def: &'a CliCommandMap, name: &str) -> Option<&'a CommandLin fn print_nested_usage_error(prefix: &str, def: &CliCommandMap, err: Error) { - let usage = generate_nested_usage(prefix, def, DocumentationFormat::Long); + let usage = generate_nested_usage(prefix, def, DocumentationFormat::Short); - eprintln!("Error: {}\n\nUsage:\n{}", err, usage); + eprintln!("Error: {}\n\nUsage:\n\n{}", err, usage); } fn generate_nested_usage(prefix: &str, def: &CliCommandMap, format: DocumentationFormat) -> String { @@ -283,7 +332,12 @@ fn generate_nested_usage(prefix: &str, def: &CliCommandMap, format: Documentatio usage } -fn handle_nested_command(prefix: &str, def: &CliCommandMap, mut args: Vec) { +fn handle_nested_command( + top_def: &CommandLineInterface, + prefix: &str, + def: &CliCommandMap, + mut args: Vec, +) { if args.len() < 1 { let mut cmds: Vec<&String> = def.commands.keys().collect(); @@ -315,10 +369,10 @@ fn handle_nested_command(prefix: &str, def: &CliCommandMap, mut args: Vec { - handle_simple_command(&new_prefix, cli_cmd, args); + handle_simple_command(top_def, &new_prefix, cli_cmd, args); } CommandLineInterface::Nested(map) => { - handle_nested_command(&new_prefix, map, args); + handle_nested_command(top_def, &new_prefix, map, args); } } } @@ -483,7 +537,22 @@ pub fn print_bash_completion(def: &CommandLineInterface) { print_nested_completion(def, args); } -pub fn run_cli_command(def: &CommandLineInterface) { +pub fn run_cli_command(def: CommandLineInterface) { + + let help_cmd_def = CliCommand::new( + ApiMethod::new( + dummy_help, + ObjectSchema::new("Get help about specified command.") + .optional("verbose", BooleanSchema::new("Verbose help.")) + ) + ); + + let def = match def { + CommandLineInterface::Simple(cli_cmd) => CommandLineInterface::Simple(cli_cmd), + CommandLineInterface::Nested(map) => CommandLineInterface::Nested(map.insert("help", help_cmd_def.into())), + }; + + let top_def = &def; // we pass this to the help function ... let mut args = std::env::args(); @@ -494,17 +563,17 @@ pub fn run_cli_command(def: &CommandLineInterface) { if !args.is_empty() { if args[0] == "bashcomplete" { - print_bash_completion(def); + print_bash_completion(&def); return; } if args[0] == "printdoc" { let usage = match def { CommandLineInterface::Simple(cli_cmd) => { - generate_usage_str(&prefix, cli_cmd, DocumentationFormat::ReST, "") + generate_usage_str(&prefix, &cli_cmd, DocumentationFormat::ReST, "") } CommandLineInterface::Nested(map) => { - generate_nested_usage(&prefix, map, DocumentationFormat::ReST) + generate_nested_usage(&prefix, &map, DocumentationFormat::ReST) } }; println!("{}", usage); @@ -513,8 +582,8 @@ pub fn run_cli_command(def: &CommandLineInterface) { } match def { - CommandLineInterface::Simple(cli_cmd) => handle_simple_command(&prefix, cli_cmd, args), - CommandLineInterface::Nested(map) => handle_nested_command(&prefix, map, args), + 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), }; } @@ -557,6 +626,10 @@ pub struct CliCommandMap { pub commands: HashMap, } +fn dummy_help(_param: Value, _info: &ApiMethod, _rpcenv: &mut RpcEnvironment) -> Result { + panic!("internal error"); // this is just a place holder - never call this +} + impl CliCommandMap { pub fn new() -> Self {