1use std::collections::BTreeMap;
2
3use crate::output::NextAction;
4
5pub const ROOT_HELP_TEMPLATE: &str = "\
10{before-help}{about-with-newline}
11{usage-heading} {usage}{after-help}";
12
13pub const GROUP_HELP_TEMPLATE: &str = "\
17{before-help}{about-with-newline}
18{usage-heading} {usage}
19
20Commands:
21{subcommands}{after-help}";
22
23#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct ModuleHelpEntry {
26 pub category: String,
28 pub name: String,
30 pub short: String,
32}
33
34#[must_use]
36pub fn build_root_long(intro: &str, entries: &[ModuleHelpEntry], has_guide: bool) -> String {
37 let mut by_category = BTreeMap::<&str, Vec<&ModuleHelpEntry>>::new();
40 for entry in entries {
41 by_category
42 .entry(entry.category.as_str())
43 .or_default()
44 .push(entry);
45 }
46
47 let max_width = entries
48 .iter()
49 .map(|entry| entry.name.len())
50 .max()
51 .unwrap_or_default();
52 let mut out = intro.to_owned();
53 for (category, category_entries) in &mut by_category {
54 category_entries.sort_by(|left, right| left.name.cmp(&right.name));
55 out.push_str(&format!("\n\n {category}:"));
56 for entry in category_entries {
57 out.push_str(&format!(
58 "\n {:<width$} {}",
59 entry.name,
60 entry.short,
61 width = max_width
62 ));
63 }
64 }
65 out.push_str("\n\n Find Commands:");
66 let mut find_commands: Vec<(&str, &str)> = vec![
67 (
68 "search <keyword>",
69 "Search all commands and guides by keyword",
70 ),
71 ("tree", "Display full command tree"),
72 ("completion", "Generate or install shell completion scripts"),
73 ];
74 if has_guide {
75 find_commands.push(("guide", "Built-in guides for AI agents and developers"));
76 }
77 let find_commands_width = find_commands
78 .iter()
79 .map(|(label, _)| label.len())
80 .max()
81 .unwrap_or_default();
82 for (label, description) in find_commands {
83 out.push_str(&format!(
84 "\n {:<find_commands_width$} {description}",
85 label
86 ));
87 }
88 out
89}
90
91#[must_use]
94pub fn render_next_actions_human(actions: &[NextAction]) -> String {
95 if actions.is_empty() {
96 return String::new();
97 }
98 let max_width = actions
99 .iter()
100 .map(|action| action.command.len())
101 .max()
102 .unwrap_or_default();
103 let mut out = String::from("\n\n Suggested next actions:");
104 for action in actions {
105 out.push_str(&format!(
106 "\n {:<width$} {}",
107 action.command,
108 action.description,
109 width = max_width
110 ));
111 }
112 out
113}