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