1use anyhow::Result;
4use clap::{builder::StyledStr, Command, CommandFactory, Parser};
5
6#[derive(Parser)]
8pub struct HelpCommand {
9 }
11
12pub struct HelpGenerator {
14 app: Command,
15}
16
17impl HelpGenerator {
18 pub fn new() -> Self {
20 use crate::cli::Cli;
21
22 let app = Cli::command();
24
25 Self { app }
26 }
27}
28
29impl Default for HelpGenerator {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl HelpGenerator {
36 pub fn generate_all_help(&self) -> Result<String> {
38 let mut help_sections = Vec::new();
39
40 let main_help = self.render_command_help(&self.app, "");
42 help_sections.push(main_help);
43
44 self.collect_help_recursive(&self.app, "", &mut help_sections);
46
47 let separator = format!("\n\n{}\n\n", "=".repeat(80));
49 Ok(help_sections.join(&separator))
50 }
51
52 fn collect_help_recursive(&self, cmd: &Command, prefix: &str, help_sections: &mut Vec<String>) {
62 let mut subcommands: Vec<_> = cmd.get_subcommands().collect();
64 subcommands.sort_by(|a, b| a.get_name().cmp(b.get_name()));
65
66 for subcmd in subcommands {
67 if subcmd.get_name() == "help" {
69 continue;
70 }
71
72 let current_path = if prefix.is_empty() {
73 subcmd.get_name().to_string()
74 } else {
75 format!("{} {}", prefix, subcmd.get_name())
76 };
77
78 let subcmd_help = self.render_command_help(subcmd, ¤t_path);
80 help_sections.push(subcmd_help);
81
82 self.collect_help_recursive(subcmd, ¤t_path, help_sections);
84 }
85 }
86
87 fn render_command_help(&self, cmd: &Command, path: &str) -> String {
89 let mut output = String::new();
90
91 let cmd_name = if path.is_empty() {
93 cmd.get_name().to_string()
94 } else {
95 format!("omni-dev {path}")
96 };
97
98 let about = cmd.get_about().map_or_else(
99 || "No description available".to_string(),
100 |s| self.styled_str_to_string(s),
101 );
102
103 output.push_str(&format!("{cmd_name} - {about}\n\n"));
104
105 let help_str = cmd.clone().render_help();
107 output.push_str(&help_str.to_string());
108
109 output
110 }
111
112 fn styled_str_to_string(&self, styled: &StyledStr) -> String {
114 styled.to_string()
115 }
116}
117
118impl HelpCommand {
119 pub fn execute(self) -> Result<()> {
121 let generator = HelpGenerator::new();
122 let help_output = generator.generate_all_help()?;
123 println!("{help_output}");
124 Ok(())
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::unwrap_used, clippy::expect_used)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn help_generator_default() {
135 let gen = HelpGenerator::default();
136 assert_eq!(gen.app.get_name(), "omni-dev");
137 }
138
139 #[test]
140 fn generate_all_help_contains_all_top_level_commands() {
141 let gen = HelpGenerator::new();
142 let output = gen.generate_all_help().unwrap();
143 assert!(output.contains("omni-dev ai"));
144 assert!(output.contains("omni-dev git"));
145 assert!(output.contains("omni-dev commands"));
146 assert!(output.contains("omni-dev config"));
147 assert!(output.contains("omni-dev help-all"));
148 }
149
150 #[test]
151 fn generate_all_help_contains_nested_commands() {
152 let gen = HelpGenerator::new();
153 let output = gen.generate_all_help().unwrap();
154 assert!(output.contains("omni-dev git commit message view"));
156 assert!(output.contains("omni-dev git commit message amend"));
157 assert!(output.contains("omni-dev git commit message twiddle"));
158 assert!(output.contains("omni-dev git commit message check"));
159 assert!(output.contains("omni-dev git branch info"));
160 assert!(output.contains("omni-dev git branch create pr"));
161 }
162
163 #[test]
164 fn generate_all_help_uses_section_separators() {
165 let gen = HelpGenerator::new();
166 let output = gen.generate_all_help().unwrap();
167 let separator = "=".repeat(80);
168 assert!(output.contains(&separator));
169 }
170
171 #[test]
172 fn generate_all_help_is_deterministic() {
173 let gen1 = HelpGenerator::new();
174 let gen2 = HelpGenerator::new();
175 let output1 = gen1.generate_all_help().unwrap();
176 let output2 = gen2.generate_all_help().unwrap();
177 assert_eq!(output1, output2, "Help output should be deterministic");
178 }
179
180 #[test]
181 fn render_command_help_includes_about() {
182 let gen = HelpGenerator::new();
183 let help = gen.render_command_help(&gen.app, "");
184 assert!(help.contains("comprehensive development toolkit"));
186 }
187
188 #[test]
189 fn styled_str_to_string_plain_text() {
190 let gen = HelpGenerator::new();
191 let styled = StyledStr::from("hello world");
192 let result = gen.styled_str_to_string(&styled);
193 assert_eq!(result, "hello world");
194 }
195}