src/cli/command.rs: add help command

This commit is contained in:
Dietmar Maurer 2019-02-23 15:10:48 +01:00
parent 8b6dd2240d
commit 698d9d4402
4 changed files with 102 additions and 29 deletions

View File

@ -185,5 +185,5 @@ fn main() {
.into() .into()
); );
run_cli_command(&cmd_def.into()); run_cli_command(cmd_def.into());
} }

View File

@ -236,5 +236,5 @@ fn main() {
.insert("garbage-collect".to_owned(), garbage_collect_cmd_def.into()) .insert("garbage-collect".to_owned(), garbage_collect_cmd_def.into())
.insert("list".to_owned(), list_cmd_def.into()); .insert("list".to_owned(), list_cmd_def.into());
run_cli_command(&cmd_def.into()); run_cli_command(cmd_def.into());
} }

View File

@ -51,5 +51,5 @@ fn main() {
.insert("datastore".to_owned(), datastore_commands()) .insert("datastore".to_owned(), datastore_commands())
.insert("garbage-collection".to_owned(), garbage_collection_commands()); .insert("garbage-collection".to_owned(), garbage_collection_commands());
run_cli_command(&cmd_def.into()); run_cli_command(cmd_def.into());
} }

View File

@ -1,7 +1,7 @@
use failure::*; use failure::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
//use serde_json::Value; use serde_json::Value;
use crate::api_schema::*; use crate::api_schema::*;
use crate::api_schema::router::*; use crate::api_schema::router::*;
@ -63,14 +63,19 @@ fn get_property_description(
Schema::Array(ref schema) => (schema.description, None), Schema::Array(ref schema) => (schema.description, None),
}; };
let default_text = match default {
Some(text) => format!(" (default={})", text),
None => String::new(),
};
if format == DocumentationFormat::ReST { if format == DocumentationFormat::ReST {
let mut text = match style { let mut text = match style {
ParameterDisplayStyle::Arg => { ParameterDisplayStyle::Arg => {
format!(":``--{} {}``: ", name, type_text) format!(":``--{} {}{}``: ", name, type_text, default_text)
} }
ParameterDisplayStyle::Fixed => { ParameterDisplayStyle::Fixed => {
format!(":``<{}> {}``: ", name, type_text) format!(":``<{}> {}{}``: ", name, type_text, default_text)
} }
}; };
@ -82,11 +87,6 @@ fn get_property_description(
} else { } else {
let default_text = match default {
Some(text) => format!(" (default={})", text),
None => String::new(),
};
let display_name = match style { let display_name = match style {
ParameterDisplayStyle::Arg => { ParameterDisplayStyle::Arg => {
format!("--{}", name) format!("--{}", name)
@ -167,28 +167,30 @@ fn generate_usage_str(
done_hash.insert(prop); done_hash.insert(prop);
} }
let option_indicator = if options.len() > 0 { " [OPTIONS]" } else { "" };
let mut text = match format { let mut text = match format {
DocumentationFormat::Short => { DocumentationFormat::Short => {
return format!("{}{}{}", indent, prefix, args); return format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator);
} }
DocumentationFormat::Long => { DocumentationFormat::Long => {
format!("{}{}{}\n", indent, prefix, args) format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator)
} }
DocumentationFormat::Full => { DocumentationFormat::Full => {
format!("{}{}{}\n\n{}\n", indent, prefix, args, description) format!("{}{}{}{}\n\n{}\n\n", indent, prefix, args, option_indicator, description)
} }
DocumentationFormat::ReST => { 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 { if arg_descr.len() > 0 {
text.push('\n');
text.push_str(&arg_descr); text.push_str(&arg_descr);
text.push('\n');
} }
if options.len() > 0 { if options.len() > 0 {
text.push('\n');
text.push_str(&options); text.push_str(&options);
text.push('\n');
} }
text text
} }
@ -199,7 +201,48 @@ fn print_simple_usage_error(prefix: &str, cli_cmd: &CliCommand, err: Error) {
eprint!("Error: {}\nUsage: {}", err, usage); eprint!("Error: {}\nUsage: {}", err, usage);
} }
fn handle_simple_command(prefix: &str, cli_cmd: &CliCommand, args: Vec<String>) { fn print_help(
top_def: &CommandLineInterface,
mut prefix: String,
args: &Vec<String>,
verbose: Option<bool>,
) {
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<String>,
) {
let (params, rest) = match getopts::parse_arguments( let (params, rest) = match getopts::parse_arguments(
&args, &cli_cmd.arg_param, &cli_cmd.info.parameters) { &args, &cli_cmd.arg_param, &cli_cmd.info.parameters) {
@ -210,6 +253,12 @@ fn handle_simple_command(prefix: &str, cli_cmd: &CliCommand, args: Vec<String>)
} }
}; };
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() { if !rest.is_empty() {
let err = format_err!("got additional arguments: {:?}", rest); let err = format_err!("got additional arguments: {:?}", rest);
print_simple_usage_error(prefix, cli_cmd, err); 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) { 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 { 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 usage
} }
fn handle_nested_command(prefix: &str, def: &CliCommandMap, mut args: Vec<String>) { fn handle_nested_command(
top_def: &CommandLineInterface,
prefix: &str,
def: &CliCommandMap,
mut args: Vec<String>,
) {
if args.len() < 1 { if args.len() < 1 {
let mut cmds: Vec<&String> = def.commands.keys().collect(); let mut cmds: Vec<&String> = def.commands.keys().collect();
@ -315,10 +369,10 @@ fn handle_nested_command(prefix: &str, def: &CliCommandMap, mut args: Vec<String
match sub_cmd { match sub_cmd {
CommandLineInterface::Simple(cli_cmd) => { CommandLineInterface::Simple(cli_cmd) => {
handle_simple_command(&new_prefix, cli_cmd, args); handle_simple_command(top_def, &new_prefix, cli_cmd, args);
} }
CommandLineInterface::Nested(map) => { 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); 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(); let mut args = std::env::args();
@ -494,17 +563,17 @@ pub fn run_cli_command(def: &CommandLineInterface) {
if !args.is_empty() { if !args.is_empty() {
if args[0] == "bashcomplete" { if args[0] == "bashcomplete" {
print_bash_completion(def); print_bash_completion(&def);
return; return;
} }
if args[0] == "printdoc" { if args[0] == "printdoc" {
let usage = match def { let usage = match def {
CommandLineInterface::Simple(cli_cmd) => { CommandLineInterface::Simple(cli_cmd) => {
generate_usage_str(&prefix, cli_cmd, DocumentationFormat::ReST, "") generate_usage_str(&prefix, &cli_cmd, DocumentationFormat::ReST, "")
} }
CommandLineInterface::Nested(map) => { CommandLineInterface::Nested(map) => {
generate_nested_usage(&prefix, map, DocumentationFormat::ReST) generate_nested_usage(&prefix, &map, DocumentationFormat::ReST)
} }
}; };
println!("{}", usage); println!("{}", usage);
@ -513,8 +582,8 @@ pub fn run_cli_command(def: &CommandLineInterface) {
} }
match def { match def {
CommandLineInterface::Simple(cli_cmd) => handle_simple_command(&prefix, cli_cmd, args), CommandLineInterface::Simple(ref cli_cmd) => handle_simple_command(top_def, &prefix, &cli_cmd, args),
CommandLineInterface::Nested(map) => handle_nested_command(&prefix, map, args), CommandLineInterface::Nested(ref map) => handle_nested_command(top_def, &prefix, &map, args),
}; };
} }
@ -557,6 +626,10 @@ pub struct CliCommandMap {
pub commands: HashMap<String, CommandLineInterface>, pub commands: HashMap<String, CommandLineInterface>,
} }
fn dummy_help(_param: Value, _info: &ApiMethod, _rpcenv: &mut RpcEnvironment) -> Result<Value, Error> {
panic!("internal error"); // this is just a place holder - never call this
}
impl CliCommandMap { impl CliCommandMap {
pub fn new() -> Self { pub fn new() -> Self {