Skip to main content

rumdl_lib/code_block_tools/
registry.rs

1//! Built-in tool registry with definitions for common formatters and linters.
2//!
3//! This module provides default configurations for popular tools like ruff, prettier,
4//! shellcheck, etc. Users can override these in their configuration.
5
6use super::config::ToolDefinition;
7use std::collections::HashMap;
8use std::sync::LazyLock;
9
10/// Registry of built-in tool definitions.
11pub struct ToolRegistry {
12    /// User-defined tools (override built-ins)
13    user_tools: HashMap<String, ToolDefinition>,
14}
15
16impl ToolRegistry {
17    /// Create a new registry with user-defined tools.
18    pub fn new(user_tools: HashMap<String, ToolDefinition>) -> Self {
19        Self { user_tools }
20    }
21
22    /// Get a tool definition by ID.
23    ///
24    /// Checks user tools first, then falls back to built-in tools.
25    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    /// Check if a tool ID is valid (either user-defined or built-in).
30    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    /// List all available tool IDs.
35    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
53/// Built-in tool definitions.
54///
55/// These are common formatters and linters that work well with stdin/stdout.
56static BUILTIN_TOOLS: LazyLock<HashMap<&'static str, ToolDefinition>> = LazyLock::new(|| {
57    let mut m = HashMap::new();
58
59    // Python - ruff
60    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    // Python - black
94    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    // JavaScript/TypeScript - prettier
106    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    // JavaScript/TypeScript - eslint (lint only)
173    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    // Shell - shellcheck (lint only)
189    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    // Shell - shfmt
201    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()], // diff mode for lint
208            format_args: vec![],
209        },
210    );
211
212    // Rust - rustfmt
213    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    // Go - gofmt
225    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()], // diff mode for lint
232            format_args: vec![],
233        },
234    );
235
236    // Go - goimports
237    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    // C/C++ - clang-format
249    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    // SQL - sqlfluff
261    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    // JSON - jq (format)
284    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    // YAML - yamlfmt
296    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    // TOML - taplo
308    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    // Terraform - terraform fmt
320    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    // Nix - nixfmt
332    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    // Lua - stylua
344    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    // Ruby - rubocop
356    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    // Haskell - ormolu
368    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    // Elm - elm-format
380    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    // Zig - zig fmt
392    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    // Dart - dart format
404    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    // Swift - swift-format
416    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    // Kotlin - ktfmt
428    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); // User override
478    }
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")); // Built-in still available
514    }
515}