1use super::config::ToolDefinition;
7use std::collections::HashMap;
8use std::sync::LazyLock;
9
10pub struct ToolRegistry {
12 user_tools: HashMap<String, ToolDefinition>,
14}
15
16impl ToolRegistry {
17 pub fn new(user_tools: HashMap<String, ToolDefinition>) -> Self {
19 Self { user_tools }
20 }
21
22 pub fn get(&self, tool_id: &str) -> Option<&ToolDefinition> {
26 self.user_tools.get(tool_id).or_else(|| BUILTIN_TOOLS.get(tool_id))
27 }
28
29 pub fn contains(&self, tool_id: &str) -> bool {
31 self.user_tools.contains_key(tool_id) || BUILTIN_TOOLS.contains_key(tool_id)
32 }
33
34 pub fn list_tools(&self) -> Vec<&str> {
36 let mut tools: Vec<&str> = self.user_tools.keys().map(|s| s.as_str()).collect();
37 for key in BUILTIN_TOOLS.keys() {
38 if !self.user_tools.contains_key(*key) {
39 tools.push(key);
40 }
41 }
42 tools.sort();
43 tools
44 }
45}
46
47impl Default for ToolRegistry {
48 fn default() -> Self {
49 Self::new(HashMap::new())
50 }
51}
52
53static BUILTIN_TOOLS: LazyLock<HashMap<&'static str, ToolDefinition>> = LazyLock::new(|| {
57 let mut m = HashMap::new();
58
59 m.insert(
61 "ruff:check",
62 ToolDefinition {
63 command: vec![
64 "ruff".to_string(),
65 "check".to_string(),
66 "--output-format=concise".to_string(),
67 "--stdin-filename=_.py".to_string(),
68 "-".to_string(),
69 ],
70 stdin: true,
71 stdout: true,
72 lint_args: vec![],
73 format_args: vec![],
74 },
75 );
76
77 m.insert(
78 "ruff:format",
79 ToolDefinition {
80 command: vec![
81 "ruff".to_string(),
82 "format".to_string(),
83 "--stdin-filename=_.py".to_string(),
84 "-".to_string(),
85 ],
86 stdin: true,
87 stdout: true,
88 lint_args: vec![],
89 format_args: vec![],
90 },
91 );
92
93 m.insert(
95 "black",
96 ToolDefinition {
97 command: vec!["black".to_string(), "--quiet".to_string(), "-".to_string()],
98 stdin: true,
99 stdout: true,
100 lint_args: vec!["--check".to_string()],
101 format_args: vec![],
102 },
103 );
104
105 m.insert(
107 "prettier",
108 ToolDefinition {
109 command: vec!["prettier".to_string(), "--stdin-filepath=_.js".to_string()],
110 stdin: true,
111 stdout: true,
112 lint_args: vec!["--check".to_string()],
113 format_args: vec![],
114 },
115 );
116
117 m.insert(
118 "prettier:json",
119 ToolDefinition {
120 command: vec!["prettier".to_string(), "--stdin-filepath=_.json".to_string()],
121 stdin: true,
122 stdout: true,
123 lint_args: vec!["--check".to_string()],
124 format_args: vec![],
125 },
126 );
127
128 m.insert(
129 "prettier:yaml",
130 ToolDefinition {
131 command: vec!["prettier".to_string(), "--stdin-filepath=_.yaml".to_string()],
132 stdin: true,
133 stdout: true,
134 lint_args: vec!["--check".to_string()],
135 format_args: vec![],
136 },
137 );
138
139 m.insert(
140 "prettier:html",
141 ToolDefinition {
142 command: vec!["prettier".to_string(), "--stdin-filepath=_.html".to_string()],
143 stdin: true,
144 stdout: true,
145 lint_args: vec!["--check".to_string()],
146 format_args: vec![],
147 },
148 );
149
150 m.insert(
151 "prettier:css",
152 ToolDefinition {
153 command: vec!["prettier".to_string(), "--stdin-filepath=_.css".to_string()],
154 stdin: true,
155 stdout: true,
156 lint_args: vec!["--check".to_string()],
157 format_args: vec![],
158 },
159 );
160
161 m.insert(
162 "prettier:markdown",
163 ToolDefinition {
164 command: vec!["prettier".to_string(), "--stdin-filepath=_.md".to_string()],
165 stdin: true,
166 stdout: true,
167 lint_args: vec!["--check".to_string()],
168 format_args: vec![],
169 },
170 );
171
172 m.insert(
174 "eslint",
175 ToolDefinition {
176 command: vec![
177 "eslint".to_string(),
178 "--stdin".to_string(),
179 "--stdin-filename=_.js".to_string(),
180 ],
181 stdin: true,
182 stdout: true,
183 lint_args: vec![],
184 format_args: vec!["--fix-dry-run".to_string()],
185 },
186 );
187
188 m.insert(
190 "shellcheck",
191 ToolDefinition {
192 command: vec!["shellcheck".to_string(), "-".to_string()],
193 stdin: true,
194 stdout: true,
195 lint_args: vec![],
196 format_args: vec![],
197 },
198 );
199
200 m.insert(
202 "shfmt",
203 ToolDefinition {
204 command: vec!["shfmt".to_string()],
205 stdin: true,
206 stdout: true,
207 lint_args: vec!["-d".to_string()], format_args: vec![],
209 },
210 );
211
212 m.insert(
214 "rustfmt",
215 ToolDefinition {
216 command: vec!["rustfmt".to_string()],
217 stdin: true,
218 stdout: true,
219 lint_args: vec!["--check".to_string()],
220 format_args: vec![],
221 },
222 );
223
224 m.insert(
226 "gofmt",
227 ToolDefinition {
228 command: vec!["gofmt".to_string()],
229 stdin: true,
230 stdout: true,
231 lint_args: vec!["-d".to_string()], format_args: vec![],
233 },
234 );
235
236 m.insert(
238 "goimports",
239 ToolDefinition {
240 command: vec!["goimports".to_string()],
241 stdin: true,
242 stdout: true,
243 lint_args: vec!["-d".to_string()],
244 format_args: vec![],
245 },
246 );
247
248 m.insert(
250 "clang-format",
251 ToolDefinition {
252 command: vec!["clang-format".to_string()],
253 stdin: true,
254 stdout: true,
255 lint_args: vec!["--dry-run".to_string(), "--Werror".to_string()],
256 format_args: vec![],
257 },
258 );
259
260 m.insert(
262 "sqlfluff:lint",
263 ToolDefinition {
264 command: vec!["sqlfluff".to_string(), "lint".to_string(), "-".to_string()],
265 stdin: true,
266 stdout: true,
267 lint_args: vec![],
268 format_args: vec![],
269 },
270 );
271
272 m.insert(
273 "sqlfluff:fix",
274 ToolDefinition {
275 command: vec!["sqlfluff".to_string(), "fix".to_string(), "-".to_string()],
276 stdin: true,
277 stdout: true,
278 lint_args: vec![],
279 format_args: vec![],
280 },
281 );
282
283 m.insert(
285 "jq",
286 ToolDefinition {
287 command: vec!["jq".to_string(), ".".to_string()],
288 stdin: true,
289 stdout: true,
290 lint_args: vec![],
291 format_args: vec![],
292 },
293 );
294
295 m.insert(
297 "yamlfmt",
298 ToolDefinition {
299 command: vec!["yamlfmt".to_string(), "-".to_string()],
300 stdin: true,
301 stdout: true,
302 lint_args: vec!["-dry".to_string()],
303 format_args: vec![],
304 },
305 );
306
307 m.insert(
309 "taplo",
310 ToolDefinition {
311 command: vec!["taplo".to_string(), "fmt".to_string(), "-".to_string()],
312 stdin: true,
313 stdout: true,
314 lint_args: vec!["--check".to_string()],
315 format_args: vec![],
316 },
317 );
318
319 m.insert(
321 "terraform-fmt",
322 ToolDefinition {
323 command: vec!["terraform".to_string(), "fmt".to_string(), "-".to_string()],
324 stdin: true,
325 stdout: true,
326 lint_args: vec!["-check".to_string()],
327 format_args: vec![],
328 },
329 );
330
331 m.insert(
333 "nixfmt",
334 ToolDefinition {
335 command: vec!["nixfmt".to_string()],
336 stdin: true,
337 stdout: true,
338 lint_args: vec!["--check".to_string()],
339 format_args: vec![],
340 },
341 );
342
343 m.insert(
345 "stylua",
346 ToolDefinition {
347 command: vec!["stylua".to_string(), "-".to_string()],
348 stdin: true,
349 stdout: true,
350 lint_args: vec!["--check".to_string()],
351 format_args: vec![],
352 },
353 );
354
355 m.insert(
357 "rubocop",
358 ToolDefinition {
359 command: vec!["rubocop".to_string(), "--stdin".to_string(), "_.rb".to_string()],
360 stdin: true,
361 stdout: true,
362 lint_args: vec![],
363 format_args: vec!["--autocorrect".to_string()],
364 },
365 );
366
367 m.insert(
369 "ormolu",
370 ToolDefinition {
371 command: vec!["ormolu".to_string()],
372 stdin: true,
373 stdout: true,
374 lint_args: vec!["--check-idempotence".to_string()],
375 format_args: vec![],
376 },
377 );
378
379 m.insert(
381 "elm-format",
382 ToolDefinition {
383 command: vec!["elm-format".to_string(), "--stdin".to_string()],
384 stdin: true,
385 stdout: true,
386 lint_args: vec!["--validate".to_string()],
387 format_args: vec![],
388 },
389 );
390
391 m.insert(
393 "zig-fmt",
394 ToolDefinition {
395 command: vec!["zig".to_string(), "fmt".to_string(), "--stdin".to_string()],
396 stdin: true,
397 stdout: true,
398 lint_args: vec!["--check".to_string()],
399 format_args: vec![],
400 },
401 );
402
403 m.insert(
405 "dart-format",
406 ToolDefinition {
407 command: vec!["dart".to_string(), "format".to_string()],
408 stdin: true,
409 stdout: true,
410 lint_args: vec!["--output=none".to_string(), "--set-exit-if-changed".to_string()],
411 format_args: vec![],
412 },
413 );
414
415 m.insert(
417 "swift-format",
418 ToolDefinition {
419 command: vec!["swift-format".to_string()],
420 stdin: true,
421 stdout: true,
422 lint_args: vec!["lint".to_string()],
423 format_args: vec![],
424 },
425 );
426
427 m.insert(
429 "ktfmt",
430 ToolDefinition {
431 command: vec!["ktfmt".to_string(), "--stdin".to_string()],
432 stdin: true,
433 stdout: true,
434 lint_args: vec!["--dry-run".to_string()],
435 format_args: vec![],
436 },
437 );
438
439 m
440});
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445
446 #[test]
447 fn test_get_builtin_tool() {
448 let registry = ToolRegistry::default();
449
450 let tool = registry.get("ruff:check").expect("Should find ruff:check");
451 assert!(tool.command.contains(&"ruff".to_string()));
452 assert!(tool.stdin);
453 assert!(tool.stdout);
454
455 let tool = registry.get("shellcheck").expect("Should find shellcheck");
456 assert!(tool.command.contains(&"shellcheck".to_string()));
457 }
458
459 #[test]
460 fn test_get_user_tool_overrides_builtin() {
461 let mut user_tools = HashMap::new();
462 user_tools.insert(
463 "ruff:check".to_string(),
464 ToolDefinition {
465 command: vec!["custom-ruff".to_string()],
466 stdin: false,
467 stdout: false,
468 lint_args: vec![],
469 format_args: vec![],
470 },
471 );
472
473 let registry = ToolRegistry::new(user_tools);
474
475 let tool = registry.get("ruff:check").expect("Should find ruff:check");
476 assert_eq!(tool.command, vec!["custom-ruff"]);
477 assert!(!tool.stdin); }
479
480 #[test]
481 fn test_contains() {
482 let registry = ToolRegistry::default();
483
484 assert!(registry.contains("ruff:check"));
485 assert!(registry.contains("prettier"));
486 assert!(registry.contains("shellcheck"));
487 assert!(!registry.contains("nonexistent-tool"));
488 }
489
490 #[test]
491 fn test_list_tools() {
492 let registry = ToolRegistry::default();
493 let tools = registry.list_tools();
494
495 assert!(tools.contains(&"ruff:check"));
496 assert!(tools.contains(&"ruff:format"));
497 assert!(tools.contains(&"prettier"));
498 assert!(tools.contains(&"shellcheck"));
499 assert!(tools.contains(&"shfmt"));
500 assert!(tools.contains(&"rustfmt"));
501 assert!(tools.contains(&"gofmt"));
502 }
503
504 #[test]
505 fn test_user_tools_in_list() {
506 let mut user_tools = HashMap::new();
507 user_tools.insert("my-custom-tool".to_string(), ToolDefinition::default());
508
509 let registry = ToolRegistry::new(user_tools);
510 let tools = registry.list_tools();
511
512 assert!(tools.contains(&"my-custom-tool"));
513 assert!(tools.contains(&"ruff:check")); }
515}