use anyhow::Result;
use clap::{builder::StyledStr, Command, CommandFactory, Parser};
#[derive(Parser)]
pub struct HelpCommand {
}
pub struct HelpGenerator {
app: Command,
}
impl HelpGenerator {
pub fn new() -> Self {
use crate::cli::Cli;
let app = Cli::command();
Self { app }
}
}
impl Default for HelpGenerator {
fn default() -> Self {
Self::new()
}
}
impl HelpGenerator {
pub fn generate_all_help(&self) -> Result<String> {
let mut help_sections = Vec::new();
let main_help = self.render_command_help(&self.app, "");
help_sections.push(main_help);
self.collect_help_recursive(&self.app, "", &mut help_sections);
let separator = format!("\n\n{}\n\n", "=".repeat(80));
Ok(help_sections.join(&separator))
}
fn collect_help_recursive(&self, cmd: &Command, prefix: &str, help_sections: &mut Vec<String>) {
let mut subcommands: Vec<_> = cmd.get_subcommands().collect();
subcommands.sort_by(|a, b| a.get_name().cmp(b.get_name()));
for subcmd in subcommands {
if subcmd.get_name() == "help" {
continue;
}
let current_path = if prefix.is_empty() {
subcmd.get_name().to_string()
} else {
format!("{} {}", prefix, subcmd.get_name())
};
let subcmd_help = self.render_command_help(subcmd, ¤t_path);
help_sections.push(subcmd_help);
self.collect_help_recursive(subcmd, ¤t_path, help_sections);
}
}
fn render_command_help(&self, cmd: &Command, path: &str) -> String {
let mut output = String::new();
let cmd_name = if path.is_empty() {
cmd.get_name().to_string()
} else {
format!("omni-dev {path}")
};
let about = cmd.get_about().map_or_else(
|| "No description available".to_string(),
|s| self.styled_str_to_string(s),
);
output.push_str(&format!("{cmd_name} - {about}\n\n"));
let help_str = cmd.clone().render_help();
output.push_str(&help_str.to_string());
output
}
fn styled_str_to_string(&self, styled: &StyledStr) -> String {
styled.to_string()
}
}
impl HelpCommand {
pub fn execute(self) -> Result<()> {
let generator = HelpGenerator::new();
let help_output = generator.generate_all_help()?;
println!("{help_output}");
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn help_generator_default() {
let gen = HelpGenerator::default();
assert_eq!(gen.app.get_name(), "omni-dev");
}
#[test]
fn generate_all_help_contains_all_top_level_commands() {
let gen = HelpGenerator::new();
let output = gen.generate_all_help().unwrap();
assert!(output.contains("omni-dev ai"));
assert!(output.contains("omni-dev git"));
assert!(output.contains("omni-dev commands"));
assert!(output.contains("omni-dev config"));
assert!(output.contains("omni-dev help-all"));
}
#[test]
fn generate_all_help_contains_nested_commands() {
let gen = HelpGenerator::new();
let output = gen.generate_all_help().unwrap();
assert!(output.contains("omni-dev git commit message view"));
assert!(output.contains("omni-dev git commit message amend"));
assert!(output.contains("omni-dev git commit message twiddle"));
assert!(output.contains("omni-dev git commit message check"));
assert!(output.contains("omni-dev git branch info"));
assert!(output.contains("omni-dev git branch create pr"));
}
#[test]
fn generate_all_help_uses_section_separators() {
let gen = HelpGenerator::new();
let output = gen.generate_all_help().unwrap();
let separator = "=".repeat(80);
assert!(output.contains(&separator));
}
#[test]
fn generate_all_help_is_deterministic() {
let gen1 = HelpGenerator::new();
let gen2 = HelpGenerator::new();
let output1 = gen1.generate_all_help().unwrap();
let output2 = gen2.generate_all_help().unwrap();
assert_eq!(output1, output2, "Help output should be deterministic");
}
#[test]
fn render_command_help_includes_about() {
let gen = HelpGenerator::new();
let help = gen.render_command_help(&gen.app, "");
assert!(help.contains("comprehensive development toolkit"));
}
#[test]
fn styled_str_to_string_plain_text() {
let gen = HelpGenerator::new();
let styled = StyledStr::from("hello world");
let result = gen.styled_str_to_string(&styled);
assert_eq!(result, "hello world");
}
}