1use std::path::Path;
4
5use crate::error::RulesError;
6use crate::model::Rule;
7
8pub fn load_rule_file(path: impl AsRef<Path>) -> Result<Rule, RulesError> {
10 let path = path.as_ref();
11 let text = std::fs::read_to_string(path).map_err(|source| RulesError::Read {
12 path: path.display().to_string(),
13 source,
14 })?;
15 Rule::from_toml_str(&text).map_err(|source| RulesError::Parse {
16 path: path.display().to_string(),
17 source,
18 })
19}
20
21pub fn load_rule_dir(dir: impl AsRef<Path>) -> Result<Vec<Rule>, RulesError> {
24 let dir = dir.as_ref();
25 let mut entries: Vec<_> = std::fs::read_dir(dir)
26 .map_err(|source| RulesError::Read {
27 path: dir.display().to_string(),
28 source,
29 })?
30 .filter_map(Result::ok)
31 .map(|e| e.path())
32 .filter(|p| p.is_file() && p.extension().is_some_and(|ext| ext == "toml"))
33 .collect();
34 entries.sort();
35
36 entries.into_iter().map(load_rule_file).collect()
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42 use std::io::Write;
43
44 fn write(dir: &Path, name: &str, body: &str) {
45 let mut f = std::fs::File::create(dir.join(name)).unwrap();
46 f.write_all(body.as_bytes()).unwrap();
47 }
48
49 #[test]
50 fn loads_a_single_rule_file() {
51 let dir = tempfile::tempdir().unwrap();
52 write(
53 dir.path(),
54 "r.toml",
55 r#"
56 id = "r"
57 language = "rust"
58 [rule]
59 kind = "macro_invocation"
60 "#,
61 );
62 let rule = load_rule_file(dir.path().join("r.toml")).unwrap();
63 assert_eq!(rule.id, "r");
64 }
65
66 #[test]
67 fn loads_a_directory_in_sorted_order() {
68 let dir = tempfile::tempdir().unwrap();
69 write(
70 dir.path(),
71 "b.toml",
72 "id = \"b\"\nlanguage = \"rust\"\n[rule]\nkind = \"x\"\n",
73 );
74 write(
75 dir.path(),
76 "a.toml",
77 "id = \"a\"\nlanguage = \"rust\"\n[rule]\nkind = \"x\"\n",
78 );
79 write(dir.path(), "ignore.txt", "not a rule");
80 let rules = load_rule_dir(dir.path()).unwrap();
81 let ids: Vec<_> = rules.iter().map(|r| r.id.as_str()).collect();
82 assert_eq!(ids, vec!["a", "b"]);
83 }
84
85 #[test]
86 fn parse_error_names_the_file() {
87 let dir = tempfile::tempdir().unwrap();
88 write(dir.path(), "bad.toml", "id = \nthis is not toml");
89 let err = load_rule_file(dir.path().join("bad.toml")).unwrap_err();
90 assert!(matches!(err, RulesError::Parse { .. }));
91 }
92}