1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use anyhow::Result;
use std::path::Path;
use walkdir::WalkDir;
pub struct DirectorySource {
path: String,
is_file: bool,
}
impl DirectorySource {
pub fn open(path: &str) -> Result<Self> {
let path_obj = Path::new(path);
if !path_obj.exists() {
return Err(anyhow::anyhow!("Path does not exist: {}", path));
}
let is_file = path_obj.is_file();
let is_dir = path_obj.is_dir();
if !is_file && !is_dir {
return Err(anyhow::anyhow!(
"Path is neither a file nor a directory: {}",
path
));
}
// If it's a file, verify the substrate can parse it (C: .c / .h).
if is_file {
let parseable = path_obj
.extension()
.map(lang_parsing_substrate::is_parseable_extension)
.unwrap_or(false);
if !parseable {
return Err(anyhow::anyhow!(
"File must have .c or .h extension: {}",
path
));
}
}
Ok(Self {
path: path.to_string(),
is_file,
})
}
pub fn get_c_files(&self) -> Result<Vec<String>> {
let mut c_files = Vec::new();
// If it's a single file, just return the path as given
if self.is_file {
c_files.push(self.path.clone());
return Ok(c_files);
}
// Otherwise, walk the directory
for entry in WalkDir::new(&self.path).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if let Some(extension) = path.extension() {
if lang_parsing_substrate::is_parseable_extension(extension) {
if let Some(path_str) = path.to_str() {
// Skip files in .git directory (in case there's a .git folder but not a valid repo)
if !path_str.contains("/.git/") {
// Normalize: strip leading "./" so paths are clean relative to cwd
let normalized = path_str.strip_prefix("./").unwrap_or(path_str);
c_files.push(normalized.to_string());
}
}
}
}
}
Ok(c_files)
}
pub fn get_modified_c_files(&self) -> Result<Vec<String>> {
// For non-git directories, return all C files as "modified"
// since we don't have version control information
self.get_c_files()
}
#[allow(dead_code)]
pub fn get_root_path(&self) -> &str {
&self.path
}
pub fn is_file(&self) -> bool {
self.is_file
}
/// Returns the directory to pre-scan for cross-file context.
///
/// For a single-file target, this is the file's parent directory so that
/// sibling headers are included in the prescan (enabling rules like DCL15-C
/// to recognise public API declared in those headers).
/// For a directory target, returns None (the caller already has the dir).
pub fn prescan_dir(&self) -> Option<String> {
if self.is_file {
let parent = std::path::Path::new(&self.path)
.parent()
.and_then(|p| p.to_str())
.map(|s| if s.is_empty() { "." } else { s });
parent.map(|s| s.to_string())
} else {
None
}
}
}