1use std::{
2 collections::{HashMap, HashSet},
3 path::{Path, PathBuf},
4};
5
6#[derive(Debug, Default, Clone)]
7pub struct IncludeHandler {
8 includes: HashSet<PathBuf>, directory_stack: Vec<PathBuf>, visited_dependencies: HashMap<PathBuf, usize>,
11 path_remapping: HashMap<PathBuf, PathBuf>, }
13pub fn canonicalize(p: &Path) -> std::io::Result<PathBuf> {
18 fn __canonicalize(path: &Path, buf: &mut PathBuf) {
20 if path.is_absolute() {
21 buf.clear();
22 }
23 for part in path {
24 if part == ".." {
25 buf.pop();
26 } else if part != "." {
27 buf.push(part);
28 if let Ok(linkpath) = buf.read_link() {
32 buf.pop();
33 __canonicalize(&linkpath, buf);
34 }
35 }
36 }
37 }
38 let mut path = if p.is_absolute() {
39 PathBuf::new()
40 } else {
41 PathBuf::from(std::env::current_dir()?)
42 };
43 __canonicalize(p, &mut path);
44 Ok(path)
45}
46
47impl IncludeHandler {
48 pub const DEPTH_LIMIT: usize = 30;
49
50 pub fn main_without_config(file: &Path) -> Self {
51 Self::main(file, Vec::new(), HashMap::new())
52 }
53 pub fn main(
54 file_path: &Path,
55 includes: Vec<String>,
56 path_remapping: HashMap<PathBuf, PathBuf>,
57 ) -> Self {
58 let cwd = file_path.parent().unwrap();
60 let mut directory_stack = Vec::new();
61 directory_stack.push(cwd.into());
62 let mut visited_dependencies = HashMap::new();
63 visited_dependencies.insert(file_path.into(), 1);
64 Self {
65 includes: includes
66 .into_iter()
67 .map(|s| canonicalize(Path::new(&s)).unwrap())
68 .collect(),
69 directory_stack: directory_stack,
70 visited_dependencies: visited_dependencies,
71 path_remapping: path_remapping,
72 }
73 }
74 pub fn get_includes(&self) -> &HashSet<PathBuf> {
75 &self.includes
76 }
77 pub fn get_visited_count(&self, path: &Path) -> usize {
78 self.visited_dependencies.get(path).cloned().unwrap_or(0)
79 }
80 pub fn search_in_includes(
81 &mut self,
82 relative_path: &Path,
83 include_callback: &mut dyn FnMut(&Path) -> Option<String>,
84 ) -> Option<(String, PathBuf)> {
85 match self.search_path_in_includes(relative_path) {
86 Some(absolute_path) => include_callback(&absolute_path).map(|e| (e, absolute_path)),
87 None => None,
88 }
89 }
90 pub fn push_directory_stack(&mut self, canonical_path: &Path) {
91 match self.visited_dependencies.get_mut(canonical_path) {
92 Some(visited_dependency_count) => *visited_dependency_count += 1,
93 None => {
94 self.visited_dependencies.insert(canonical_path.into(), 1);
95 if let Some(parent) = canonical_path.parent() {
96 if let Some(last) = self.directory_stack.last() {
98 if last != parent {
99 self.directory_stack.push(parent.into());
100 }
101 }
102 }
103 }
104 }
105 }
106 pub fn search_path_in_includes(&mut self, relative_path: &Path) -> Option<PathBuf> {
107 self.search_path_in_includes_relative(relative_path)
108 .map(|e| canonicalize(&e).unwrap())
109 }
110 fn search_path_in_includes_relative(&self, relative_path: &Path) -> Option<PathBuf> {
111 if relative_path.is_file() {
117 Some(PathBuf::from(relative_path))
118 } else {
119 for directory_stack in self.directory_stack.iter().rev() {
123 let path = directory_stack.join(&relative_path);
124 if path.is_file() {
125 return Some(path);
126 }
127 }
128 for include_path in &self.includes {
130 let path = include_path.join(&relative_path);
131 if path.is_file() {
132 return Some(path);
133 }
134 }
135 if let Some(target_path) =
137 Self::resolve_virtual_path(relative_path, &self.path_remapping)
138 {
139 if target_path.is_file() {
140 return Some(target_path);
141 }
142 }
143 return None;
144 }
145 }
146 fn resolve_virtual_path(
147 virtual_path: &Path,
148 virtual_folders: &HashMap<PathBuf, PathBuf>,
149 ) -> Option<PathBuf> {
150 let virtual_path = if virtual_path.starts_with("./") || virtual_path.starts_with(".\\") {
155 let mut comp = virtual_path.components();
156 comp.next();
157 Path::new("/").join(comp.as_path())
158 } else {
159 PathBuf::from(virtual_path)
160 };
161 for (virtual_folder, target_path) in virtual_folders {
163 let mut path_components = virtual_path.components();
164 let mut found = true;
165 for virtual_folder_component in virtual_folder.components() {
166 match path_components.next() {
167 Some(component) => {
168 if component != virtual_folder_component {
169 found = false;
170 break;
171 }
172 }
173 None => {
174 found = false;
175 break;
176 }
177 }
178 }
179 if found {
180 let resolved_path = target_path.join(path_components.as_path());
181 return Some(resolved_path.into());
182 }
183 }
184 None
185 }
186}