1 use anyhow
::{format_err, Error}
;
3 use std
::cell
::RefCell
;
6 use proxmox_schema
::format
::DocumentationFormat
;
9 use super::environment
::CliEnvironment
;
12 generate_nested_usage
, generate_usage_str
, print_help
, print_nested_usage_error
,
13 print_simple_usage_error
, CliCommand
, CliCommandMap
, CommandLineInterface
,
15 use crate::{ApiFuture, ApiHandler, ApiMethod, RpcEnvironment}
;
17 /// Schema definition for ``--output-format`` parameter.
19 /// - ``text``: command specific text format.
20 /// - ``json``: JSON, single line.
21 /// - ``json-pretty``: JSON, human readable.
23 pub const OUTPUT_FORMAT
: Schema
= StringSchema
::new("Output format.")
24 .format(&ApiStringFormat
::Enum(&[
25 EnumEntry
::new("text", "plain text output"),
26 EnumEntry
::new("json", "single-line json formatted output"),
27 EnumEntry
::new("json-pretty", "pretty-printed json output"),
31 fn parse_arguments(prefix
: &str, cli_cmd
: &CliCommand
, args
: Vec
<String
>) -> Result
<Value
, Error
> {
32 let (params
, remaining
) = match getopts
::parse_arguments(
36 cli_cmd
.info
.parameters
,
40 let err_msg
= err
.to_string();
41 print_simple_usage_error(prefix
, cli_cmd
, &err_msg
);
42 return Err(format_err
!("{}", err_msg
));
46 if !remaining
.is_empty() {
47 let err_msg
= format
!("got additional arguments: {:?}", remaining
);
48 print_simple_usage_error(prefix
, cli_cmd
, &err_msg
);
49 return Err(format_err
!("{}", err_msg
));
55 async
fn handle_simple_command_future(
59 mut rpcenv
: CliEnvironment
,
60 ) -> Result
<(), Error
> {
61 let params
= parse_arguments(prefix
, cli_cmd
, args
)?
;
63 let result
= match cli_cmd
.info
.handler
{
64 ApiHandler
::Sync(handler
) => (handler
)(params
, cli_cmd
.info
, &mut rpcenv
),
65 ApiHandler
::StreamingSync(handler
) => (handler
)(params
, cli_cmd
.info
, &mut rpcenv
)
66 .and_then(|r
| r
.to_value().map_err(Error
::from
)),
67 ApiHandler
::Async(handler
) => (handler
)(params
, cli_cmd
.info
, &mut rpcenv
).await
,
68 ApiHandler
::StreamingAsync(handler
) => (handler
)(params
, cli_cmd
.info
, &mut rpcenv
)
70 .and_then(|r
| r
.to_value().map_err(Error
::from
)),
71 #[cfg(feature = "server")]
72 ApiHandler
::AsyncHttp(_
) => {
73 let err_msg
= "CliHandler does not support ApiHandler::AsyncHttp - internal error";
74 print_simple_usage_error(prefix
, cli_cmd
, err_msg
);
75 return Err(format_err
!("{}", err_msg
));
81 if value
!= Value
::Null
{
82 println
!("Result: {}", serde_json
::to_string_pretty(&value
).unwrap());
86 eprintln
!("Error: {}", err
);
94 fn handle_simple_command(
98 mut rpcenv
: CliEnvironment
,
99 run
: Option
<fn(ApiFuture
) -> Result
<Value
, Error
>>,
100 ) -> Result
<(), Error
> {
101 let params
= parse_arguments(prefix
, cli_cmd
, args
)?
;
103 let result
= match cli_cmd
.info
.handler
{
104 ApiHandler
::Sync(handler
) => (handler
)(params
, cli_cmd
.info
, &mut rpcenv
),
105 ApiHandler
::StreamingSync(handler
) => (handler
)(params
, cli_cmd
.info
, &mut rpcenv
)
106 .and_then(|r
| r
.to_value().map_err(Error
::from
)),
107 ApiHandler
::Async(handler
) => match run
{
109 let future
= (handler
)(params
, cli_cmd
.info
, &mut rpcenv
);
113 let err_msg
= "CliHandler does not support ApiHandler::Async - internal error";
114 print_simple_usage_error(prefix
, cli_cmd
, err_msg
);
115 return Err(format_err
!("{}", err_msg
));
118 ApiHandler
::StreamingAsync(_handler
) => {
119 let err_msg
= "CliHandler does not support ApiHandler::StreamingAsync - internal error";
120 print_simple_usage_error(prefix
, cli_cmd
, err_msg
);
121 return Err(format_err
!("{}", err_msg
));
123 #[cfg(feature = "server")]
124 ApiHandler
::AsyncHttp(_
) => {
125 let err_msg
= "CliHandler does not support ApiHandler::AsyncHttp - internal error";
126 print_simple_usage_error(prefix
, cli_cmd
, err_msg
);
127 return Err(format_err
!("{}", err_msg
));
133 if value
!= Value
::Null
{
134 println
!("Result: {}", serde_json
::to_string_pretty(&value
).unwrap());
138 eprintln
!("Error: {}", err
);
146 fn parse_nested_command
<'a
>(
148 def
: &'a CliCommandMap
,
149 args
: &mut Vec
<String
>,
150 ) -> Result
<&'a CliCommand
, Error
> {
153 // Note: Avoid async recursive function, because current rust compiler cant handle that
155 replace_aliases(args
, &map
.aliases
);
158 let mut cmds
: Vec
<&String
> = map
.commands
.keys().collect();
161 let list
= cmds
.iter().fold(String
::new(), |mut s
, item
| {
169 let err_msg
= format
!("no command specified.\nPossible commands: {}", list
);
170 print_nested_usage_error(prefix
, map
, &err_msg
);
171 return Err(format_err
!("{}", err_msg
));
174 let command
= args
.remove(0);
176 let (_
, sub_cmd
) = match map
.find_command(&command
) {
179 let err_msg
= format
!("no such command '{}'", command
);
180 print_nested_usage_error(prefix
, map
, &err_msg
);
181 return Err(format_err
!("{}", err_msg
));
185 *prefix
= format
!("{} {}", prefix
, command
);
188 CommandLineInterface
::Simple(cli_cmd
) => {
189 //return handle_simple_command(&prefix, cli_cmd, args).await;
192 CommandLineInterface
::Nested(new_map
) => map
= new_map
,
197 const API_METHOD_COMMAND_HELP
: ApiMethod
= ApiMethod
::new(
198 &ApiHandler
::Sync(&help_command
),
200 "Get help about specified command (or sub-command).",
206 "Command. This may be a list in order to spefify nested sub-commands.",
207 &StringSchema
::new("Name.").schema(),
214 &BooleanSchema
::new("Verbose help.").schema(),
221 static HELP_CONTEXT
: RefCell
<Option
<Arc
<CommandLineInterface
>>> = RefCell
::new(None
);
227 _rpcenv
: &mut dyn RpcEnvironment
,
228 ) -> Result
<Value
, Error
> {
229 let mut command
: Vec
<String
> = param
["command"]
231 .unwrap_or(&Vec
::new())
233 .map(|v
| v
.as_str().unwrap().to_string())
236 let verbose
= param
["verbose"].as_bool();
238 HELP_CONTEXT
.with(|ctx
| match &*ctx
.borrow() {
240 if let CommandLineInterface
::Nested(map
) = def
.as_ref() {
241 replace_aliases(&mut command
, &map
.aliases
);
243 print_help(def
, String
::from(""), &command
, verbose
);
246 eprintln
!("Sorry, help context not set - internal error.");
253 fn set_help_context(def
: Option
<Arc
<CommandLineInterface
>>) {
254 HELP_CONTEXT
.with(|ctx
| {
255 *ctx
.borrow_mut() = def
;
259 pub(crate) fn help_command_def() -> CliCommand
{
260 CliCommand
::new(&API_METHOD_COMMAND_HELP
).arg_param(&["command"])
263 fn replace_aliases(args
: &mut Vec
<String
>, aliases
: &[(Vec
<&'
static str>, Vec
<&'
static str>)]) {
264 for (old
, new
) in aliases
{
265 if args
.len() < old
.len() {
268 if old
[..] == args
[..old
.len()] {
269 let new_args
: Vec
<String
> = new
.iter().map(|s
| String
::from(*s
)).collect();
270 let rest
= args
.split_off(old
.len());
272 args
.extend(new_args
);
273 for arg
in rest
.iter() {
274 args
.push(arg
.clone());
281 /// Handle command invocation.
283 /// This command gets the command line ``args`` and tries to invoke
284 /// the corresponding API handler.
285 pub async
fn handle_command_future(
286 def
: Arc
<CommandLineInterface
>,
288 mut args
: Vec
<String
>,
289 rpcenv
: CliEnvironment
,
290 ) -> Result
<(), Error
> {
291 set_help_context(Some(def
.clone()));
293 let result
= match &*def
{
294 CommandLineInterface
::Simple(ref cli_cmd
) => {
295 handle_simple_command_future(prefix
, cli_cmd
, args
, rpcenv
).await
297 CommandLineInterface
::Nested(ref map
) => {
298 let mut prefix
= prefix
.to_string();
299 let cli_cmd
= parse_nested_command(&mut prefix
, map
, &mut args
)?
;
300 handle_simple_command_future(&prefix
, cli_cmd
, args
, rpcenv
).await
304 set_help_context(None
);
309 /// Handle command invocation.
311 /// This command gets the command line ``args`` and tries to invoke
312 /// the corresponding API handler.
313 pub fn handle_command(
314 def
: Arc
<CommandLineInterface
>,
316 mut args
: Vec
<String
>,
317 rpcenv
: CliEnvironment
,
318 run
: Option
<fn(ApiFuture
) -> Result
<Value
, Error
>>,
319 ) -> Result
<(), Error
> {
320 set_help_context(Some(def
.clone()));
322 let result
= match &*def
{
323 CommandLineInterface
::Simple(ref cli_cmd
) => {
324 handle_simple_command(prefix
, cli_cmd
, args
, rpcenv
, run
)
326 CommandLineInterface
::Nested(ref map
) => {
327 let mut prefix
= prefix
.to_string();
328 let cli_cmd
= parse_nested_command(&mut prefix
, map
, &mut args
)?
;
329 handle_simple_command(&prefix
, cli_cmd
, args
, rpcenv
, run
)
333 set_help_context(None
);
338 fn prepare_cli_command(def
: &CommandLineInterface
) -> (String
, Vec
<String
>) {
339 let mut args
= std
::env
::args();
341 let prefix
= args
.next().unwrap();
342 let prefix
= prefix
.rsplit('
/'
).next().unwrap().to_string(); // without path
344 let args
: Vec
<String
> = args
.collect();
346 if !args
.is_empty() {
347 if args
[0] == "bashcomplete" {
348 def
.print_bash_completion();
349 std
::process
::exit(0);
352 if args
[0] == "printdoc" {
353 let usage
= match def
{
354 CommandLineInterface
::Simple(cli_cmd
) => {
355 generate_usage_str(&prefix
, cli_cmd
, DocumentationFormat
::ReST
, "", &[])
357 CommandLineInterface
::Nested(map
) => {
358 generate_nested_usage(&prefix
, map
, DocumentationFormat
::ReST
)
361 println
!("{}", usage
);
362 std
::process
::exit(0);
369 /// Helper to get arguments and invoke the command (async).
371 /// This helper reads arguments with ``std::env::args()``. The first
372 /// argument is assumed to be the program name, and is passed as ``prefix`` to
373 /// ``handle_command()``.
375 /// This helper automatically add the help command, and two special
378 /// - ``bashcomplete``: Output bash completions instead of running the command.
379 /// - ``printdoc``: Output ReST documentation.
381 pub async
fn run_async_cli_command
<C
: Into
<CommandLineInterface
>>(def
: C
, rpcenv
: CliEnvironment
) {
382 let def
= match def
.into() {
383 CommandLineInterface
::Simple(cli_cmd
) => CommandLineInterface
::Simple(cli_cmd
),
384 CommandLineInterface
::Nested(map
) => CommandLineInterface
::Nested(map
.insert_help()),
387 let (prefix
, args
) = prepare_cli_command(&def
);
389 if handle_command_future(Arc
::new(def
), &prefix
, args
, rpcenv
)
393 std
::process
::exit(-1);
397 /// Helper to get arguments and invoke the command.
399 /// This is the synchrounous version of run_async_cli_command. You can
400 /// pass an optional ``run`` function to execute async commands (else
401 /// async commands simply fail).
402 pub fn run_cli_command
<C
: Into
<CommandLineInterface
>>(
404 rpcenv
: CliEnvironment
,
405 run
: Option
<fn(ApiFuture
) -> Result
<Value
, Error
>>,
407 let def
= match def
.into() {
408 CommandLineInterface
::Simple(cli_cmd
) => CommandLineInterface
::Simple(cli_cmd
),
409 CommandLineInterface
::Nested(map
) => CommandLineInterface
::Nested(map
.insert_help()),
412 let (prefix
, args
) = prepare_cli_command(&def
);
414 if handle_command(Arc
::new(def
), &prefix
, args
, rpcenv
, run
).is_err() {
415 std
::process
::exit(-1);