1use kaish_types::ToolSchema;
8
9use crate::content::{IGNORE, LIMITS, OUTPUT_LIMIT, OVERLAY, 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 Overlay,
32 Tool(String),
34}
35
36impl HelpTopic {
37 pub fn parse_topic(s: &str) -> Self {
42 match s.to_lowercase().as_str() {
43 "" | "overview" | "help" => Self::Overview,
44 "syntax" | "language" | "lang" => Self::Syntax,
45 "builtins" | "tools" | "commands" => Self::Builtins,
46 "vfs" | "filesystem" | "fs" | "paths" => Self::Vfs,
47 "scatter" | "gather" | "parallel" | "散" | "集" => Self::Scatter,
48 "ignore" | "gitignore" | "kaish-ignore" => Self::Ignore,
49 "output-limit" | "spill" | "truncate" | "kaish-output-limit" => Self::OutputLimit,
50 "limits" | "limitations" | "missing" => Self::Limits,
51 "overlay" | "kaish-vfs" | "vfs-overlay" => Self::Overlay,
52 other => Self::Tool(other.to_string()),
53 }
54 }
55
56 pub fn description(&self) -> &'static str {
58 match self {
59 Self::Overview => "What kaish is, list of topics",
60 Self::Syntax => "Variables, quoting, pipes, control flow",
61 Self::Builtins => "List of available builtins",
62 Self::Vfs => "Virtual filesystem mounts and paths",
63 Self::Scatter => "Parallel processing (散/集)",
64 Self::Ignore => "Ignore file configuration",
65 Self::OutputLimit => "Output size limit configuration",
66 Self::Limits => "Known limitations",
67 Self::Overlay => "Copy-on-write overlay mode and kaish-vfs",
68 Self::Tool(_) => "Help for a specific tool",
69 }
70 }
71}
72
73pub fn get_help(topic: &HelpTopic, tool_schemas: &[ToolSchema]) -> String {
79 match topic {
80 HelpTopic::Overview => OVERVIEW.to_string(),
81 HelpTopic::Syntax => SYNTAX.to_string(),
82 HelpTopic::Builtins => format_tool_list(tool_schemas),
83 HelpTopic::Vfs => VFS.to_string(),
84 HelpTopic::Scatter => SCATTER.to_string(),
85 HelpTopic::Ignore => IGNORE.to_string(),
86 HelpTopic::OutputLimit => OUTPUT_LIMIT.to_string(),
87 HelpTopic::Limits => LIMITS.to_string(),
88 HelpTopic::Overlay => OVERLAY.to_string(),
89 HelpTopic::Tool(name) => format_tool_help(name, tool_schemas),
90 }
91}
92
93pub fn tool_help(name: &str, schemas: &[ToolSchema]) -> Option<String> {
98 let schema = schemas.iter().find(|s| s.name == name)?;
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 Some(output)
125}
126
127fn format_tool_help(name: &str, schemas: &[ToolSchema]) -> String {
129 tool_help(name, schemas).unwrap_or_else(|| {
130 format!(
131 "Unknown topic or tool: {}\n\nUse 'help' to see available topics, or 'help builtins' for tool list.",
132 name
133 )
134 })
135}
136
137fn format_tool_list(schemas: &[ToolSchema]) -> String {
142 let mut output = String::from("# Available Builtins\n\n");
143
144 let max_len = schemas.iter().map(|s| s.name.len()).max().unwrap_or(0);
145
146 for schema in schemas {
147 output.push_str(&format!(
148 " {:width$} {}\n",
149 schema.name,
150 schema.description,
151 width = max_len
152 ));
153 }
154
155 output.push_str("\n---\n");
156 output.push_str("Use 'help <tool>' for detailed help on a specific tool.\n");
157 output.push_str("Use 'help syntax' for language syntax reference.\n");
158
159 output
160}
161
162pub fn list_topics() -> Vec<(&'static str, &'static str)> {
164 vec![
165 ("overview", "What kaish is, list of topics"),
166 ("syntax", "Variables, quoting, pipes, control flow"),
167 ("builtins", "List of available builtins"),
168 ("vfs", "Virtual filesystem mounts and paths"),
169 ("scatter", "Parallel processing (散/集)"),
170 ("ignore", "Ignore file configuration"),
171 ("output-limit", "Output size limit configuration"),
172 ("limits", "Known limitations"),
173 ("overlay", "Copy-on-write overlay mode and kaish-vfs"),
174 ]
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_topic_parsing() {
183 assert_eq!(HelpTopic::parse_topic(""), HelpTopic::Overview);
184 assert_eq!(HelpTopic::parse_topic("overview"), HelpTopic::Overview);
185 assert_eq!(HelpTopic::parse_topic("syntax"), HelpTopic::Syntax);
186 assert_eq!(HelpTopic::parse_topic("SYNTAX"), HelpTopic::Syntax);
187 assert_eq!(HelpTopic::parse_topic("builtins"), HelpTopic::Builtins);
188 assert_eq!(HelpTopic::parse_topic("vfs"), HelpTopic::Vfs);
189 assert_eq!(HelpTopic::parse_topic("scatter"), HelpTopic::Scatter);
190 assert_eq!(HelpTopic::parse_topic("集"), HelpTopic::Scatter);
191 assert_eq!(HelpTopic::parse_topic("output-limit"), HelpTopic::OutputLimit);
192 assert_eq!(HelpTopic::parse_topic("spill"), HelpTopic::OutputLimit);
193 assert_eq!(HelpTopic::parse_topic("kaish-output-limit"), HelpTopic::OutputLimit);
194 assert_eq!(HelpTopic::parse_topic("limits"), HelpTopic::Limits);
195 assert_eq!(
196 HelpTopic::parse_topic("grep"),
197 HelpTopic::Tool("grep".to_string())
198 );
199 }
200
201 #[test]
202 fn test_static_content_embedded() {
203 assert!(OVERVIEW.contains("kaish"));
205 assert!(SYNTAX.contains("Variables"));
206 assert!(VFS.contains("Mount Points"));
207 assert!(SCATTER.contains("scatter"));
208 assert!(IGNORE.contains("kaish-ignore"));
209 assert!(OUTPUT_LIMIT.contains("kaish-output-limit"));
210 assert!(LIMITS.contains("Limitations"));
211 }
212
213 #[test]
214 fn test_get_help_overview() {
215 let content = get_help(&HelpTopic::Overview, &[]);
216 assert!(content.contains("kaish"));
217 assert!(content.contains("help syntax"));
218 }
219
220 #[test]
221 fn test_get_help_unknown_tool() {
222 let content = get_help(&HelpTopic::Tool("nonexistent".to_string()), &[]);
223 assert!(content.contains("Unknown topic or tool"));
224 }
225
226 #[test]
227 fn test_tool_help_none_for_missing() {
228 assert!(tool_help("nonexistent", &[]).is_none());
229 }
230}