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 let max_name = cmds.iter().map(|c| c.get_name().len()).max().unwrap_or(0);
20 let col_width = 4 + max_name; 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}