use crate::cli::args::Commands;
use crate::model::loader::Models;
use crate::model::types::{Operation, Resource};
const CLOUD_MANAGEMENT: &[&str] = &[
"cluster", "project", "backup", "import", "volume", "job", "billing", "alert",
];
const DATA_OPERATIONS: &[&str] = &[
"collection", "vector", "database", "index", "partition", "user", "role", "alias",
];
struct ConfigCmd {
name: &'static str,
description: &'static str,
}
const CONFIGURATION: &[ConfigCmd] = &[
ConfigCmd {
name: "configure",
description: "Configure API key and default settings.",
},
ConfigCmd {
name: "context",
description: "Manage current cluster context.",
},
ConfigCmd {
name: "auth",
description: "Authentication commands.",
},
ConfigCmd {
name: "login",
description: "Log in to Zilliz Cloud.",
},
ConfigCmd {
name: "logout",
description: "Log out and clear stored credentials.",
},
ConfigCmd {
name: "completion",
description: "Shell completion management.",
},
ConfigCmd {
name: "version",
description: "Show CLI version.",
},
];
pub fn render_help(models: &Models) -> String {
let version = env!("CARGO_PKG_VERSION");
let mut out = String::new();
out.push_str(&format!("CLI and TUI for Zilliz Cloud {}\n\n", version));
out.push_str("Usage: zz [OPTIONS] <COMMAND> [ARGS]\n\n");
out.push_str("Cloud Management:\n");
append_resource_group(&mut out, CLOUD_MANAGEMENT, models);
out.push('\n');
out.push_str("Data Operations:\n");
append_resource_group(&mut out, DATA_OPERATIONS, models);
out.push('\n');
out.push_str("Configuration:\n");
for cmd in CONFIGURATION {
out.push_str(&format!(" {:16}{}\n", cmd.name, cmd.description));
}
out.push('\n');
out.push_str(
"Options:\n\
\x20 --api-key <KEY> API key (overrides env/config) [env: ZILLIZ_API_KEY]\n\
\x20 -o, --output <FORMAT> Output format: json, table, text, yaml, csv [default: table]\n\
\x20 -a, --all Fetch all pages for paginated results\n\
\x20 --query <QUERY> JMESPath query to filter output\n\
\x20 --no-header Suppress table/CSV header row\n\
\x20 --wait Wait for async jobs to complete\n\
\x20 -h, --help Print help\n\
\x20 -V, --version Print version\n",
);
out
}
fn append_resource_group(out: &mut String, names: &[&str], models: &Models) {
for &name in names {
let desc = lookup_description(name, models);
out.push_str(&format!(" {:16}{}\n", name, desc));
}
}
pub fn subcommand_name(cmd: &Commands) -> &'static str {
match cmd {
Commands::Configure { .. } => "configure",
Commands::Context(_) => "context",
Commands::Version => "version",
Commands::Login { .. } => "login",
Commands::Logout => "logout",
Commands::Auth(_) => "auth",
Commands::Completion(_) => "completion",
Commands::Alert { .. } => "alert",
Commands::External(_) => "external",
}
}
fn lookup_description<'a>(name: &str, models: &'a Models) -> &'a str {
if let Some(r) = models.control_plane.resources.get(name) {
if let Some(ref d) = r.description {
return d.as_str();
}
}
if let Some(r) = models.data_plane.resources.get(name) {
if let Some(ref d) = r.description {
return d.as_str();
}
}
""
}
const HAND_WRITTEN_OPS: &[(&str, &str, &str)] = &[
("cluster", "create", "Create a new cluster."),
("cluster", "metrics", "Show cluster metrics."),
("billing", "usage", "Show billing usage summary."),
("billing", "invoices", "List invoices."),
];
pub fn find_resource<'a>(models: &'a Models, name: &str) -> Option<&'a Resource> {
models
.control_plane
.resources
.get(name)
.or_else(|| models.data_plane.resources.get(name))
}
pub fn render_resource_help(resource_name: &str, resource: &Resource) -> String {
let mut out = String::new();
let desc = resource
.description
.as_deref()
.unwrap_or("No description.");
out.push_str(&format!("{}\n\n", desc));
out.push_str(&format!(
"Usage: zz {} <OPERATION> [OPTIONS]\n\n",
resource_name
));
out.push_str("Operations:\n");
for &(res, op, desc) in HAND_WRITTEN_OPS {
if res == resource_name && !resource.operations.contains_key(op) {
out.push_str(&format!(" {:24}{}\n", op, desc));
}
}
for (op_name, op) in &resource.operations {
let op_desc = op.description.as_deref().unwrap_or("");
out.push_str(&format!(" {:24}{}\n", op_name, op_desc));
}
out
}
pub fn render_operation_help(
resource_name: &str,
op_name: &str,
operation: &Operation,
) -> String {
let mut out = String::new();
let desc = operation
.description
.as_deref()
.unwrap_or("No description.");
out.push_str(&format!("{}\n\n", desc));
out.push_str(&format!(
"Usage: zz {} {} [OPTIONS]\n\n",
resource_name, op_name
));
if !operation.params.is_empty() {
out.push_str("Options:\n");
for p in &operation.params {
let flag = p.cli_flag();
let mut meta = format!("<{}>", p.param_type);
if p.required {
meta.push_str(" (required)");
}
if let Some(ref def) = p.default {
meta.push_str(&format!(" [default: {}]", def));
}
if let Some(ref choices) = p.choices {
meta.push_str(&format!(" [{}]", choices.join(", ")));
}
let desc = p.description.as_deref().unwrap_or("");
out.push_str(&format!(" {:24}{:30}{}\n", flag, meta, desc));
}
out.push('\n');
}
if !operation.examples.is_empty() {
out.push_str("Examples:\n");
for ex in &operation.examples {
if ex.is_empty() {
out.push('\n');
} else {
out.push_str(&format!(" {}\n", ex));
}
}
}
out
}
pub fn available_resources(models: &Models) -> Vec<&str> {
let mut names: Vec<&str> = Vec::new();
for name in models.control_plane.resources.keys() {
names.push(name.as_str());
}
for name in models.data_plane.resources.keys() {
names.push(name.as_str());
}
names
}