use std::io::Write;
use anyhow::Result;
use clap::{Args, CommandFactory};
use schemars::JsonSchema;
use serde::Serialize;
use crate::cli::{Cli, GlobalArgs};
use crate::output::NdjsonWriter;
use crate::signal::ShutdownSignal;
#[derive(Args, Debug, Default)]
pub struct CommandsTreeArgs {
#[arg(
long,
action = clap::ArgAction::SetTrue,
help = "Include global flags under the root node"
)]
pub include_globals: bool,
}
#[derive(Serialize, JsonSchema)]
struct CommandsTreeReport {
r#type: &'static str,
version: String,
name: String,
about: String,
commands: Vec<CommandNode>,
global_args: Vec<ArgNode>,
}
#[derive(Serialize, JsonSchema)]
struct CommandNode {
name: String,
about: String,
aliases: Vec<String>,
args: Vec<ArgNode>,
subcommands: Vec<CommandNode>,
}
#[derive(Serialize, JsonSchema)]
struct ArgNode {
name: String,
long: Option<String>,
short: Option<String>,
required: bool,
takes_value: bool,
help: String,
}
#[tracing::instrument(skip_all, fields(command = "commands-tree"))]
pub fn cmd_commands_tree(
args: &CommandsTreeArgs,
_global: &GlobalArgs,
writer: &mut NdjsonWriter<impl Write>,
_shutdown: &ShutdownSignal,
_defaults: &crate::config::DefaultsSection,
) -> Result<()> {
let cmd = Cli::command();
let about = cmd
.get_about()
.map(|s| s.to_string())
.unwrap_or_default();
let mut commands: Vec<CommandNode> = cmd
.get_subcommands()
.map(command_node)
.collect();
commands.sort_by(|a, b| a.name.cmp(&b.name));
let global_args = if args.include_globals {
arg_nodes(&cmd)
} else {
Vec::new()
};
writer.write_event(&CommandsTreeReport {
r#type: "commands_tree",
version: env!("CARGO_PKG_VERSION").into(),
name: cmd.get_name().to_string(),
about,
commands,
global_args,
})?;
Ok(())
}
fn command_node(cmd: &clap::Command) -> CommandNode {
let mut subcommands: Vec<CommandNode> = cmd.get_subcommands().map(command_node).collect();
subcommands.sort_by(|a, b| a.name.cmp(&b.name));
CommandNode {
name: cmd.get_name().to_string(),
about: cmd
.get_about()
.map(|s| s.to_string())
.unwrap_or_default(),
aliases: cmd.get_all_aliases().map(|s| s.to_string()).collect(),
args: arg_nodes(cmd),
subcommands,
}
}
fn arg_nodes(cmd: &clap::Command) -> Vec<ArgNode> {
cmd.get_arguments()
.filter(|a| !a.is_hide_set())
.map(|a| ArgNode {
name: a.get_id().to_string(),
long: a.get_long().map(|s| s.to_string()),
short: a.get_short().map(|c| c.to_string()),
required: a.is_required_set(),
takes_value: !matches!(
a.get_action(),
clap::ArgAction::SetTrue
| clap::ArgAction::SetFalse
| clap::ArgAction::Count
| clap::ArgAction::Help
| clap::ArgAction::HelpLong
| clap::ArgAction::HelpShort
| clap::ArgAction::Version
),
help: a
.get_help()
.map(|s| s.to_string())
.unwrap_or_default(),
})
.collect()
}