git.delta.rocks / jrsonnet / refs/commits / b89fdd32bd3a

difftreelog

source

crates/jrsonnet-evaluator/src/import.rs6.7 KiBsourcehistory
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}9394/// Implements file resolution logic for `import` and `importStr`95pub trait ImportResolver: Acyclic + Any {96	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond97	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`98	/// where `${vendor}` is a library path.99	///100	/// `from` should only be returned from [`ImportResolver::resolve`], or from other defined file, any other value101	/// may result in panic102	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	/// Load resolved file110	/// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],111	/// this cannot be resolved using associated type, as evaluator uses object instead of generic for [`ImportResolver`]112	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;113}114115/// Dummy resolver, can't resolve/load any file116#[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}129130/// File resolver, can load file from both FS and library paths131#[derive(Default, Acyclic)]132pub struct FileImportResolver {133	/// Library directories to search for file.134	/// Referred to as `jpath` in original jsonnet implementation.135	library_paths: Vec<PathBuf>,136}137impl FileImportResolver {138	pub fn new(library_paths: Vec<PathBuf>) -> Self {139		Self { library_paths }140	}141	/// Dynamically add new jpath, used by bindings142	pub fn add_jpath(&mut self, path: PathBuf) {143		self.library_paths.push(path);144	}145}146147/// Create `SourcePath` from path, handling directories/Fifo files (on unix)/etc148fn 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	// Block device/some other magic thing.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}