1use std::{2 any::Any,3 borrow::Cow,4 env::current_dir,5 fmt, fs,6 io::{ErrorKind, Read},7 path::{Path, PathBuf},8};910use fs::File;11use jrsonnet_gcmodule::Acyclic;12use jrsonnet_interner::IBytes;13use jrsonnet_ir::{14 IStr, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath,15};1617use crate::{18 bail,19 error::{ErrorKind::*, Result},20};21#[derive(Clone, Debug, Acyclic, Eq, Hash, PartialEq)]22pub enum ResolvePathOwned {23 Str(String),24 Path(PathBuf),25}26impl fmt::Display for ResolvePathOwned {27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {28 match self {29 ResolvePathOwned::Str(s) => write!(f, "{s}"),30 ResolvePathOwned::Path(p) => write!(f, "{}", p.display()),31 }32 }33}34#[derive(Clone, Copy)]35pub enum ResolvePath<'s> {36 Str(&'s str),37 Path(&'s Path),38}39impl ResolvePath<'_> {40 pub fn to_owned(self) -> ResolvePathOwned {41 match self {42 ResolvePath::Str(s) => ResolvePathOwned::Str(s.to_owned()),43 ResolvePath::Path(p) => ResolvePathOwned::Path(p.to_owned()),44 }45 }46}47impl AsRef<Path> for ResolvePath<'_> {48 fn as_ref(&self) -> &Path {49 match self {50 ResolvePath::Str(s) => s.as_ref(),51 ResolvePath::Path(p) => p,52 }53 }54}55pub trait AsPathLike {56 fn as_path(&self) -> ResolvePath<'_>;57}58impl<T> AsPathLike for &T59where60 T: AsPathLike + ?Sized,61{62 fn as_path(&self) -> ResolvePath<'_> {63 (*self).as_path()64 }65}66impl AsPathLike for str {67 fn as_path(&self) -> ResolvePath<'_> {68 ResolvePath::Str(self)69 }70}71impl AsPathLike for IStr {72 fn as_path(&self) -> ResolvePath<'_> {73 ResolvePath::Str(self)74 }75}76impl AsPathLike for Cow<'_, Path> {77 fn as_path(&self) -> ResolvePath<'_> {78 ResolvePath::Path(self.as_ref())79 }80}81impl AsPathLike for Path {82 fn as_path(&self) -> ResolvePath<'_> {83 ResolvePath::Path(self)84 }85}86impl AsPathLike for ResolvePathOwned {87 fn as_path(&self) -> ResolvePath<'_> {88 match self {89 ResolvePathOwned::Str(s) => ResolvePath::Str(s),90 ResolvePathOwned::Path(path_buf) => ResolvePath::Path(path_buf),91 }92 }93}949596pub trait ImportResolver: Acyclic + Any {97 98 99 100 101 102 103 fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {104 bail!(ImportNotSupported(from.clone(), path.as_path().to_owned()))105 }106 fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {107 self.resolve_from(&SourcePath::default(), path)108 }109110 111 112 113 fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;114}115116117#[derive(Acyclic)]118pub struct DummyImportResolver;119impl ImportResolver for DummyImportResolver {120 fn load_file_contents(&self, _resolved: &SourcePath) -> Result<Vec<u8>> {121 panic!("dummy resolver can't load any file")122 }123}124#[allow(clippy::use_self)]125impl Default for Box<dyn ImportResolver> {126 fn default() -> Self {127 Box::new(DummyImportResolver)128 }129}130131132#[derive(Default, Acyclic)]133pub struct FileImportResolver {134 135 136 library_paths: Vec<PathBuf>,137}138impl FileImportResolver {139 pub fn new(library_paths: Vec<PathBuf>) -> Self {140 Self { library_paths }141 }142 143 pub fn add_jpath(&mut self, path: PathBuf) {144 self.library_paths.push(path);145 }146}147148149fn check_path(path: &Path) -> Result<Option<SourcePath>> {150 let meta = match fs::metadata(path) {151 Ok(v) => v,152 Err(e) if e.kind() == ErrorKind::NotFound => {153 return Ok(None);154 }155 Err(e) => bail!(ImportIo(e.to_string())),156 };157 let ty = meta.file_type();158 if ty.is_file() {159 return Ok(Some(SourcePath::new(SourceFile::new(160 path.canonicalize().map_err(|e| ImportIo(e.to_string()))?,161 ))));162 }163 let ty = meta.file_type();164 #[cfg(unix)]165 {166 use std::os::unix::fs::FileTypeExt;167 if ty.is_fifo() {168 let file = fs::read(path).map_err(|e| ImportIo(format!("FIFO read failed: {e}")))?;169 return Ok(Some(SourcePath::new(SourceFifo(170 format!("{}", path.display()),171 IBytes::from(file.as_slice()),172 ))));173 }174 }175 176 Err(RuntimeError("special file can't be imported".into()).into())177}178179impl ImportResolver for FileImportResolver {180 fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {181 let path = path.as_path();182 let mut direct = if let Some(f) = from.downcast_ref::<SourceFile>() {183 let mut o = f.path().to_owned();184 o.pop();185 o186 } else if let Some(d) = from.downcast_ref::<SourceDirectory>() {187 d.path().to_owned()188 } else if from.downcast_ref::<SourceDefaultIgnoreJpath>().is_some() {189 let mut direct = current_dir().map_err(|e| ImportIo(e.to_string()))?;190 direct.push(path);191 if let Some(direct) = check_path(&direct)? {192 return Ok(direct);193 }194 bail!(ImportFileNotFound(from.clone(), path.to_owned()))195 } else if from.is_default() {196 current_dir().map_err(|e| ImportIo(e.to_string()))?197 } else {198 unreachable!("resolver can't return this path")199 };200201 direct.push(path);202 if let Some(direct) = check_path(&direct)? {203 return Ok(direct);204 }205 for library_path in &self.library_paths {206 let mut cloned = library_path.clone();207 cloned.push(path);208 if let Some(cloned) = check_path(&cloned)? {209 return Ok(cloned);210 }211 }212 bail!(ImportFileNotFound(from.clone(), path.to_owned()))213 }214215 fn load_file_contents(&self, id: &SourcePath) -> Result<Vec<u8>> {216 let path = if let Some(f) = id.downcast_ref::<SourceFile>() {217 f.path()218 } else if id.downcast_ref::<SourceDirectory>().is_some() {219 bail!(ImportIsADirectory(id.clone()))220 } else if let Some(f) = id.downcast_ref::<SourceFifo>() {221 return Ok(f.1.to_vec());222 } else {223 unreachable!("other types are not supported in resolve");224 };225 let mut file = File::open(path).map_err(|_e| ResolvedFileNotFound(id.clone()))?;226 let mut out = Vec::new();227 file.read_to_end(&mut out)228 .map_err(|e| ImportIo(e.to_string()))?;229 Ok(out)230 }231232 fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {233 self.resolve_from(&SourcePath::default(), path)234 }235}