proxmox-backup/src/cli/command.rs
2019-12-01 16:41:49 +01:00

263 lines
7.4 KiB
Rust

use failure::*;
use serde_json::Value;
use std::sync::Arc;
use std::cell::RefCell;
use proxmox::api::*;
use proxmox::api::format::*;
use proxmox::api::schema::*;
use proxmox::api::{ApiHandler, ApiMethod};
use super::environment::CliEnvironment;
use super::getopts;
use super::{CommandLineInterface, CliCommand, CliCommandMap, completion::*};
use super::format::*;
/// Schema definition for ``--output-format`` parameter.
///
/// - ``text``: command specific text format.
/// - ``json``: JSON, single line.
/// - ``json-pretty``: JSON, human readable.
///
pub const OUTPUT_FORMAT: Schema =
StringSchema::new("Output format.")
.format(&ApiStringFormat::Enum(&["text", "json", "json-pretty"]))
.schema();
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) => {
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_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();
match cli_cmd.info.handler {
ApiHandler::Sync(handler) => {
match (handler)(params, &cli_cmd.info, &mut rpcenv) {
Ok(value) => {
if value != Value::Null {
println!("Result: {}", serde_json::to_string_pretty(&value).unwrap());
}
}
Err(err) => {
eprintln!("Error: {}", err);
return Err(err);
}
}
}
ApiHandler::AsyncHttp(_) => {
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(
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();
cmds.sort();
let list = cmds.iter().fold(String::new(),|mut s,item| {
if !s.is_empty() { s+= ", "; }
s += item;
s
});
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);
let (_, sub_cmd) = match def.find_command(&command) {
Some(cmd) => cmd,
None => {
let err_msg = format!("no such command '{}'", command);
print_nested_usage_error(prefix, def, &err_msg);
return Err(format_err!("{}", err_msg));
}
};
let new_prefix = format!("{} {}", prefix, command);
match sub_cmd {
CommandLineInterface::Simple(cli_cmd) => {
handle_simple_command(top_def, &new_prefix, cli_cmd, args)?;
}
CommandLineInterface::Nested(map) => {
handle_nested_command(top_def, &new_prefix, map, args)?;
}
}
Ok(())
}
const API_METHOD_COMMAND_HELP: ApiMethod = ApiMethod::new(
&ApiHandler::Sync(&help_command),
&ObjectSchema::new(
"Get help about specified command (or sub-command).",
&[
( "command",
true,
&ArraySchema::new(
"Command. This may be a list in order to spefify nested sub-commands.",
&StringSchema::new("Name.").schema()
).schema()
),
( "verbose",
true,
&BooleanSchema::new("Verbose help.").schema()
),
],
)
);
std::thread_local! {
static HELP_CONTEXT: RefCell<Option<Arc<CommandLineInterface>>> = RefCell::new(None);
}
fn help_command(
param: Value,
_info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let command: Vec<String> = param["command"].as_array().unwrap_or(&Vec::new())
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect();
let verbose = param["verbose"].as_bool();
HELP_CONTEXT.with(|ctx| {
match &*ctx.borrow() {
Some(def) => {
print_help(def, String::from(""), &command, verbose);
}
None => {
eprintln!("Sorry, help context not set - internal error.");
}
}
});
Ok(Value::Null)
}
fn set_help_context(def: Option<Arc<CommandLineInterface>>) {
HELP_CONTEXT.with(|ctx| { *ctx.borrow_mut() = def; });
}
pub(crate) fn help_command_def() -> CliCommand {
CliCommand::new(&API_METHOD_COMMAND_HELP)
.arg_param(&["command"])
}
/// Handle command invocation.
///
/// This command gets the command line ``args`` and tries to invoke
/// the corresponding API handler.
pub fn handle_command(
def: Arc<CommandLineInterface>,
prefix: &str,
args: Vec<String>,
) -> Result<(), Error> {
set_help_context(Some(def.clone()));
let result = match &*def {
CommandLineInterface::Simple(ref cli_cmd) => {
handle_simple_command(&def, &prefix, &cli_cmd, args)
}
CommandLineInterface::Nested(ref map) => {
handle_nested_command(&def, &prefix, &map, args)
}
};
set_help_context(None);
result
}
/// Helper to get arguments and invoke the command.
///
/// This helper reads arguments with ``std::env::args()``. The first
/// argument is assumed to be the program name, and is passed as ``prefix`` to
/// ``handle_command()``.
///
/// This helper automatically add the help command, and two special
/// sub-command:
///
/// - ``bashcomplete``: Output bash completions instead of running the command.
/// - ``printdoc``: Output ReST documentation.
///
pub fn run_cli_command(def: CommandLineInterface) {
let def = match def {
CommandLineInterface::Simple(cli_cmd) => CommandLineInterface::Simple(cli_cmd),
CommandLineInterface::Nested(map) =>
CommandLineInterface::Nested(map.insert("help", help_command_def().into())),
};
let mut args = std::env::args();
let prefix = args.next().unwrap();
let prefix = prefix.rsplit('/').next().unwrap(); // without path
let args: Vec<String> = args.collect();
if !args.is_empty() {
if args[0] == "bashcomplete" {
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, "")
}
CommandLineInterface::Nested(map) => {
generate_nested_usage(&prefix, &map, DocumentationFormat::ReST)
}
};
println!("{}", usage);
return;
}
}
if let Err(_) = handle_command(Arc::new(def), &prefix, args) {
std::process::exit(-1);
}
}