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