use clap::{ArgAction, CommandFactory};
use serde::Serialize;
use crate::error::{CliError, Result};
use crate::Cli;
#[derive(Serialize)]
struct SchemaRoot<'a> {
version: u32,
cli: &'a str,
subcommands: Vec<SchemaSubcommand>,
}
#[derive(Serialize)]
struct SchemaSubcommand {
name: String,
flags: Vec<SchemaFlag>,
positionals: Vec<SchemaPositional>,
}
#[derive(Serialize)]
struct SchemaFlag {
name: String,
required: bool,
kind: &'static str,
choices: Option<Vec<String>>,
}
#[derive(Serialize)]
struct SchemaPositional {
name: String,
required: bool,
repeating: bool,
}
pub fn run() -> Result<u8> {
let cmd = Cli::command();
let mut subcommands: Vec<SchemaSubcommand> = Vec::new();
for sub in cmd.get_subcommands() {
let name = sub.get_name();
if name == "help" || name == "gui-schema" {
continue;
}
subcommands.push(reflect_subcommand(sub));
}
let root = SchemaRoot {
version: 1,
cli: "ms",
subcommands,
};
let s = serde_json::to_string(&root)
.map_err(|e| CliError::BadInput(format!("gui-schema serialization: {}", e)))?;
println!("{}", s);
Ok(0)
}
fn reflect_subcommand(sub: &clap::Command) -> SchemaSubcommand {
let mut flags: Vec<SchemaFlag> = Vec::new();
let mut positionals: Vec<SchemaPositional> = Vec::new();
for arg in sub.get_arguments() {
if arg.get_id() == "help" {
continue;
}
if arg.is_positional() {
positionals.push(SchemaPositional {
name: arg.get_id().to_string(),
required: arg.is_required_set(),
repeating: arg
.get_num_args()
.map(|r| r.max_values() > 1)
.unwrap_or(false),
});
} else {
let name = if let Some(long) = arg.get_long() {
format!("--{}", long)
} else if let Some(short) = arg.get_short() {
format!("-{}", short)
} else {
continue;
};
let (kind, choices) = classify_flag(arg);
flags.push(SchemaFlag {
name,
required: arg.is_required_set(),
kind,
choices,
});
}
}
SchemaSubcommand {
name: sub.get_name().to_string(),
flags,
positionals,
}
}
fn classify_flag(arg: &clap::Arg) -> (&'static str, Option<Vec<String>>) {
match arg.get_action() {
ArgAction::SetTrue | ArgAction::SetFalse | ArgAction::Count => {
return ("boolean", None);
}
_ => {}
}
let possible: Vec<String> = arg
.get_possible_values()
.iter()
.map(|pv| pv.get_name().to_string())
.collect();
if !possible.is_empty() {
return ("dropdown", Some(possible));
}
("text", None)
}