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 out.push_str("\n --search <keyword> Search all commands and guides by keyword");
67 out.push_str("\n tree Display full command tree");
68 if has_guide {
69 out.push_str("\n guide Built-in guides for AI agents and developers");
70 }
71 out
72}
73
74#[must_use]
77pub fn render_next_actions_human(actions: &[NextAction]) -> String {
78 if actions.is_empty() {
79 return String::new();
80 }
81 let max_width = actions
82 .iter()
83 .map(|action| action.command.len())
84 .max()
85 .unwrap_or_default();
86 let mut out = String::from("\n\n Suggested next actions:");
87 for action in actions {
88 out.push_str(&format!(
89 "\n {:<width$} {}",
90 action.command,
91 action.description,
92 width = max_width
93 ));
94 }
95 out
96}