1use std::{
3 collections::{HashMap, HashSet},
4 path::{Path, PathBuf},
5};
6
7#[derive(Debug, Default, Clone)]
9pub struct IncludeHandler {
10 includes: HashSet<PathBuf>, directory_stack: Vec<PathBuf>, visited_dependencies: HashMap<PathBuf, usize>,
13 path_remapping: HashMap<PathBuf, PathBuf>, }
15
16pub fn canonicalize(p: &Path) -> std::io::Result<PathBuf> {
23 fn __canonicalize(path: &Path, buf: &mut PathBuf) {
25 if path.is_absolute() {
26 buf.clear();
27 }
28 for part in path {
29 if part == ".." {
30 buf.pop();
31 } else if part != "." {
32 buf.push(part);
33 if let Ok(linkpath) = buf.read_link() {
37 buf.pop();
38 __canonicalize(&linkpath, buf);
39 }
40 }
41 }
42 }
43 let mut path = if p.is_absolute() {
44 PathBuf::new()
45 } else {
46 PathBuf::from(std::env::current_dir()?)
47 };
48 __canonicalize(p, &mut path);
49 Ok(path)
50}
51
52impl IncludeHandler {
53 pub const DEPTH_LIMIT: usize = 30;
55
56 pub fn main_without_config(file: &Path) -> Self {
58 Self::main(file, Vec::new(), HashMap::new())
59 }
60 pub fn main(
62 file_path: &Path,
63 includes: Vec<PathBuf>,
64 path_remapping: HashMap<PathBuf, PathBuf>,
65 ) -> Self {
66 let cwd = file_path.parent().unwrap();
68 let mut directory_stack = Vec::new();
69 directory_stack.push(cwd.into());
70 let mut visited_dependencies = HashMap::new();
71 visited_dependencies.insert(file_path.into(), 1);
72 Self {
73 includes: includes.into_iter().collect(),
74 directory_stack: directory_stack,
75 visited_dependencies: visited_dependencies,
76 path_remapping: path_remapping,
77 }
78 }
79 pub fn get_includes(&self) -> &HashSet<PathBuf> {
81 &self.includes
82 }
83 pub fn get_visited_count(&self, path: &Path) -> usize {
85 self.visited_dependencies.get(path).cloned().unwrap_or(0)
86 }
87 pub fn search_in_includes(
89 &mut self,
90 relative_path: &Path,
91 include_callback: &mut dyn FnMut(&Path) -> Option<String>,
92 ) -> Option<(String, PathBuf)> {
93 match self.search_path_in_includes(relative_path) {
94 Some(absolute_path) => include_callback(&absolute_path).map(|e| (e, absolute_path)),
95 None => None,
96 }
97 }
98 pub fn push_directory_stack(&mut self, canonical_path: &Path) {
100 match self.visited_dependencies.get_mut(canonical_path) {
101 Some(visited_dependency_count) => *visited_dependency_count += 1,
102 None => {
103 self.visited_dependencies.insert(canonical_path.into(), 1);
104 if let Some(parent) = canonical_path.parent() {
105 if let Some(last) = self.directory_stack.last() {
107 if last != parent {
108 self.directory_stack.push(parent.into());
109 }
110 }
111 }
112 }
113 }
114 }
115 pub fn search_path_in_includes(&mut self, relative_path: &Path) -> Option<PathBuf> {
117 self.search_path_in_includes_relative(relative_path)
118 .map(|e| canonicalize(&e).unwrap())
119 }
120 fn search_path_in_includes_relative(&self, relative_path: &Path) -> Option<PathBuf> {
127 if relative_path.is_file() {
133 Some(PathBuf::from(relative_path))
134 } else {
135 for directory_stack in self.directory_stack.iter().rev() {
139 let path = directory_stack.join(&relative_path);
140 if path.is_file() {
141 return Some(path);
142 }
143 }
144 for include_path in &self.includes {
146 let path = include_path.join(&relative_path);
147 if path.is_file() {
148 return Some(path);
149 }
150 }
151 if let Some(target_path) =
153 Self::resolve_virtual_path(relative_path, &self.path_remapping)
154 {
155 if target_path.is_file() {
156 return Some(target_path);
157 }
158 }
159 return None;
160 }
161 }
162 fn resolve_virtual_path(
164 virtual_path: &Path,
165 virtual_folders: &HashMap<PathBuf, PathBuf>,
166 ) -> Option<PathBuf> {
167 let virtual_path = if virtual_path.starts_with("./") || virtual_path.starts_with(".\\") {
172 let mut comp = virtual_path.components();
173 comp.next();
174 Path::new("/").join(comp.as_path())
175 } else {
176 PathBuf::from(virtual_path)
177 };
178 for (virtual_folder, target_path) in virtual_folders {
180 let mut path_components = virtual_path.components();
181 let mut found = true;
182 for virtual_folder_component in virtual_folder.components() {
183 match path_components.next() {
184 Some(component) => {
185 if component != virtual_folder_component {
186 found = false;
187 break;
188 }
189 }
190 None => {
191 found = false;
192 break;
193 }
194 }
195 }
196 if found {
197 let resolved_path = target_path.join(path_components.as_path());
198 return Some(resolved_path.into());
199 }
200 }
201 None
202 }
203}