Skip to main content

release_kit/commands/
usage.rs

1//! `rk usage`: the whole command tree in one call.
2//!
3//! Generated from the clap definitions, never hand-maintained, so an
4//! agent loads the surface once instead of walking `--help` per
5//! subcommand and the dump cannot describe a CLI it no longer matches.
6
7use clap::CommandFactory;
8
9use crate::cli::Cli;
10use crate::error::RkError;
11use crate::output::Output;
12
13/// Print every verb, flag, default, and one example each.
14///
15/// # Errors
16///
17/// Never fails; the signature matches the dispatch table.
18pub fn run() -> Result<(), RkError> {
19    let out = Output::human();
20    let root = Cli::command();
21    out.result_line(format!(
22        "rk {} — {}",
23        env!("CARGO_PKG_VERSION"),
24        root.get_about()
25            .map(ToString::to_string)
26            .unwrap_or_default()
27    ));
28    for sub in root.get_subcommands() {
29        describe(out, sub, "rk");
30    }
31    out.next(&[
32        "rk doctor reports whether this host is ready".to_owned(),
33        "rk method --list starts the reading path".to_owned(),
34    ]);
35    Ok(())
36}
37
38/// Print one command's block, then recurse into its subcommands.
39fn describe(out: Output, cmd: &clap::Command, prefix: &str) {
40    let path = format!("{prefix} {}", cmd.get_name());
41    out.result_line(String::new());
42    out.result_line(format!(
43        "{path} — {}",
44        cmd.get_about().map(ToString::to_string).unwrap_or_default()
45    ));
46    let own: Vec<&clap::Arg> = cmd
47        .get_arguments()
48        .filter(|arg| !matches!(arg.get_id().as_str(), "help" | "version"))
49        .collect();
50    // A verb that is itself runnable beside its subcommands, like
51    // `rk stage` or `rk setup`, describes its own flags before them; a
52    // pure group describes nothing but its children.
53    if !cmd.has_subcommands() || !own.is_empty() {
54        out.result_line(format!("  example: {}", example(cmd, &path)));
55        for arg in own {
56            out.result_line(format!("  {}", describe_arg(arg)));
57        }
58    }
59    for sub in cmd.get_subcommands() {
60        describe(out, sub, &path);
61    }
62}
63
64/// One pasteable example: the command path, every required argument with a
65/// placeholder value, and every required either-or group rendered as its
66/// alternatives — so no example invokes a command in a shape the parser
67/// refuses.
68fn example(cmd: &clap::Command, path: &str) -> String {
69    use std::fmt::Write as _;
70    let mut example = path.to_owned();
71    for arg in cmd.get_arguments() {
72        if !arg.is_required_set() {
73            continue;
74        }
75        let value = format!("<{}>", arg.get_id().as_str().to_ascii_uppercase());
76        match arg.get_long() {
77            Some(long) => {
78                let _ = write!(example, " --{long} {value}");
79            }
80            None => {
81                let _ = write!(example, " {value}");
82            }
83        }
84    }
85    for group in cmd.get_groups() {
86        if !group.is_required_set() {
87            continue;
88        }
89        let alternatives: Vec<String> = group
90            .get_args()
91            .filter_map(|id| {
92                let arg = cmd.get_arguments().find(|arg| arg.get_id() == id)?;
93                Some(arg.get_long().map_or_else(
94                    || arg.get_id().as_str().to_ascii_uppercase(),
95                    |long| format!("--{long}"),
96                ))
97            })
98            .collect();
99        if !alternatives.is_empty() {
100            let _ = write!(example, " <{}>", alternatives.join("|"));
101        }
102    }
103    example
104}
105
106/// One argument line: the form, whether it is required, its help, and its
107/// default where one exists.
108fn describe_arg(arg: &clap::Arg) -> String {
109    use std::fmt::Write as _;
110    let takes_value = arg.get_num_args().is_none_or(|num| num.takes_values());
111    let form = match (arg.get_long(), takes_value) {
112        (Some(long), true) => format!("--{long} <{}>", arg.get_id().as_str().to_ascii_uppercase()),
113        (Some(long), false) => format!("--{long}"),
114        (None, _) => format!("[{}]", arg.get_id().as_str().to_ascii_uppercase()),
115    };
116    let mut line = form;
117    if arg.is_required_set() {
118        line.push_str("  (required)");
119    }
120    if let Some(help) = arg.get_help() {
121        let _ = write!(line, "  {help}");
122    }
123    let defaults = arg.get_default_values();
124    if !defaults.is_empty() {
125        let rendered: Vec<String> = defaults
126            .iter()
127            .map(|v| v.to_string_lossy().into_owned())
128            .collect();
129        let _ = write!(line, " (default: {})", rendered.join(", "));
130    }
131    line
132}