1use crate::tools::ToolSchema;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum HelpTopic {
11 Overview,
13 Syntax,
15 Builtins,
17 Vfs,
19 Scatter,
21 Ignore,
23 OutputLimit,
25 Limits,
27 Tool(String),
29}
30
31impl HelpTopic {
32 pub fn parse_topic(s: &str) -> Self {
37 match s.to_lowercase().as_str() {
38 "" | "overview" | "help" => Self::Overview,
39 "syntax" | "language" | "lang" => Self::Syntax,
40 "builtins" | "tools" | "commands" => Self::Builtins,
41 "vfs" | "filesystem" | "fs" | "paths" => Self::Vfs,
42 "scatter" | "gather" | "parallel" | "散" | "集" => Self::Scatter,
43 "ignore" | "gitignore" | "kaish-ignore" => Self::Ignore,
44 "output-limit" | "spill" | "truncate" | "kaish-output-limit" => Self::OutputLimit,
45 "limits" | "limitations" | "missing" => Self::Limits,
46 other => Self::Tool(other.to_string()),
47 }
48 }
49
50 pub fn description(&self) -> &'static str {
52 match self {
53 Self::Overview => "What kaish is, list of topics",
54 Self::Syntax => "Variables, quoting, pipes, control flow",
55 Self::Builtins => "List of available builtins",
56 Self::Vfs => "Virtual filesystem mounts and paths",
57 Self::Scatter => "Parallel processing (散/集)",
58 Self::Ignore => "Ignore file configuration",
59 Self::OutputLimit => "Output size limit configuration",
60 Self::Limits => "Known limitations",
61 Self::Tool(_) => "Help for a specific tool",
62 }
63 }
64}
65
66const OVERVIEW: &str = include_str!("../docs/help/overview.md");
69const SYNTAX: &str = include_str!("../docs/help/syntax.md");
70const VFS: &str = include_str!("../docs/help/vfs.md");
71const SCATTER: &str = include_str!("../docs/help/scatter.md");
72const IGNORE: &str = include_str!("../docs/help/ignore.md");
73const OUTPUT_LIMIT: &str = include_str!("../docs/help/output-limit.md");
74const LIMITS: &str = include_str!("../docs/help/limits.md");
75
76pub fn get_help(topic: &HelpTopic, tool_schemas: &[ToolSchema]) -> String {
82 match topic {
83 HelpTopic::Overview => OVERVIEW.to_string(),
84 HelpTopic::Syntax => SYNTAX.to_string(),
85 HelpTopic::Builtins => format_tool_list(tool_schemas),
86 HelpTopic::Vfs => VFS.to_string(),
87 HelpTopic::Scatter => SCATTER.to_string(),
88 HelpTopic::Ignore => IGNORE.to_string(),
89 HelpTopic::OutputLimit => OUTPUT_LIMIT.to_string(),
90 HelpTopic::Limits => LIMITS.to_string(),
91 HelpTopic::Tool(name) => format_tool_help(name, tool_schemas),
92 }
93}
94
95fn format_tool_help(name: &str, schemas: &[ToolSchema]) -> String {
97 match schemas.iter().find(|s| s.name == name) {
98 Some(schema) => {
99 let mut output = String::new();
100
101 output.push_str(&format!("{} — {}\n\n", schema.name, schema.description));
102
103 if schema.params.is_empty() {
104 output.push_str("No parameters.\n");
105 } else {
106 output.push_str("Parameters:\n");
107 for param in &schema.params {
108 let req = if param.required { " (required)" } else { "" };
109 output.push_str(&format!(
110 " {} : {}{}\n {}\n",
111 param.name, param.param_type, req, param.description
112 ));
113 }
114 }
115
116 if !schema.examples.is_empty() {
117 output.push_str("\nExamples:\n");
118 for example in &schema.examples {
119 output.push_str(&format!(" # {}\n", example.description));
120 output.push_str(&format!(" {}\n\n", example.code));
121 }
122 }
123
124 output
125 }
126 None => format!(
127 "Unknown topic or tool: {}\n\nUse 'help' to see available topics, or 'help builtins' for tool list.",
128 name
129 ),
130 }
131}
132
133fn format_tool_list(schemas: &[ToolSchema]) -> String {
138 let mut output = String::from("# Available Builtins\n\n");
139
140 let max_len = schemas.iter().map(|s| s.name.len()).max().unwrap_or(0);
141
142 for schema in schemas {
143 output.push_str(&format!(
144 " {:width$} {}\n",
145 schema.name,
146 schema.description,
147 width = max_len
148 ));
149 }
150
151 output.push_str("\n---\n");
152 output.push_str("Use 'help <tool>' for detailed help on a specific tool.\n");
153 output.push_str("Use 'help syntax' for language syntax reference.\n");
154
155 output
156}
157
158pub fn list_topics() -> Vec<(&'static str, &'static str)> {
160 vec![
161 ("overview", "What kaish is, list of topics"),
162 ("syntax", "Variables, quoting, pipes, control flow"),
163 ("builtins", "List of available builtins"),
164 ("vfs", "Virtual filesystem mounts and paths"),
165 ("scatter", "Parallel processing (散/集)"),
166 ("ignore", "Ignore file configuration"),
167 ("output-limit", "Output size limit configuration"),
168 ("limits", "Known limitations"),
169 ]
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_topic_parsing() {
178 assert_eq!(HelpTopic::parse_topic(""), HelpTopic::Overview);
179 assert_eq!(HelpTopic::parse_topic("overview"), HelpTopic::Overview);
180 assert_eq!(HelpTopic::parse_topic("syntax"), HelpTopic::Syntax);
181 assert_eq!(HelpTopic::parse_topic("SYNTAX"), HelpTopic::Syntax);
182 assert_eq!(HelpTopic::parse_topic("builtins"), HelpTopic::Builtins);
183 assert_eq!(HelpTopic::parse_topic("vfs"), HelpTopic::Vfs);
184 assert_eq!(HelpTopic::parse_topic("scatter"), HelpTopic::Scatter);
185 assert_eq!(HelpTopic::parse_topic("集"), HelpTopic::Scatter);
186 assert_eq!(HelpTopic::parse_topic("output-limit"), HelpTopic::OutputLimit);
187 assert_eq!(HelpTopic::parse_topic("spill"), HelpTopic::OutputLimit);
188 assert_eq!(HelpTopic::parse_topic("kaish-output-limit"), HelpTopic::OutputLimit);
189 assert_eq!(HelpTopic::parse_topic("limits"), HelpTopic::Limits);
190 assert_eq!(
191 HelpTopic::parse_topic("grep"),
192 HelpTopic::Tool("grep".to_string())
193 );
194 }
195
196 #[test]
197 fn test_static_content_embedded() {
198 assert!(OVERVIEW.contains("kaish"));
200 assert!(SYNTAX.contains("Variables"));
201 assert!(VFS.contains("Mount Points"));
202 assert!(SCATTER.contains("scatter"));
203 assert!(IGNORE.contains("kaish-ignore"));
204 assert!(OUTPUT_LIMIT.contains("kaish-output-limit"));
205 assert!(LIMITS.contains("Limitations"));
206 }
207
208 #[test]
209 fn test_get_help_overview() {
210 let content = get_help(&HelpTopic::Overview, &[]);
211 assert!(content.contains("kaish"));
212 assert!(content.contains("help syntax"));
213 }
214
215 #[test]
216 fn test_get_help_unknown_tool() {
217 let content = get_help(&HelpTopic::Tool("nonexistent".to_string()), &[]);
218 assert!(content.contains("Unknown topic or tool"));
219 }
220}