Skip to main content

agent_rules_tool/
walk.rs

1//! Recursive discovery of rule files on disk.
2
3use crate::RuleFormat;
4use crate::discover::ToolRuleDir;
5use std::path::{Path, PathBuf};
6
7/// A rule file found during a directory walk.
8#[derive(Debug, Clone)]
9pub struct DiscoveredFile {
10    /// Absolute path to the file.
11    pub path: PathBuf,
12    /// Path relative to the walk root.
13    pub relative: PathBuf,
14    /// Format associated with the tool directory.
15    pub format: RuleFormat,
16}
17
18/// Walk a tool rule directory using extension/suffix rules from `dir`.
19pub fn walk_tool_dir(
20    root: &Path,
21    dir: &ToolRuleDir,
22) -> Result<Vec<DiscoveredFile>, std::io::Error> {
23    let mut files = Vec::new();
24    walk_recursive(root, root, dir, &mut files)?;
25    files.sort_by(|a, b| a.relative.cmp(&b.relative));
26    Ok(files)
27}
28
29fn walk_recursive(
30    root: &Path,
31    current: &Path,
32    dir: &ToolRuleDir,
33    files: &mut Vec<DiscoveredFile>,
34) -> Result<(), std::io::Error> {
35    if !current.is_dir() {
36        return Ok(());
37    }
38
39    for entry in std::fs::read_dir(current)? {
40        let entry = entry?;
41        let path = entry.path();
42        if path.is_dir() {
43            walk_recursive(root, &path, dir, files)?;
44        } else if matches_rule_file(&path, dir) {
45            let relative = path.strip_prefix(root).unwrap_or(&path).to_path_buf();
46            files.push(DiscoveredFile {
47                path: path.clone(),
48                relative,
49                format: dir.format,
50            });
51        }
52    }
53    Ok(())
54}
55
56fn matches_rule_file(path: &Path, dir: &ToolRuleDir) -> bool {
57    let file_name = match path.file_name().and_then(|n| n.to_str()) {
58        Some(n) => n,
59        None => return false,
60    };
61
62    if let Some(suffix) = dir.suffix {
63        return file_name.ends_with(suffix);
64    }
65
66    path.extension()
67        .and_then(|e| e.to_str())
68        .is_some_and(|ext| dir.extensions.contains(&ext))
69}
70
71/// Recursively collect all `*.md` files under `root`.
72pub fn walk_md_files(root: &Path) -> Result<Vec<PathBuf>, std::io::Error> {
73    let mut files = Vec::new();
74    walk_md_recursive(root, &mut files)?;
75    files.sort();
76    Ok(files)
77}
78
79fn walk_md_recursive(current: &Path, files: &mut Vec<PathBuf>) -> Result<(), std::io::Error> {
80    if !current.is_dir() {
81        return Ok(());
82    }
83    for entry in std::fs::read_dir(current)? {
84        let entry = entry?;
85        let path = entry.path();
86        if path.is_dir() {
87            walk_md_recursive(&path, files)?;
88        } else if path.extension().and_then(|e| e.to_str()) == Some("md") {
89            files.push(path);
90        }
91    }
92    Ok(())
93}