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.insert(
441 "djlint",
442 ToolDefinition {
443 command: vec!["djlint".to_string(), "-".to_string()],
444 stdin: true,
445 stdout: true,
446 lint_args: vec![],
447 format_args: vec!["--reformat".to_string()],
448 },
449 );
450
451 m.insert(
452 "djlint:lint",
453 ToolDefinition {
454 command: vec!["djlint".to_string(), "-".to_string()],
455 stdin: true,
456 stdout: true,
457 lint_args: vec![],
458 format_args: vec![],
459 },
460 );
461
462 m.insert(
463 "djlint:reformat",
464 ToolDefinition {
465 command: vec!["djlint".to_string(), "-".to_string(), "--reformat".to_string()],
466 stdin: true,
467 stdout: true,
468 lint_args: vec![],
469 format_args: vec![],
470 },
471 );
472
473 m.insert(
475 "beautysh",
476 ToolDefinition {
477 command: vec!["beautysh".to_string(), "-".to_string()],
478 stdin: true,
479 stdout: true,
480 lint_args: vec!["--check".to_string()],
481 format_args: vec![],
482 },
483 );
484
485 m.insert(
487 "tombi",
488 ToolDefinition {
489 command: vec!["tombi".to_string(), "format".to_string(), "-".to_string()],
490 stdin: true,
491 stdout: true,
492 lint_args: vec![],
493 format_args: vec![],
494 },
495 );
496
497 m.insert(
498 "tombi:format",
499 ToolDefinition {
500 command: vec!["tombi".to_string(), "format".to_string(), "-".to_string()],
501 stdin: true,
502 stdout: true,
503 lint_args: vec![],
504 format_args: vec![],
505 },
506 );
507
508 m.insert(
509 "tombi:lint",
510 ToolDefinition {
511 command: vec!["tombi".to_string(), "lint".to_string(), "-".to_string()],
512 stdin: true,
513 stdout: true,
514 lint_args: vec![],
515 format_args: vec![],
516 },
517 );
518
519 m.insert(
521 "oxfmt",
522 ToolDefinition {
523 command: vec!["oxfmt".to_string(), "--stdin-filepath=_.js".to_string()],
524 stdin: true,
525 stdout: true,
526 lint_args: vec!["--check".to_string()],
527 format_args: vec![],
528 },
529 );
530
531 m.insert(
532 "oxfmt:js",
533 ToolDefinition {
534 command: vec!["oxfmt".to_string(), "--stdin-filepath=_.js".to_string()],
535 stdin: true,
536 stdout: true,
537 lint_args: vec!["--check".to_string()],
538 format_args: vec![],
539 },
540 );
541
542 m.insert(
543 "oxfmt:ts",
544 ToolDefinition {
545 command: vec!["oxfmt".to_string(), "--stdin-filepath=_.ts".to_string()],
546 stdin: true,
547 stdout: true,
548 lint_args: vec!["--check".to_string()],
549 format_args: vec![],
550 },
551 );
552
553 m.insert(
554 "oxfmt:jsx",
555 ToolDefinition {
556 command: vec!["oxfmt".to_string(), "--stdin-filepath=_.jsx".to_string()],
557 stdin: true,
558 stdout: true,
559 lint_args: vec!["--check".to_string()],
560 format_args: vec![],
561 },
562 );
563
564 m.insert(
565 "oxfmt:tsx",
566 ToolDefinition {
567 command: vec!["oxfmt".to_string(), "--stdin-filepath=_.tsx".to_string()],
568 stdin: true,
569 stdout: true,
570 lint_args: vec!["--check".to_string()],
571 format_args: vec![],
572 },
573 );
574
575 m.insert(
576 "oxfmt:json",
577 ToolDefinition {
578 command: vec!["oxfmt".to_string(), "--stdin-filepath=_.json".to_string()],
579 stdin: true,
580 stdout: true,
581 lint_args: vec!["--check".to_string()],
582 format_args: vec![],
583 },
584 );
585
586 m.insert(
587 "oxfmt:css",
588 ToolDefinition {
589 command: vec!["oxfmt".to_string(), "--stdin-filepath=_.css".to_string()],
590 stdin: true,
591 stdout: true,
592 lint_args: vec!["--check".to_string()],
593 format_args: vec![],
594 },
595 );
596
597 m
598});
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603
604 #[test]
605 fn test_get_builtin_tool() {
606 let registry = ToolRegistry::default();
607
608 let tool = registry.get("ruff:check").expect("Should find ruff:check");
609 assert!(tool.command.contains(&"ruff".to_string()));
610 assert!(tool.stdin);
611 assert!(tool.stdout);
612
613 let tool = registry.get("shellcheck").expect("Should find shellcheck");
614 assert!(tool.command.contains(&"shellcheck".to_string()));
615 }
616
617 #[test]
618 fn test_get_user_tool_overrides_builtin() {
619 let mut user_tools = HashMap::new();
620 user_tools.insert(
621 "ruff:check".to_string(),
622 ToolDefinition {
623 command: vec!["custom-ruff".to_string()],
624 stdin: false,
625 stdout: false,
626 lint_args: vec![],
627 format_args: vec![],
628 },
629 );
630
631 let registry = ToolRegistry::new(user_tools);
632
633 let tool = registry.get("ruff:check").expect("Should find ruff:check");
634 assert_eq!(tool.command, vec!["custom-ruff"]);
635 assert!(!tool.stdin); }
637
638 #[test]
639 fn test_contains() {
640 let registry = ToolRegistry::default();
641
642 assert!(registry.contains("ruff:check"));
643 assert!(registry.contains("prettier"));
644 assert!(registry.contains("shellcheck"));
645 assert!(!registry.contains("nonexistent-tool"));
646 }
647
648 #[test]
649 fn test_list_tools() {
650 let registry = ToolRegistry::default();
651 let tools = registry.list_tools();
652
653 assert!(tools.contains(&"ruff:check"));
654 assert!(tools.contains(&"ruff:format"));
655 assert!(tools.contains(&"prettier"));
656 assert!(tools.contains(&"shellcheck"));
657 assert!(tools.contains(&"shfmt"));
658 assert!(tools.contains(&"rustfmt"));
659 assert!(tools.contains(&"gofmt"));
660 }
661
662 #[test]
663 fn test_user_tools_in_list() {
664 let mut user_tools = HashMap::new();
665 user_tools.insert("my-custom-tool".to_string(), ToolDefinition::default());
666
667 let registry = ToolRegistry::new(user_tools);
668 let tools = registry.list_tools();
669
670 assert!(tools.contains(&"my-custom-tool"));
671 assert!(tools.contains(&"ruff:check")); }
673
674 #[test]
675 fn test_new_builtin_tools() {
676 let registry = ToolRegistry::default();
677
678 let tool = registry.get("djlint").expect("Should find djlint");
680 assert!(tool.command.contains(&"djlint".to_string()));
681 assert!(tool.stdin);
682
683 let tool = registry.get("beautysh").expect("Should find beautysh");
685 assert!(tool.command.contains(&"beautysh".to_string()));
686 assert!(tool.stdin);
687
688 let tool = registry.get("tombi").expect("Should find tombi");
690 assert!(tool.command.contains(&"tombi".to_string()));
691 assert!(tool.stdin);
692
693 let tool = registry.get("tombi:lint").expect("Should find tombi:lint");
694 assert!(tool.command.contains(&"lint".to_string()));
695
696 let tool = registry.get("oxfmt").expect("Should find oxfmt");
698 assert!(tool.command.contains(&"oxfmt".to_string()));
699 assert!(tool.stdin);
700
701 let tool = registry.get("oxfmt:ts").expect("Should find oxfmt:ts");
702 assert!(tool.command.iter().any(|s| s.contains("_.ts")));
703 }
704}