use calcit::cli_args::*;
use colored::Colorize;
macro_rules! echo_items {
($tokens:expr $(,)?) => {};
($tokens:expr, pos $name:literal => $value:expr $(, $($rest:tt)*)?) => {{
push_positional($tokens, $name, $value);
echo_items!($tokens $(, $($rest)*)?);
}};
($tokens:expr, switch $name:literal => $value:expr $(, $($rest:tt)*)?) => {{
push_switch($tokens, $name, $value);
echo_items!($tokens $(, $($rest)*)?);
}};
($tokens:expr, value $name:literal => $value:expr ; default $default:expr $(, $($rest:tt)*)?) => {{
let value = ($value).to_string();
push_value($tokens, $name, &value, Some($default));
echo_items!($tokens $(, $($rest)*)?);
}};
($tokens:expr, value $name:literal => $value:expr $(, $($rest:tt)*)?) => {{
let value = ($value).to_string();
push_value($tokens, $name, &value, None);
echo_items!($tokens $(, $($rest)*)?);
}};
($tokens:expr, opt $name:literal => $value:expr ; default $default:expr $(, $($rest:tt)*)?) => {{
push_optional($tokens, $name, $value, $default);
echo_items!($tokens $(, $($rest)*)?);
}};
($tokens:expr, opt_owned $name:literal => $value:expr ; default $default:expr $(, $($rest:tt)*)?) => {{
push_optional_owned($tokens, $name, $value, $default);
echo_items!($tokens $(, $($rest)*)?);
}};
($tokens:expr, list $name:literal => $value:expr $(, $($rest:tt)*)?) => {{
push_list($tokens, $name, $value);
echo_items!($tokens $(, $($rest)*)?);
}};
($tokens:expr, code_input $opts:expr $(, $($rest:tt)*)?) => {{
let opts = &$opts;
push_code_input(
$tokens,
&CodeInputParts::new(
opts.file.as_deref(),
opts.code.as_deref(),
opts.json.as_deref(),
opts.json_input,
opts.leaf,
),
);
echo_items!($tokens $(, $($rest)*)?);
}};
}
pub fn should_echo_command(cli_args: &ToplevelCalcit) -> bool {
matches!(
cli_args.subcommand,
Some(CalcitCommand::Query(_))
| Some(CalcitCommand::Docs(_))
| Some(CalcitCommand::Libs(_))
| Some(CalcitCommand::Edit(_))
| Some(CalcitCommand::Tree(_))
| Some(CalcitCommand::Analyze(_))
| Some(CalcitCommand::Cirru(_))
)
}
pub fn print_command_echo(cli_args: &ToplevelCalcit) {
let Some(command) = render_command_echo(cli_args) else {
return;
};
eprintln!("{}", format!("Command: {command}").blue().bold());
}
fn render_command_echo(cli_args: &ToplevelCalcit) -> Option<String> {
let subcommand = cli_args.subcommand.as_ref()?;
let mut tokens = vec![match subcommand {
CalcitCommand::Query(cmd) => format!("cr query {}", query_name(&cmd.subcommand)),
CalcitCommand::Docs(cmd) => format!("cr docs {}", docs_name(&cmd.subcommand)),
CalcitCommand::Libs(cmd) => format!("cr libs {}", libs_name(cmd.subcommand.as_ref()?)),
CalcitCommand::Edit(cmd) => format!("cr edit {}", edit_name(&cmd.subcommand)),
CalcitCommand::Tree(cmd) => format!("cr tree {}", tree_name(&cmd.subcommand)),
CalcitCommand::Analyze(cmd) => format!("cr analyze {}", analyze_name(&cmd.subcommand)),
CalcitCommand::Cirru(cmd) => format!("cr cirru {}", cirru_name(&cmd.subcommand)),
_ => return None,
}];
match subcommand {
CalcitCommand::Query(cmd) => push_query(&mut tokens, cmd),
CalcitCommand::Docs(cmd) => push_docs(&mut tokens, cmd),
CalcitCommand::Libs(cmd) => push_libs(&mut tokens, cmd.subcommand.as_ref()?),
CalcitCommand::Edit(cmd) => push_edit(&mut tokens, cmd),
CalcitCommand::Tree(cmd) => push_tree(&mut tokens, cmd),
CalcitCommand::Analyze(cmd) => push_analyze(&mut tokens, cmd),
CalcitCommand::Cirru(cmd) => push_cirru(&mut tokens, cmd),
_ => return None,
}
Some(tokens.join(" "))
}
fn push_query(tokens: &mut Vec<String>, cmd: &QueryCommand) {
match &cmd.subcommand {
QuerySubcommand::Schema(opts) => echo_items!(tokens, pos "target" => &opts.target, switch "json" => opts.json),
QuerySubcommand::Ns(opts) => {
echo_items!(tokens, opt "namespace" => opts.namespace.as_deref(); default "all", switch "deps" => opts.deps)
}
QuerySubcommand::Defs(opts) => echo_items!(tokens, pos "namespace" => &opts.namespace),
QuerySubcommand::Pkg(_) | QuerySubcommand::Config(_) | QuerySubcommand::Error(_) | QuerySubcommand::Modules(_) => {}
QuerySubcommand::Def(opts) => echo_items!(
tokens,
pos "target" => &opts.target,
switch "json" => opts.json,
value "chunk-target-nodes" => opts.chunk_target_nodes; default "56",
value "chunk-max-nodes" => opts.chunk_max_nodes; default "68",
value "chunk-trigger-nodes" => opts.chunk_trigger_nodes; default "88",
switch "raw" => opts.raw
),
QuerySubcommand::Peek(opts) => echo_items!(tokens, pos "target" => &opts.target),
QuerySubcommand::Examples(opts) => echo_items!(tokens, pos "target" => &opts.target),
QuerySubcommand::Find(opts) => echo_items!(
tokens,
pos "symbol" => &opts.symbol,
switch "deps" => opts.deps,
switch "exact" => opts.exact,
value "limit" => opts.limit; default "20",
value "detail-offset" => opts.detail_offset; default "0"
),
QuerySubcommand::Usages(opts) => echo_items!(
tokens,
pos "target" => &opts.target,
switch "deps" => opts.deps,
value "detail-offset" => opts.detail_offset; default "0"
),
QuerySubcommand::Search(opts) => echo_items!(
tokens,
pos "pattern" => &opts.pattern,
opt "filter" => opts.filter.as_deref(); default "none",
switch "exact" => opts.exact,
value "max-depth" => opts.max_depth; default "0",
opt "start-path" => opts.start_path.as_deref(); default "none",
opt "entry" => opts.entry.as_deref(); default "none",
value "detail-offset" => opts.detail_offset; default "0"
),
QuerySubcommand::SearchExpr(opts) => echo_items!(
tokens,
pos "pattern" => &opts.pattern,
opt "filter" => opts.filter.as_deref(); default "none",
switch "exact" => opts.exact,
value "max-depth" => opts.max_depth; default "0",
switch "json" => opts.json,
opt "entry" => opts.entry.as_deref(); default "none",
value "detail-offset" => opts.detail_offset; default "0"
),
}
}
fn push_docs(tokens: &mut Vec<String>, cmd: &DocsCommand) {
match &cmd.subcommand {
DocsSubcommand::Search(opts) => echo_items!(
tokens,
pos "keyword" => &opts.keyword,
value "context" => opts.context; default "5",
opt "filename" => opts.filename.as_deref(); default "none",
opt "scope" => opts.scope.as_deref(); default "none",
opt "module" => opts.module.as_deref(); default "none"
),
DocsSubcommand::Read(opts) => echo_items!(
tokens,
pos "filename" => &opts.filename,
list "heading" => &opts.headings,
switch "no-subheadings" => opts.no_subheadings,
switch "full" => opts.full,
switch "with-lines" => opts.with_lines,
opt "scope" => opts.scope.as_deref(); default "none",
opt "module" => opts.module.as_deref(); default "none"
),
DocsSubcommand::Agents(opts) => echo_items!(
tokens,
list "heading" => &opts.headings,
switch "no-subheadings" => opts.no_subheadings,
switch "full" => opts.full,
switch "with-lines" => opts.with_lines,
switch "refresh" => opts.refresh
),
DocsSubcommand::ReadLines(opts) => echo_items!(
tokens,
pos "filename" => &opts.filename,
value "start" => opts.start; default "0",
value "lines" => opts.lines; default "80",
opt "scope" => opts.scope.as_deref(); default "none",
opt "module" => opts.module.as_deref(); default "none"
),
DocsSubcommand::List(_) => {}
DocsSubcommand::CheckMd(opts) => {
echo_items!(tokens, pos "file" => &opts.file, value "entry" => &opts.entry; default "demos/compact.cirru", list "dep" => &opts.dep)
}
}
}
fn push_libs(tokens: &mut Vec<String>, subcommand: &LibsSubcommand) {
match subcommand {
LibsSubcommand::Readme(opts) => echo_items!(
tokens,
pos "package" => &opts.package,
list "heading" => &opts.headings,
opt "file" => opts.file.as_deref(); default "none",
switch "no-subheadings" => opts.no_subheadings,
switch "full" => opts.full,
switch "with-lines" => opts.with_lines
),
LibsSubcommand::Search(opts) => echo_items!(tokens, pos "keyword" => &opts.keyword),
LibsSubcommand::ScanMd(opts) => echo_items!(tokens, pos "module" => &opts.module),
}
}
fn push_cirru(tokens: &mut Vec<String>, cmd: &CirruCommand) {
match &cmd.subcommand {
CirruSubcommand::Parse(opts) => {
echo_items!(tokens, pos "code" => &opts.code, switch "expr-one" => opts.expr_one_liner, switch "validate" => opts.validate)
}
CirruSubcommand::Format(opts) => echo_items!(tokens, pos "json" => &opts.json),
CirruSubcommand::ParseEdn(opts) => echo_items!(tokens, pos "edn" => &opts.edn),
CirruSubcommand::ShowGuide(_) => {}
}
}
fn push_analyze(tokens: &mut Vec<String>, cmd: &AnalyzeCommand) {
match &cmd.subcommand {
AnalyzeSubcommand::CallGraph(opts) => echo_items!(
tokens,
opt "root" => opts.root.as_deref(); default "config.init-fn",
opt "ns-prefix" => opts.ns_prefix.as_deref(); default "none",
switch "include-core" => opts.include_core,
value "max-depth" => opts.max_depth; default "0",
switch "show-unused" => opts.show_unused,
value "format" => &opts.format; default "text"
),
AnalyzeSubcommand::CountCalls(opts) => echo_items!(
tokens,
opt "root" => opts.root.as_deref(); default "config.init-fn",
opt "ns-prefix" => opts.ns_prefix.as_deref(); default "none",
switch "include-core" => opts.include_core,
value "format" => &opts.format; default "text",
value "sort" => &opts.sort; default "count"
),
AnalyzeSubcommand::CheckExamples(opts) => echo_items!(tokens, value "ns" => &opts.ns),
AnalyzeSubcommand::CheckTypes(opts) => echo_items!(
tokens,
opt "ns" => opts.ns.as_deref(); default "none",
opt "ns-prefix" => opts.ns_prefix.as_deref(); default "none",
opt "only" => opts.only.as_deref(); default "all",
switch "deps" => opts.deps
),
AnalyzeSubcommand::JsEscape(opts) => echo_items!(tokens, pos "symbol" => &opts.symbol),
AnalyzeSubcommand::JsUnescape(opts) => echo_items!(tokens, pos "symbol" => &opts.symbol),
}
}
fn push_edit(tokens: &mut Vec<String>, cmd: &EditCommand) {
match &cmd.subcommand {
EditSubcommand::Format(_) => {}
EditSubcommand::Def(opts) => {
echo_items!(tokens, pos "target" => &opts.target, code_input opts, switch "overwrite" => opts.overwrite)
}
EditSubcommand::MvDef(opts) => echo_items!(tokens, pos "source" => &opts.source, pos "target" => &opts.target),
EditSubcommand::RmDef(opts) => echo_items!(tokens, pos "target" => &opts.target),
EditSubcommand::Doc(opts) => echo_items!(tokens, pos "target" => &opts.target, pos "doc" => &opts.doc),
EditSubcommand::Schema(opts) => echo_items!(tokens, pos "target" => &opts.target, code_input opts, switch "clear" => opts.clear),
EditSubcommand::Examples(opts) => echo_items!(tokens, pos "target" => &opts.target, code_input opts, switch "clear" => opts.clear),
EditSubcommand::AddExample(opts) => {
echo_items!(tokens, pos "target" => &opts.target, opt_owned "at" => opts.at.map(|v| v.to_string()); default "append", code_input opts)
}
EditSubcommand::RmExample(opts) => echo_items!(tokens, pos "target" => &opts.target, pos "index" => &opts.index.to_string()),
EditSubcommand::AddNs(opts) => echo_items!(tokens, pos "namespace" => &opts.namespace, code_input opts),
EditSubcommand::RmNs(opts) => echo_items!(tokens, pos "namespace" => &opts.namespace),
EditSubcommand::Imports(opts) => echo_items!(tokens, pos "namespace" => &opts.namespace, code_input opts),
EditSubcommand::AddImport(opts) => {
echo_items!(tokens, pos "namespace" => &opts.namespace, code_input opts, switch "overwrite" => opts.overwrite)
}
EditSubcommand::RmImport(opts) => echo_items!(tokens, pos "namespace" => &opts.namespace, pos "source-ns" => &opts.source_ns),
EditSubcommand::NsDoc(opts) => echo_items!(tokens, pos "namespace" => &opts.namespace, pos "doc" => &opts.doc),
EditSubcommand::AddModule(opts) => echo_items!(tokens, pos "module-path" => &opts.module_path),
EditSubcommand::RmModule(opts) => echo_items!(tokens, pos "module-path" => &opts.module_path),
EditSubcommand::Config(opts) => echo_items!(tokens, pos "key" => &opts.key, pos "value" => &opts.value),
EditSubcommand::Inc(opts) => {
echo_items!(tokens, list "added-ns" => &opts.added_ns, list "removed-ns" => &opts.removed_ns, list "ns-updated" => &opts.ns_updated, list "added" => &opts.added, list "removed" => &opts.removed, list "changed" => &opts.changed)
}
EditSubcommand::Cp(opts) => {
echo_items!(tokens, pos "target" => &opts.target, value "from" => &opts.from, value "path" => &opts.path, value "at" => &opts.at; default "after")
}
EditSubcommand::Mv(opts) => {
echo_items!(tokens, pos "target" => &opts.target, value "from" => &opts.from, value "path" => &opts.path, value "at" => &opts.at; default "after")
}
EditSubcommand::Rename(opts) => echo_items!(tokens, pos "source" => &opts.source, pos "new-name" => &opts.new_name),
EditSubcommand::SplitDef(opts) => {
echo_items!(tokens, pos "target" => &opts.target, value "path" => &opts.path, value "name" => &opts.new_name)
}
}
}
fn push_tree(tokens: &mut Vec<String>, cmd: &TreeCommand) {
match &cmd.subcommand {
TreeSubcommand::Show(opts) => echo_items!(
tokens,
pos "target" => &opts.target,
value "path" => &opts.path,
value "depth" => opts.depth; default "2",
switch "json" => opts.json,
value "chunk-target-nodes" => opts.chunk_target_nodes; default "56",
value "chunk-max-nodes" => opts.chunk_max_nodes; default "68",
value "chunk-trigger-nodes" => opts.chunk_trigger_nodes; default "88",
value "chunk-expand-depth" => opts.chunk_expand_depth; default "1",
switch "raw" => opts.raw
),
TreeSubcommand::Replace(opts) => {
echo_items!(tokens, pos "target" => &opts.target, value "path" => &opts.path, code_input opts, value "depth" => opts.depth; default "2")
}
TreeSubcommand::ReplaceLeaf(opts) => {
echo_items!(tokens, pos "target" => &opts.target, value "pattern" => &opts.pattern, code_input opts, value "depth" => opts.depth; default "2")
}
TreeSubcommand::Delete(opts) => {
echo_items!(tokens, pos "target" => &opts.target, value "path" => &opts.path, value "depth" => opts.depth; default "2")
}
TreeSubcommand::InsertBefore(opts) => push_tree_insert(
tokens,
&opts.target,
&opts.path,
CodeInputParts::new(
opts.file.as_deref(),
opts.code.as_deref(),
opts.json.as_deref(),
opts.json_input,
opts.leaf,
),
opts.depth,
),
TreeSubcommand::InsertAfter(opts) => push_tree_insert(
tokens,
&opts.target,
&opts.path,
CodeInputParts::new(
opts.file.as_deref(),
opts.code.as_deref(),
opts.json.as_deref(),
opts.json_input,
opts.leaf,
),
opts.depth,
),
TreeSubcommand::InsertChild(opts) => push_tree_insert(
tokens,
&opts.target,
&opts.path,
CodeInputParts::new(
opts.file.as_deref(),
opts.code.as_deref(),
opts.json.as_deref(),
opts.json_input,
opts.leaf,
),
opts.depth,
),
TreeSubcommand::AppendChild(opts) => push_tree_insert(
tokens,
&opts.target,
&opts.path,
CodeInputParts::new(
opts.file.as_deref(),
opts.code.as_deref(),
opts.json.as_deref(),
opts.json_input,
opts.leaf,
),
opts.depth,
),
TreeSubcommand::SwapNext(opts) => push_tree_path_depth(tokens, &opts.target, &opts.path, opts.depth),
TreeSubcommand::SwapPrev(opts) => push_tree_path_depth(tokens, &opts.target, &opts.path, opts.depth),
TreeSubcommand::Unwrap(opts) => push_tree_path_depth(tokens, &opts.target, &opts.path, opts.depth),
TreeSubcommand::Raise(opts) => push_tree_path_depth(tokens, &opts.target, &opts.path, opts.depth),
TreeSubcommand::Wrap(opts) => {
push_tree_path_depth(tokens, &opts.target, &opts.path, opts.depth);
echo_items!(tokens, code_input opts);
}
TreeSubcommand::TargetReplace(opts) => {
echo_items!(tokens, pos "target" => &opts.target, value "pattern" => &opts.pattern, code_input opts, value "depth" => opts.depth; default "2")
}
TreeSubcommand::Rewrite(opts) => {
push_tree_path_depth(tokens, &opts.target, &opts.path, opts.depth);
echo_items!(tokens, code_input opts, list "with" => &opts.with);
}
}
}
struct CodeInputParts<'a> {
file: Option<&'a str>,
code: Option<&'a str>,
json: Option<&'a str>,
json_input: bool,
leaf: bool,
}
impl<'a> CodeInputParts<'a> {
fn new(file: Option<&'a str>, code: Option<&'a str>, json: Option<&'a str>, json_input: bool, leaf: bool) -> Self {
Self {
file,
code,
json,
json_input,
leaf,
}
}
}
fn push_tree_insert(tokens: &mut Vec<String>, target: &str, path: &str, code_input: CodeInputParts<'_>, depth: usize) {
push_tree_path_depth(tokens, target, path, depth);
push_code_input(tokens, &code_input);
}
fn push_tree_path_depth(tokens: &mut Vec<String>, target: &str, path: &str, depth: usize) {
push_positional(tokens, "target", target);
push_value(tokens, "path", path, None);
push_value(tokens, "depth", &depth.to_string(), Some("2"));
}
fn push_code_input(tokens: &mut Vec<String>, code_input: &CodeInputParts<'_>) {
push_optional(tokens, "file", code_input.file, "none");
push_optional(tokens, "code", code_input.code, "none");
push_optional(tokens, "json", code_input.json, "none");
push_switch(tokens, "json-input", code_input.json_input);
push_switch(tokens, "leaf", code_input.leaf);
}
fn push_switch(tokens: &mut Vec<String>, name: &str, enabled: bool) {
tokens.push(format!("--{name}={}", if enabled { "ON" } else { "OFF" }));
}
fn push_value(tokens: &mut Vec<String>, name: &str, value: &str, default: Option<&str>) {
let display_value = format_atom(value);
match default {
Some(default_value) if default_value == value => {
tokens.push(format!("--{name}=({})", format_atom(default_value)));
}
_ => tokens.push(format!("--{name}={display_value}")),
}
}
fn push_optional(tokens: &mut Vec<String>, name: &str, value: Option<&str>, default_label: &str) {
match value {
Some(value) => tokens.push(format!("--{name}={}", format_atom(value))),
None => tokens.push(format!("--{name}=({})", format_atom(default_label))),
}
}
fn push_optional_owned(tokens: &mut Vec<String>, name: &str, value: Option<String>, default_label: &str) {
match value {
Some(value) => tokens.push(format!("--{name}={}", format_atom(&value))),
None => tokens.push(format!("--{name}=({})", format_atom(default_label))),
}
}
fn push_list(tokens: &mut Vec<String>, name: &str, values: &[String]) {
if values.is_empty() {
tokens.push(format!("--{name}=(none)"));
} else {
for value in values {
tokens.push(format!("--{name}={}", format_atom(value)));
}
}
}
fn push_positional(tokens: &mut Vec<String>, name: &str, value: &str) {
tokens.push(format!("{name}={}", format_atom(value)));
}
fn format_atom(value: &str) -> String {
if value.is_empty() {
return String::from("''");
}
if value.chars().any(char::is_whitespace) {
return format!("{value:?}");
}
value.to_owned()
}
fn query_name(subcommand: &QuerySubcommand) -> &'static str {
match subcommand {
QuerySubcommand::Ns(_) => "ns",
QuerySubcommand::Defs(_) => "defs",
QuerySubcommand::Pkg(_) => "pkg",
QuerySubcommand::Config(_) => "config",
QuerySubcommand::Error(_) => "error",
QuerySubcommand::Modules(_) => "modules",
QuerySubcommand::Def(_) => "def",
QuerySubcommand::Peek(_) => "peek",
QuerySubcommand::Examples(_) => "examples",
QuerySubcommand::Find(_) => "find",
QuerySubcommand::Usages(_) => "usages",
QuerySubcommand::Search(_) => "search",
QuerySubcommand::SearchExpr(_) => "search-expr",
QuerySubcommand::Schema(_) => "schema",
}
}
fn docs_name(subcommand: &DocsSubcommand) -> &'static str {
match subcommand {
DocsSubcommand::Search(_) => "search",
DocsSubcommand::Read(_) => "read",
DocsSubcommand::Agents(_) => "agents",
DocsSubcommand::ReadLines(_) => "read-lines",
DocsSubcommand::List(_) => "list",
DocsSubcommand::CheckMd(_) => "check-md",
}
}
fn libs_name(subcommand: &LibsSubcommand) -> &'static str {
match subcommand {
LibsSubcommand::Readme(_) => "readme",
LibsSubcommand::Search(_) => "search",
LibsSubcommand::ScanMd(_) => "scan-md",
}
}
fn cirru_name(subcommand: &CirruSubcommand) -> &'static str {
match subcommand {
CirruSubcommand::Parse(_) => "parse",
CirruSubcommand::Format(_) => "format",
CirruSubcommand::ParseEdn(_) => "parse-edn",
CirruSubcommand::ShowGuide(_) => "show-guide",
}
}
fn analyze_name(subcommand: &AnalyzeSubcommand) -> &'static str {
match subcommand {
AnalyzeSubcommand::CallGraph(_) => "call-graph",
AnalyzeSubcommand::CountCalls(_) => "count-calls",
AnalyzeSubcommand::CheckExamples(_) => "check-examples",
AnalyzeSubcommand::CheckTypes(_) => "check-types",
AnalyzeSubcommand::JsEscape(_) => "js-escape",
AnalyzeSubcommand::JsUnescape(_) => "js-unescape",
}
}
fn edit_name(subcommand: &EditSubcommand) -> &'static str {
match subcommand {
EditSubcommand::Format(_) => "format",
EditSubcommand::Def(_) => "def",
EditSubcommand::MvDef(_) => "mv-def",
EditSubcommand::RmDef(_) => "rm-def",
EditSubcommand::Doc(_) => "doc",
EditSubcommand::Schema(_) => "schema",
EditSubcommand::Examples(_) => "examples",
EditSubcommand::AddExample(_) => "add-example",
EditSubcommand::RmExample(_) => "rm-example",
EditSubcommand::AddNs(_) => "add-ns",
EditSubcommand::RmNs(_) => "rm-ns",
EditSubcommand::Imports(_) => "imports",
EditSubcommand::AddImport(_) => "add-import",
EditSubcommand::RmImport(_) => "rm-import",
EditSubcommand::NsDoc(_) => "ns-doc",
EditSubcommand::AddModule(_) => "add-module",
EditSubcommand::RmModule(_) => "rm-module",
EditSubcommand::Config(_) => "config",
EditSubcommand::Inc(_) => "inc",
EditSubcommand::Cp(_) => "cp",
EditSubcommand::Mv(_) => "mv",
EditSubcommand::Rename(_) => "rename",
EditSubcommand::SplitDef(_) => "split-def",
}
}
fn tree_name(subcommand: &TreeSubcommand) -> &'static str {
match subcommand {
TreeSubcommand::Show(_) => "show",
TreeSubcommand::Replace(_) => "replace",
TreeSubcommand::ReplaceLeaf(_) => "replace-leaf",
TreeSubcommand::Delete(_) => "delete",
TreeSubcommand::InsertBefore(_) => "insert-before",
TreeSubcommand::InsertAfter(_) => "insert-after",
TreeSubcommand::InsertChild(_) => "insert-child",
TreeSubcommand::AppendChild(_) => "append-child",
TreeSubcommand::SwapNext(_) => "swap-next",
TreeSubcommand::SwapPrev(_) => "swap-prev",
TreeSubcommand::Unwrap(_) => "unwrap",
TreeSubcommand::Raise(_) => "raise",
TreeSubcommand::Wrap(_) => "wrap",
TreeSubcommand::TargetReplace(_) => "target-replace",
TreeSubcommand::Rewrite(_) => "rewrite",
}
}