]> git.proxmox.com Git - proxmox-backup.git/commitdiff
src/cli/format.rs: move doc generation code into extra file
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 26 Nov 2019 11:11:21 +0000 (12:11 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 26 Nov 2019 11:12:53 +0000 (12:12 +0100)
src/cli.rs
src/cli/command.rs
src/cli/format.rs [new file with mode: 0644]

index 8292c5423881b8ab27970a74ec55de5ef8b9f5ca..02ad6a61e4cbe412972ea7d95b2934eb17d4beee 100644 (file)
@@ -7,9 +7,105 @@
 mod environment;
 pub use environment::*;
 
+mod format;
+pub use format::*;
+
 mod getopts;
 pub use getopts::*;
 
 mod command;
 pub use command::*;
 
+use std::collections::HashMap;
+
+use proxmox::api::ApiMethod;
+
+pub type CompletionFunction = fn(&str, &HashMap<String, String>) -> Vec<String>;
+
+pub struct CliCommand {
+    pub info: &'static ApiMethod,
+    pub arg_param: &'static [&'static str],
+    pub fixed_param: HashMap<&'static str, String>,
+    pub completion_functions: HashMap<String, CompletionFunction>,
+}
+
+impl CliCommand {
+
+    pub fn new(info: &'static ApiMethod) -> Self {
+        Self {
+            info, arg_param: &[],
+            fixed_param: HashMap::new(),
+            completion_functions: HashMap::new(),
+        }
+    }
+
+    pub fn arg_param(mut self, names: &'static [&'static str]) -> Self {
+        self.arg_param = names;
+        self
+    }
+
+    pub fn fixed_param(mut self, key: &'static str, value: String) -> Self {
+        self.fixed_param.insert(key, value);
+        self
+    }
+
+    pub fn completion_cb(mut self, param_name: &str, cb:  CompletionFunction) -> Self {
+        self.completion_functions.insert(param_name.into(), cb);
+        self
+    }
+}
+
+pub struct CliCommandMap {
+    pub commands: HashMap<String, CommandLineInterface>,
+}
+
+impl CliCommandMap {
+
+    pub fn new() -> Self {
+        Self { commands: HashMap:: new() }
+    }
+
+    pub fn insert<S: Into<String>>(mut self, name: S, cli: CommandLineInterface) -> Self {
+        self.commands.insert(name.into(), cli);
+        self
+    }
+
+    fn find_command(&self, name: &str) -> Option<&CommandLineInterface> {
+
+        if let Some(sub_cmd) = self.commands.get(name) {
+            return Some(sub_cmd);
+        };
+
+        let mut matches: Vec<&str> = vec![];
+
+        for cmd in self.commands.keys() {
+            if cmd.starts_with(name) {
+                matches.push(cmd); }
+        }
+
+        if matches.len() != 1 { return None; }
+
+        if let Some(sub_cmd) = self.commands.get(matches[0]) {
+            return Some(sub_cmd);
+        };
+
+        None
+    }
+}
+
+pub enum CommandLineInterface {
+    Simple(CliCommand),
+    Nested(CliCommandMap),
+}
+
+impl From<CliCommand> for CommandLineInterface {
+    fn from(cli_cmd: CliCommand) -> Self {
+         CommandLineInterface::Simple(cli_cmd)
+    }
+}
+
+impl From<CliCommandMap> for CommandLineInterface {
+    fn from(list: CliCommandMap) -> Self {
+        CommandLineInterface::Nested(list)
+    }
+}
index 5636606cbc82445eec43ad16b509e5c8e1ebc71f..e64ca898bc6555362e98ca79579cbf653f3d7994 100644 (file)
@@ -1,7 +1,6 @@
 use failure::*;
 use serde_json::Value;
-
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 
 use proxmox::api::format::*;
 use proxmox::api::schema::*;
@@ -10,158 +9,14 @@ use proxmox::api::{ApiHandler, ApiMethod};
 use super::environment::CliEnvironment;
 
 use super::getopts;
+use super::{CommandLineInterface, CliCommand, CliCommandMap, CompletionFunction};
+use super::format::*;
 
 pub const OUTPUT_FORMAT: Schema =
     StringSchema::new("Output format.")
     .format(&ApiStringFormat::Enum(&["text", "json", "json-pretty"]))
     .schema();
 
