solar_interface/source_map/
file_resolver.rs1use super::SourceFile;
6use crate::SourceMap;
7use itertools::Itertools;
8use normalize_path::NormalizePath;
9use std::{
10 borrow::Cow,
11 io,
12 path::{Path, PathBuf},
13 sync::Arc,
14};
15
16#[derive(Debug, thiserror::Error)]
18pub enum ResolveError {
19 #[error("couldn't read stdin: {0}")]
20 ReadStdin(#[source] io::Error),
21 #[error("couldn't read {0}: {1}")]
22 ReadFile(PathBuf, #[source] io::Error),
23 #[error("file {0} not found")]
24 NotFound(PathBuf),
25 #[error("multiple files match {}: {}", .0.display(), .1.iter().map(|f| f.name.display()).format(", "))]
26 MultipleMatches(PathBuf, Vec<Arc<SourceFile>>),
27}
28
29pub struct FileResolver<'a> {
30 source_map: &'a SourceMap,
31 import_paths: Vec<(Option<PathBuf>, PathBuf)>,
32}
33
34impl<'a> FileResolver<'a> {
35 pub fn new(source_map: &'a SourceMap) -> Self {
37 Self { source_map, import_paths: Vec::new() }
38 }
39
40 pub fn source_map(&self) -> &'a SourceMap {
42 self.source_map
43 }
44
45 pub fn add_import_path(&mut self, path: PathBuf) -> bool {
47 let entry = (None, path);
48 let new = !self.import_paths.contains(&entry);
49 if new {
50 self.import_paths.push(entry);
51 }
52 new
53 }
54
55 pub fn add_import_map(&mut self, map: PathBuf, path: PathBuf) {
57 let map = Some(map);
58 if let Some((_, e)) = self.import_paths.iter_mut().find(|(k, _)| *k == map) {
59 *e = path;
60 } else {
61 self.import_paths.push((map, path));
62 }
63 }
64
65 pub fn get_import_path(&self, import_no: usize) -> Option<&(Option<PathBuf>, PathBuf)> {
67 self.import_paths.get(import_no)
68 }
69
70 pub fn get_import_paths(&self) -> &[(Option<PathBuf>, PathBuf)] {
72 self.import_paths.as_slice()
73 }
74
75 pub fn get_import_map(&self, map: &Path) -> Option<&PathBuf> {
77 self.import_paths.iter().find(|(m, _)| m.as_deref() == Some(map)).map(|(_, pb)| pb)
78 }
79
80 #[instrument(level = "debug", skip_all, fields(path = %path.display()))]
82 pub fn resolve_file(
83 &self,
84 path: &Path,
85 parent: Option<&Path>,
86 ) -> Result<Arc<SourceFile>, ResolveError> {
87 if path.starts_with("./") || path.starts_with("../") {
92 if let Some(parent) = parent {
93 let base = parent.parent().unwrap_or(Path::new("."));
94 let path = base.join(path);
95 if let Some(file) = self.try_file(&path)? {
96 return Ok(file);
98 }
99 }
100
101 return Err(ResolveError::NotFound(path.into()));
102 }
103
104 if parent.is_none() {
105 if let Some(file) = self.try_file(path)? {
106 return Ok(file);
107 }
108 if path.is_absolute() {
109 return Err(ResolveError::NotFound(path.into()));
110 }
111 }
112
113 let original_path = path;
114 let path = self.remap_path(path);
115 let mut result = Vec::with_capacity(1);
116
117 for import in &self.import_paths {
119 if let (None, import_path) = import {
120 let path = import_path.join(&path);
121 if let Some(file) = self.try_file(&path)? {
122 result.push(file);
123 }
124 }
125 }
126
127 if !self.import_paths.iter().any(|(m, _)| m.is_none()) {
131 if let Some(file) = self.try_file(&path)? {
132 result.push(file);
133 }
134 }
135
136 match result.len() {
137 0 => Err(ResolveError::NotFound(original_path.into())),
138 1 => Ok(result.pop().unwrap()),
139 _ => Err(ResolveError::MultipleMatches(original_path.into(), result)),
140 }
141 }
142
143 #[instrument(level = "trace", skip_all, ret)]
145 pub fn remap_path<'b>(&self, path: &'b Path) -> Cow<'b, Path> {
146 let orig = path;
147 let mut remapped = Cow::Borrowed(path);
148 for import_path in &self.import_paths {
149 if let (Some(mapping), target) = import_path {
150 if let Ok(relpath) = orig.strip_prefix(mapping) {
151 remapped = Cow::Owned(target.join(relpath));
152 }
153 }
154 }
155 remapped
156 }
157
158 pub fn load_stdin(&self) -> Result<Arc<SourceFile>, ResolveError> {
160 self.source_map().load_stdin().map_err(ResolveError::ReadStdin)
161 }
162
163 #[instrument(level = "debug", skip_all)]
165 pub fn try_file(&self, path: &Path) -> Result<Option<Arc<SourceFile>>, ResolveError> {
166 let cache_path = path.normalize();
167 if let Ok(file) = self.source_map().load_file(&cache_path) {
168 trace!("loaded from cache");
169 return Ok(Some(file));
170 }
171
172 if let Ok(path) = crate::canonicalize(path) {
173 let mut path = path.as_path();
176 if let Ok(curdir) = std::env::current_dir() {
177 if let Ok(p) = path.strip_prefix(curdir) {
178 path = p;
179 }
180 }
181 trace!("canonicalized to {}", path.display());
182 return self
183 .source_map()
184 .load_file(path)
185 .map(Some)
186 .map_err(|e| ResolveError::ReadFile(path.into(), e));
187 }
188
189 trace!("not found");
190 Ok(None)
191 }
192}