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