use anyhow::Error; use serde_json::Value; use proxmox_router::cli::{CliCommand, CliCommandMap, CommandLineInterface, GlobalOptions}; use proxmox_router::{ApiHandler, ApiMethod, RpcEnvironment}; use proxmox_schema::format::DocumentationFormat; use proxmox_schema::{ ApiStringFormat, ApiType, BooleanSchema, EnumEntry, ObjectSchema, Schema, StringSchema, }; fn dummy_method( _param: Value, _info: &ApiMethod, _rpcenv: &mut dyn RpcEnvironment, ) -> Result { Ok(Value::Null) } const API_METHOD_SIMPLE1: ApiMethod = ApiMethod::new( &ApiHandler::Sync(&dummy_method), &ObjectSchema::new( "Simple API method with one required and one optional argument.", &[ ( "optional-arg", true, &BooleanSchema::new("Optional boolean argument.") .default(false) .schema(), ), ( "required-arg", false, &StringSchema::new("Required string argument.").schema(), ), ( "another-required-arg", false, &StringSchema::new("A second required string argument.").schema(), ), ], ), ); #[allow(dead_code)] struct GlobalOpts { global: String, } impl<'de> serde::Deserialize<'de> for GlobalOpts { fn deserialize(_deserializer: D) -> Result where D: serde::Deserializer<'de>, { unreachable!("not used in tests, implemented to satisfy `.global_option` constraint"); } } impl ApiType for GlobalOpts { const API_SCHEMA: Schema = ObjectSchema::new( "Global options.", &[ ( "global1", true, &StringSchema::new("A global option.") .format(&ApiStringFormat::Enum(&[ EnumEntry::new("one", "Option one."), EnumEntry::new("two", "Option two."), ])) .schema(), ), ( "global2", true, &StringSchema::new("A second global option.").schema(), ), ], ) .schema(); } /// Generates the following: /// /// ```text /// clicmd l0c1 --required-arg --another-required-arg [--optional-arg] /// clicmd l0c2 --another-required-arg [--optional-arg] /// clicmd l0sub l1c1 --required-arg --another-required-arg [--optional-arg] /// clicmd l0sub l1c2 --required-arg --another-required-arg [--optional-arg] /// ``` fn get_complex_test_cmddef() -> CliCommandMap { let sub_def = CliCommandMap::new() .global_option(GlobalOptions::of::()) .insert("l1c1", CliCommand::new(&API_METHOD_SIMPLE1)) .insert("l1c2", CliCommand::new(&API_METHOD_SIMPLE1)); CliCommandMap::new() .insert_help() .insert("l0sub", CommandLineInterface::Nested(sub_def)) .insert("l0c1", CliCommand::new(&API_METHOD_SIMPLE1)) .insert( "l0c2", CliCommand::new(&API_METHOD_SIMPLE1).arg_param(&["required-arg"]), ) } fn expected_toplevel_help_text() -> &'static str { r##" Usage: clicmd help [{}] [OPTIONS] clicmd l0c1 --required-arg --another-required-arg [OPTIONS] clicmd l0c2 --another-required-arg [OPTIONS] clicmd l0sub l1c1 --required-arg --another-required-arg [OPTIONS] clicmd l0sub l1c2 --required-arg --another-required-arg [OPTIONS] "## .trim_start() } fn expected_group_help_text() -> &'static str { r##" Usage: clicmd l0sub l1c1 --required-arg --another-required-arg [OPTIONS] Simple API method with one required and one optional argument. --required-arg Required string argument. --another-required-arg A second required string argument. Optional parameters: --optional-arg (default=false) Optional boolean argument. Inherited group parameters: --global1 one|two A global option. --global2 A second global option. "## .trim_start() } fn expected_nested_usage_text() -> &'static str { r##" ``clicmd help [{}] [OPTIONS]`` Get help about specified command (or sub-command). ```` : ```` Command. This may be a list in order to spefify nested sub-commands. Can be specified more than once. Optional parameters: ``--verbose`` ```` Verbose help. ---- ``clicmd l0c1 --required-arg --another-required-arg [OPTIONS]`` Simple API method with one required and one optional argument. ``--required-arg`` ```` Required string argument. ``--another-required-arg`` ```` A second required string argument. Optional parameters: ``--optional-arg`` `` (default=false)`` Optional boolean argument. ---- ``clicmd l0c2 --another-required-arg [OPTIONS]`` Simple API method with one required and one optional argument. ```` : ```` Required string argument. ``--another-required-arg`` ```` A second required string argument. Optional parameters: ``--optional-arg`` `` (default=false)`` Optional boolean argument. ---- Options available for command group ``clicmd l0sub``: ``--global1`` ``one|two`` A global option. ``--global2`` ```` A second global option. ---- ``clicmd l0sub l1c1 --required-arg --another-required-arg [OPTIONS]`` Simple API method with one required and one optional argument. ``--required-arg`` ```` Required string argument. ``--another-required-arg`` ```` A second required string argument. Optional parameters: ``--optional-arg`` `` (default=false)`` Optional boolean argument. Inherited group parameters: ``--global1`` ``--global2`` ---- ``clicmd l0sub l1c2 --required-arg --another-required-arg [OPTIONS]`` Simple API method with one required and one optional argument. ``--required-arg`` ```` Required string argument. ``--another-required-arg`` ```` A second required string argument. Optional parameters: ``--optional-arg`` `` (default=false)`` Optional boolean argument. Inherited group parameters: ``--global1`` ``--global2``"## .trim_start() } #[test] fn test_nested_usage() { let doc = proxmox_router::cli::generate_nested_usage( "clicmd", &get_complex_test_cmddef(), DocumentationFormat::ReST, ); // println!("--- BEGIN EXPECTED DOC OUTPUT ---"); // print!("{doc}"); // println!("--- END EXPECTED DOC OUTPUT ---"); assert_eq!(doc, expected_nested_usage_text()); } #[test] fn test_toplevel_help() { let mut help = String::new(); proxmox_router::cli::print_help_to( &get_complex_test_cmddef().into(), "clicmd".to_string(), &[], None, &mut help, ) .expect("failed to format help string"); // println!("--- BEGIN EXPECTED DOC OUTPUT ---"); // print!("{help}"); // println!("--- END EXPECTED DOC OUTPUT ---"); assert_eq!(help, expected_toplevel_help_text()); } #[test] fn test_group_help() { let mut help = String::new(); proxmox_router::cli::print_help_to( &get_complex_test_cmddef().into(), "clicmd".to_string(), &["l0sub".to_string(), "l1c1".to_string()], None, &mut help, ) .expect("failed to format help string"); // println!("--- BEGIN EXPECTED DOC OUTPUT ---"); // print!("{help}"); // println!("--- END EXPECTED DOC OUTPUT ---"); assert_eq!(help, expected_group_help_text()); }