Skip to main content

apm/cmd/
instructions.rs

1use anyhow::Result;
2
3const PREAMBLE: &str = "apm \u{2014} Agent Project Manager\n\
4Run `apm <command> --help` for full flag details on any command.\n";
5
6pub fn run(cli_cmd: clap::Command) -> Result<()> {
7    print!("{}", render(cli_cmd));
8    Ok(())
9}
10
11fn render_compact_commands(cli_cmd: &clap::Command) -> String {
12    let mut cmds: Vec<&clap::Command> = cli_cmd
13        .get_subcommands()
14        .filter(|c| !c.is_hide_set())
15        .collect();
16    cmds.sort_by_key(|c| c.get_name());
17
18    // Compute column width: len("apm ") + longest name
19    let max_name = cmds.iter().map(|c| c.get_name().len()).max().unwrap_or(0);
20    let col_width = 4 + max_name; // "apm " prefix
21
22    let mut out = String::new();
23    for cmd in &cmds {
24        let label = format!("apm {}", cmd.get_name());
25        let about = cmd.get_about().map(|a| a.to_string()).unwrap_or_default();
26        out.push_str(&format!("  {:<col_width$}  {}\n", label, about));
27    }
28    out
29}
30
31fn render(cli_cmd: clap::Command) -> String {
32    let mut out = String::from(PREAMBLE);
33    out.push('\n');
34    out.push_str(&render_compact_commands(&cli_cmd));
35    out.push('\n');
36    out
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    fn make_test_cmd() -> clap::Command {
44        clap::Command::new("testapp")
45            .subcommand(
46                clap::Command::new("foo")
47                    .about("Do foo things")
48                    .arg(
49                        clap::Arg::new("verbose")
50                            .long("verbose")
51                            .action(clap::ArgAction::SetTrue),
52                    ),
53            )
54            .subcommand(
55                clap::Command::new("bar")
56                    .about("Do bar things")
57                    .arg(
58                        clap::Arg::new("count")
59                            .long("count")
60                            .value_name("N")
61                            .action(clap::ArgAction::Set),
62                    ),
63            )
64            .subcommand(clap::Command::new("_hook").about("Hidden hook").hide(true))
65    }
66
67    #[test]
68    fn run_returns_ok() {
69        let result = run(make_test_cmd());
70        assert!(result.is_ok());
71    }
72
73    #[test]
74    fn render_includes_preamble() {
75        let out = render(make_test_cmd());
76        assert!(
77            out.contains("apm \u{2014} Agent Project Manager"),
78            "preamble missing in:\n{out}"
79        );
80    }
81
82    #[test]
83    fn render_includes_command_name() {
84        let out = render(make_test_cmd());
85        assert!(out.contains("foo"), "command name 'foo' missing in:\n{out}");
86    }
87
88    #[test]
89    fn render_no_ansi() {
90        let out = render(make_test_cmd());
91        assert!(!out.contains('\x1b'), "ANSI escape code found in:\n{out}");
92    }
93
94    #[test]
95    fn render_compact_has_apm_prefix() {
96        let out = render(make_test_cmd());
97        assert!(out.contains("apm foo"), "apm foo prefix missing in:\n{out}");
98        assert!(out.contains("apm bar"), "apm bar prefix missing in:\n{out}");
99    }
100
101    #[test]
102    fn render_compact_shows_about() {
103        let out = render(make_test_cmd());
104        assert!(out.contains("Do foo things"), "about for foo missing in:\n{out}");
105        assert!(out.contains("Do bar things"), "about for bar missing in:\n{out}");
106    }
107
108    #[test]
109    fn render_compact_no_flags() {
110        let out = render(make_test_cmd());
111        assert!(!out.contains("--verbose"), "flag --verbose found in:\n{out}");
112        assert!(!out.contains("--count"), "flag --count found in:\n{out}");
113    }
114
115    #[test]
116    fn render_compact_excludes_hidden() {
117        let out = render(make_test_cmd());
118        assert!(!out.contains("_hook"), "hidden subcommand _hook found in:\n{out}");
119    }
120}