solar_interface/source_map/
file_resolver.rsuse super::SourceFile;
use crate::SourceMap;
use itertools::Itertools;
use normalize_path::NormalizePath;
use std::{
borrow::Cow,
io,
path::{Path, PathBuf},
sync::Arc,
};
#[derive(Debug, thiserror::Error)]
pub enum ResolveError {
#[error("couldn't read stdin: {0}")]
ReadStdin(#[source] io::Error),
#[error("couldn't read {0}: {1}")]
ReadFile(PathBuf, #[source] io::Error),
#[error("file {0} not found")]
NotFound(PathBuf),
#[error("multiple files match {0}: {}", _1.iter().map(|f| f.name.display()).format(", "))]
MultipleMatches(PathBuf, Vec<Arc<SourceFile>>),
}
pub struct FileResolver<'a> {
source_map: &'a SourceMap,
import_paths: Vec<(Option<PathBuf>, PathBuf)>,
}
impl<'a> FileResolver<'a> {
pub fn new(source_map: &'a SourceMap) -> Self {
Self { source_map, import_paths: Vec::new() }
}
pub fn source_map(&self) -> &'a SourceMap {
self.source_map
}
pub fn add_import_path(&mut self, path: PathBuf) -> bool {
let entry = (None, path);
let new = !self.import_paths.contains(&entry);
if new {
self.import_paths.push(entry);
}
new
}
pub fn add_import_map(&mut self, map: PathBuf, path: PathBuf) {
let map = Some(map);
if let Some((_, e)) = self.import_paths.iter_mut().find(|(k, _)| *k == map) {
*e = path;
} else {
self.import_paths.push((map, path));
}
}
pub fn get_import_path(&self, import_no: usize) -> Option<&(Option<PathBuf>, PathBuf)> {
self.import_paths.get(import_no)
}
pub fn get_import_paths(&self) -> &[(Option<PathBuf>, PathBuf)] {
self.import_paths.as_slice()
}
pub fn get_import_map(&self, map: &Path) -> Option<&PathBuf> {
self.import_paths.iter().find(|(m, _)| m.as_deref() == Some(map)).map(|(_, pb)| pb)
}
#[instrument(level = "debug", skip_all, fields(path = %path.display()))]
pub fn resolve_file(
&self,
path: &Path,
parent: Option<&Path>,
) -> Result<Arc<SourceFile>, ResolveError> {
if path.starts_with("./") || path.starts_with("../") {
if let Some(parent) = parent {
let base = parent.parent().unwrap_or(Path::new("."));
let path = base.join(path);
if let Some(file) = self.try_file(&path)? {
return Ok(file);
}
}
return Err(ResolveError::NotFound(path.into()));
}
if parent.is_none() {
if let Some(file) = self.try_file(path)? {
return Ok(file);
}
if path.is_absolute() {
return Err(ResolveError::NotFound(path.into()));
}
}
let original_path = path;
let path = self.remap_path(path);
let mut result = Vec::with_capacity(1);
for import in &self.import_paths {
if let (None, import_path) = import {
let path = import_path.join(&path);
if let Some(file) = self.try_file(&path)? {
result.push(file);
}
}
}
if !self.import_paths.iter().any(|(m, _)| m.is_none()) {
if let Some(file) = self.try_file(&path)? {
result.push(file);
}
}
match result.len() {
0 => Err(ResolveError::NotFound(original_path.into())),
1 => Ok(result.pop().unwrap()),
_ => Err(ResolveError::MultipleMatches(original_path.into(), result)),
}
}
#[instrument(level = "trace", skip_all, ret)]
pub fn remap_path<'b>(&self, path: &'b Path) -> Cow<'b, Path> {
let orig = path;
let mut remapped = Cow::Borrowed(path);
for import_path in &self.import_paths {
if let (Some(mapping), target) = import_path {
if let Ok(relpath) = orig.strip_prefix(mapping) {
remapped = Cow::Owned(target.join(relpath));
}
}
}
remapped
}
pub fn load_stdin(&self) -> Result<Arc<SourceFile>, ResolveError> {
self.source_map().load_stdin().map_err(ResolveError::ReadStdin)
}
#[instrument(level = "debug", skip_all)]
pub fn try_file(&self, path: &Path) -> Result<Option<Arc<SourceFile>>, ResolveError> {
let cache_path = path.normalize();
if let Ok(file) = self.source_map().load_file(&cache_path) {
trace!("loaded from cache");
return Ok(Some(file));
}
if let Ok(path) = crate::canonicalize(path) {
let mut path = path.as_path();
if let Ok(curdir) = std::env::current_dir() {
if let Ok(p) = path.strip_prefix(curdir) {
path = p;
}
}
trace!("canonicalized to {}", path.display());
return self
.source_map()
.load_file(path)
.map(Some)
.map_err(|e| ResolveError::ReadFile(path.into(), e));
}
trace!("not found");
Ok(None)
}
}