-/// Helper function to format and print result
-///
-/// This is implemented for machine generatable formats 'json' and
-/// 'json-pretty'. The 'text' format needs to be handled somewhere
-/// else.
-pub fn format_and_print_result(result: &Value, output_format: &str) {
-
-    if output_format == "json-pretty" {
-        println!("{}", serde_json::to_string_pretty(&result).unwrap());
-    } else if output_format == "json" {
-        println!("{}", serde_json::to_string(&result).unwrap());
-    } else {
-        unimplemented!();
-    }
-}
-
-fn generate_usage_str(
-    prefix: &str,
-    cli_cmd: &CliCommand,
-    format: DocumentationFormat,
-    indent: &str) -> String {
-
-    let arg_param = cli_cmd.arg_param;
-    let fixed_param = &cli_cmd.fixed_param;
-    let schema = cli_cmd.info.parameters;
-    let mut done_hash = HashSet::<&str>::new();
-    let mut args = String::new();
-
-    for positional_arg in arg_param {
-        match schema.lookup(positional_arg) {
-            Some((optional, param_schema)) => {
-                args.push(' ');
-
-                let is_array = if let Schema::Array(_) = param_schema { true } else { false };
-                if optional { args.push('['); }
-                if is_array { args.push('{'); }
-                args.push('<'); args.push_str(positional_arg); args.push('>');
-                if is_array { args.push('}'); }
-                if optional { args.push(']'); }
-
-                done_hash.insert(positional_arg);
-            }
-            None => panic!("no such property '{}' in schema", positional_arg),
-        }
-    }
-
-    let mut arg_descr = String::new();
-    for positional_arg in arg_param {
-        let (_optional, param_schema) = schema.lookup(positional_arg).unwrap();
-        let param_descr = get_property_description(
-            positional_arg, param_schema, ParameterDisplayStyle::Fixed, format);
-        arg_descr.push_str(&param_descr);
-    }
-
-    let mut options = String::new();
-
-    for (prop, optional, param_schema) in schema.properties {
-        if done_hash.contains(prop) { continue; }
-        if fixed_param.contains_key(prop) { continue; }
-
-        let type_text = get_schema_type_text(param_schema, ParameterDisplayStyle::Arg);
-
-        if *optional {
-
-            if options.len() > 0 { options.push('\n'); }
-            options.push_str(&get_property_description(prop, param_schema, ParameterDisplayStyle::Arg, format));
-
-        } else {
-            args.push_str(" --"); args.push_str(prop);
-            args.push(' ');
-            args.push_str(&type_text);
-        }
-
-        done_hash.insert(prop);
-    }
-
-    let option_indicator = if options.len() > 0 { " [OPTIONS]" } else { "" };
-
-    let mut text = match format {
-        DocumentationFormat::Short => {
-            return format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator);
-        }
-        DocumentationFormat::Long => {
-            format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator)
-        }
-        DocumentationFormat::Full => {
-            format!("{}{}{}{}\n\n{}\n\n", indent, prefix, args, option_indicator, schema.description)
-        }
-        DocumentationFormat::ReST => {
-            format!("``{}{}{}``\n\n{}\n\n", prefix, args, option_indicator, schema.description)
-        }
-    };
-
-    if arg_descr.len() > 0 {
-        text.push_str(&arg_descr);
-        text.push('\n');
-    }
-    if options.len() > 0 {
-        text.push_str(&options);
-        text.push('\n');
-    }
-    text
-}
-
-fn print_simple_usage_error(prefix: &str, cli_cmd: &CliCommand, err: Error) {
-
-    let usage =  generate_usage_str(prefix, cli_cmd, DocumentationFormat::Long, "");
-    eprint!("Error: {}\nUsage: {}", err, usage);
-}
-
-pub 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,
@@ -210,61 +65,6 @@ fn handle_simple_command(
     }    
 }
 
