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 {
135 let mut output = String::new();
136
137 output.push_str("# Available Builtins\n\n");
138
139 let max_len = schemas.iter().map(|s| s.name.len()).max().unwrap_or(0);
141
142 let mut text_tools = Vec::new();
145 let mut file_tools = Vec::new();
146 let mut system_tools = Vec::new();
147 let mut json_tools = Vec::new();
148 let mut parallel_tools = Vec::new();
149 let mut process_tools = Vec::new();
150 let mut introspection_tools = Vec::new();
151 let mut other_tools = Vec::new();
152
153 for schema in schemas {
154 let entry = (schema.name.as_str(), schema.description.as_str());
155 match schema.name.as_str() {
156 "grep" | "sed" | "awk" | "cut" | "tr" | "sort" | "uniq" | "wc" | "head" | "tail"
157 | "split" | "diff" => text_tools.push(entry),
158 "cat" | "ls" | "tree" | "cd" | "pwd" | "mkdir" | "rm" | "cp" | "mv" | "touch"
159 | "ln" | "readlink" | "write" | "glob" | "find" | "stat" | "dirname" | "basename"
160 | "realpath" | "mktemp" | "patch" => file_tools.push(entry),
161 "alias" | "unalias" | "echo" | "printf" | "read" | "sleep" | "date" | "env"
162 | "export" | "set" | "unset" | "true" | "false" | "test" | "[" | "assert" | "seq"
163 | "tee" | "hostname" | "uname" | "which" => system_tools.push(entry),
164 "jq" => json_tools.push(entry),
165 "scatter" | "gather" => parallel_tools.push(entry),
166 "exec" | "spawn" | "jobs" | "wait" | "ps" | "git" | "bg" | "fg" | "kill" => process_tools.push(entry),
167 "help" | "kaish-validate" | "kaish-vars" | "kaish-mounts" | "kaish-tools" | "tokens"
168 | "kaish-ast" | "kaish-clear" | "kaish-status" | "kaish-trash" | "kaish-version"
169 | "kaish-ignore" | "kaish-output-limit" => {
170 introspection_tools.push(entry)
171 }
172 _ => other_tools.push(entry),
173 }
174 }
175
176 let format_group = |name: &str, tools: &[(&str, &str)], max: usize| -> String {
177 if tools.is_empty() {
178 return String::new();
179 }
180 let mut s = format!("## {}\n\n", name);
181 for (tool_name, desc) in tools {
182 s.push_str(&format!(" {:width$} {}\n", tool_name, desc, width = max));
183 }
184 s.push('\n');
185 s
186 };
187
188 output.push_str(&format_group("Text Processing", &text_tools, max_len));
189 output.push_str(&format_group("Files & Directories", &file_tools, max_len));
190 output.push_str(&format_group("JSON", &json_tools, max_len));
191 output.push_str(&format_group("Processes & Jobs", &process_tools, max_len));
192 output.push_str(&format_group("Parallel (散/集)", ¶llel_tools, max_len));
193 output.push_str(&format_group("Shell & System", &system_tools, max_len));
194 output.push_str(&format_group("Introspection", &introspection_tools, max_len));
195 output.push_str(&format_group("Other", &other_tools, max_len));
196
197 output.push_str("---\n");
198 output.push_str("Use 'help <tool>' for detailed help on a specific tool.\n");
199 output.push_str("Use 'help syntax' for language syntax reference.\n");
200
201 output
202}
203
204pub fn list_topics() -> Vec<(&'static str, &'static str)> {
206 vec![
207 ("overview", "What kaish is, list of topics"),
208 ("syntax", "Variables, quoting, pipes, control flow"),
209 ("builtins", "List of available builtins"),
210 ("vfs", "Virtual filesystem mounts and paths"),
211 ("scatter", "Parallel processing (散/集)"),
212 ("ignore", "Ignore file configuration"),
213 ("output-limit", "Output size limit configuration"),
214 ("limits", "Known limitations"),
215 ]
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_topic_parsing() {
224 assert_eq!(HelpTopic::parse_topic(""), HelpTopic::Overview);
225 assert_eq!(HelpTopic::parse_topic("overview"), HelpTopic::Overview);
226 assert_eq!(HelpTopic::parse_topic("syntax"), HelpTopic::Syntax);
227 assert_eq!(HelpTopic::parse_topic("SYNTAX"), HelpTopic::Syntax);
228 assert_eq!(HelpTopic::parse_topic("builtins"), HelpTopic::Builtins);
229 assert_eq!(HelpTopic::parse_topic("vfs"), HelpTopic::Vfs);
230 assert_eq!(HelpTopic::parse_topic("scatter"), HelpTopic::Scatter);
231 assert_eq!(HelpTopic::parse_topic("集"), HelpTopic::Scatter);
232 assert_eq!(HelpTopic::parse_topic("output-limit"), HelpTopic::OutputLimit);
233 assert_eq!(HelpTopic::parse_topic("spill"), HelpTopic::OutputLimit);
234 assert_eq!(HelpTopic::parse_topic("kaish-output-limit"), HelpTopic::OutputLimit);
235 assert_eq!(HelpTopic::parse_topic("limits"), HelpTopic::Limits);
236 assert_eq!(
237 HelpTopic::parse_topic("grep"),
238 HelpTopic::Tool("grep".to_string())
239 );
240 }
241
242 #[test]
243 fn test_static_content_embedded() {
244 assert!(OVERVIEW.contains("kaish"));
246 assert!(SYNTAX.contains("Variables"));
247 assert!(VFS.contains("Mount Points"));
248 assert!(SCATTER.contains("scatter"));
249 assert!(IGNORE.contains("kaish-ignore"));
250 assert!(OUTPUT_LIMIT.contains("kaish-output-limit"));
251 assert!(LIMITS.contains("Limitations"));
252 }
253
254 #[test]
255 fn test_get_help_overview() {
256 let content = get_help(&HelpTopic::Overview, &[]);
257 assert!(content.contains("kaish"));
258 assert!(content.contains("help syntax"));
259 }
260
261 #[test]
262 fn test_get_help_unknown_tool() {
263 let content = get_help(&HelpTopic::Tool("nonexistent".to_string()), &[]);
264 assert!(content.contains("Unknown topic or tool"));
265 }
266}