-fn find_command<'a>(def: &'a CliCommandMap, name: &str) -> Option<&'a CommandLineInterface> {
-
-    if let Some(sub_cmd) = def.commands.get(name) {
-        return Some(sub_cmd);
-    };
-
-    let mut matches: Vec<&str> = vec![];
-
-    for cmd in def.commands.keys() {
-        if cmd.starts_with(name) {
-             matches.push(cmd); }
-    }
-
-    if matches.len() != 1 { return None; }
-
-    if let Some(sub_cmd) = def.commands.get(matches[0]) {
-        return Some(sub_cmd);
-    };
-
-    None
-}
-
-fn print_nested_usage_error(prefix: &str, def: &CliCommandMap, err: Error) {
-
-    let usage = generate_nested_usage(prefix, def, DocumentationFormat::Short);
-
-    eprintln!("Error: {}\n\nUsage:\n\n{}", err, usage);
-}
-
-fn generate_nested_usage(prefix: &str, def: &CliCommandMap, format: DocumentationFormat) -> String {
-
-    let mut cmds: Vec<&String> = def.commands.keys().collect();
-    cmds.sort();
-
-    let mut usage = String::new();
-
-    for cmd in cmds {
-        let new_prefix = format!("{} {}", prefix, cmd);
-
-        match def.commands.get(cmd).unwrap() {
-            CommandLineInterface::Simple(cli_cmd) => {
-                if usage.len() > 0 && format == DocumentationFormat::ReST {
-                    usage.push_str("----\n\n");
-                }
-                usage.push_str(&generate_usage_str(&new_prefix, cli_cmd, format, ""));
-            }
-            CommandLineInterface::Nested(map) => {
-                usage.push_str(&generate_nested_usage(&new_prefix, map, format));
-            }
-        }
-    }
-
-    usage
-}
-
 fn handle_nested_command(
     top_def: &CommandLineInterface,
     prefix: &str,
@@ -289,7 +89,7 @@ fn handle_nested_command(
 
     let command = args.remove(0);
 
-    let sub_cmd = match find_command(def, &command) {
+    let sub_cmd = match def.find_command(&command) {
         Some(cmd) => cmd,
         None => {
             let err = format_err!("no such command '{}'", command);
@@ -572,70 +372,3 @@ pub fn run_cli_command(def: CommandLineInterface) {
     };
 }
 
-pub type CompletionFunction = fn(&str, &HashMap<String, String>) -> Vec<String>;
-
-pub struct CliCommand {
-    pub info: &'static ApiMethod,
-    pub arg_param: &'static [&'static str],
-    pub fixed_param: HashMap<&'static str, String>,
-    pub completion_functions: HashMap<String, CompletionFunction>,
-}
-
-impl CliCommand {
-
-    pub fn new(info: &'static ApiMethod) -> Self {
-        Self {
-            info, arg_param: &[],
-            fixed_param: HashMap::new(),
-            completion_functions: HashMap::new(),
-        }
-    }
-
-    pub fn arg_param(mut self, names: &'static [&'static str]) -> Self {
-        self.arg_param = names;
-        self
-    }
-
-    pub fn fixed_param(mut self, key: &'static str, value: String) -> Self {
-        self.fixed_param.insert(key, value);
-        self
-    }
-
-    pub fn completion_cb(mut self, param_name: &str, cb:  CompletionFunction) -> Self {
-        self.completion_functions.insert(param_name.into(), cb);
-        self
-    }
-}
-
-pub struct CliCommandMap {
-    pub commands: HashMap<String, CommandLineInterface>,
-}
-
-impl CliCommandMap {
-
-    pub fn new() -> Self {
-        Self { commands: HashMap:: new() }
-    }
-
-    pub fn insert<S: Into<String>>(mut self, name: S, cli: CommandLineInterface) -> Self {
-        self.commands.insert(name.into(), cli);
-        self
-    }
-}
-
-pub enum CommandLineInterface {
-    Simple(CliCommand),
-    Nested(CliCommandMap),
-}
-
-impl From<CliCommand> for CommandLineInterface {
-    fn from(cli_cmd: CliCommand) -> Self {
-         CommandLineInterface::Simple(cli_cmd)
-    }
-}
-
-impl From<CliCommandMap> for CommandLineInterface {
-    fn from(list: CliCommandMap) -> Self {
-        CommandLineInterface::Nested(list)
-    }
-}
diff --git a/src/cli/format.rs b/src/cli/format.rs
new file mode 100644 (file)
index 0000000..df0a312
--- /dev/null
@@ -0,0 +1,201 @@
+use failure::*;
+use serde_json::Value;
+
+use std::collections::HashSet;
+
+use proxmox::api::schema::*;
+use proxmox::api::format::*;
+
+use super::{CommandLineInterface, CliCommand, CliCommandMap};
+
+/// Helper function to format and print result
+///
+/// This is implemented for machine generatable formats 'json' and
+/// 'json-pretty'. The 'text' format needs to be handled somewhere
+/// else.
+pub fn format_and_print_result(
+    result: &Value,
+    output_format: &str,
+) {
+
+    if output_format == "json-pretty" {
+        println!("{}", serde_json::to_string_pretty(&result).unwrap());
+    } else if output_format == "json" {
+        println!("{}", serde_json::to_string(&result).unwrap());
+    } else {
+        unimplemented!();
+    }
+}
+
+pub fn generate_usage_str(
+    prefix: &str,
+    cli_cmd: &CliCommand,
+    format: DocumentationFormat,
+    indent: &str) -> String {
+
+    let arg_param = cli_cmd.arg_param;
+    let fixed_param = &cli_cmd.fixed_param;
+    let schema = cli_cmd.info.parameters;
+
+    let mut done_hash = HashSet::<&str>::new();
+    let mut args = String::new();
+
+    for positional_arg in arg_param {
+        match schema.lookup(positional_arg) {
+            Some((optional, param_schema)) => {
+                args.push(' ');
+
+                let is_array = if let Schema::Array(_) = param_schema { true } else { false };
+                if optional { args.push('['); }
+                if is_array { args.push('{'); }
+                args.push('<'); args.push_str(positional_arg); args.push('>');
+                if is_array { args.push('}'); }
+                if optional { args.push(']'); }
+
+                done_hash.insert(positional_arg);
+            }
+            None => panic!("no such property '{}' in schema", positional_arg),
+        }
+    }
+
+    let mut arg_descr = String::new();
+    for positional_arg in arg_param {
+        let (_optional, param_schema) = schema.lookup(positional_arg).unwrap();
+        let param_descr = get_property_description(
+            positional_arg, param_schema, ParameterDisplayStyle::Fixed, format);
+        arg_descr.push_str(&param_descr);
+    }
+
+    let mut options = String::new();
+
+    for (prop, optional, param_schema) in schema.properties {
+        if done_hash.contains(prop) { continue; }
+        if fixed_param.contains_key(prop) { continue; }
+
+        let type_text = get_schema_type_text(param_schema, ParameterDisplayStyle::Arg);
+
+        if *optional {
+
+            if options.len() > 0 { options.push('\n'); }
+            options.push_str(&get_property_description(prop, param_schema, ParameterDisplayStyle::Arg, format));
+
+        } else {
+            args.push_str(" --"); args.push_str(prop);
+            args.push(' ');
+            args.push_str(&type_text);
+        }
+
+        done_hash.insert(prop);
+    }
+
+    let option_indicator = if options.len() > 0 { " [OPTIONS]" } else { "" };
+
+    let mut text = match format {
+        DocumentationFormat::Short => {
+            return format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator);
+        }
+        DocumentationFormat::Long => {
+            format!("{}{}{}{}\n\n", indent, prefix, args, option_indicator)
+        }
+        DocumentationFormat::Full => {
+            format!("{}{}{}{}\n\n{}\n\n", indent, prefix, args, option_indicator, schema.description)
+        }
+        DocumentationFormat::ReST => {
+            format!("``{}{}{}``\n\n{}\n\n", prefix, args, option_indicator, schema.description)
+        }
+    };
+
+    if arg_descr.len() > 0 {
+        text.push_str(&arg_descr);
+        text.push('\n');
+    }
+    if options.len() > 0 {
+        text.push_str(&options);
+        text.push('\n');
+    }
+    text
+}
+
+pub fn print_simple_usage_error(
+    prefix: &str,
+    cli_cmd: &CliCommand,
+    err: Error,
+) {
+    let usage =  generate_usage_str(prefix, cli_cmd, DocumentationFormat::Long, "");
+    eprint!("Error: {}\nUsage: {}", err, usage);
+}
+
+pub fn print_nested_usage_error(
+    prefix: &str,
+    def: &CliCommandMap,
+    err: Error,
+) {
+    let usage = generate_nested_usage(prefix, def, DocumentationFormat::Short);
+    eprintln!("Error: {}\n\nUsage:\n\n{}", err, usage);
+}
+
+pub fn generate_nested_usage(
+    prefix: &str,
+    def: &CliCommandMap,
+    format: DocumentationFormat
+) -> String {
+
+    let mut cmds: Vec<&String> = def.commands.keys().collect();
+    cmds.sort();
+
+    let mut usage = String::new();
+
+    for cmd in cmds {
+        let new_prefix = format!("{} {}", prefix, cmd);
+
+        match def.commands.get(cmd).unwrap() {
+            CommandLineInterface::Simple(cli_cmd) => {
+                if usage.len() > 0 && format == DocumentationFormat::ReST {
+                    usage.push_str("----\n\n");
+                }
+                usage.push_str(&generate_usage_str(&new_prefix, cli_cmd, format, ""));
+            }
+            CommandLineInterface::Nested(map) => {
+                usage.push_str(&generate_nested_usage(&new_prefix, map, format));
+            }
+        }
+    }
+
+    usage
+}
+
+pub 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) = map.find_command(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, ""));
+        }
+    }
+}