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

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_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}9495/// Implements file resolution logic for `import` and `importStr`96pub trait ImportResolver: Acyclic + Any {97	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond98	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`99	/// where `${vendor}` is a library path.100	///101	/// `from` should only be returned from [`ImportResolver::resolve`], or from other defined file, any other value102	/// may result in panic103	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	/// Load resolved file111	/// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],112	/// this cannot be resolved using associated type, as evaluator uses object instead of generic for [`ImportResolver`]113	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;114}115116/// Dummy resolver, can't resolve/load any file117#[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}130131/// File resolver, can load file from both FS and library paths132#[derive(Default, Acyclic)]133pub struct FileImportResolver {134	/// Library directories to search for file.135	/// Referred to as `jpath` in original jsonnet implementation.136	library_paths: Vec<PathBuf>,137}138impl FileImportResolver {139	pub fn new(library_paths: Vec<PathBuf>) -> Self {140		Self { library_paths }141	}142	/// Dynamically add new jpath, used by bindings143	pub fn add_jpath(&mut self, path: PathBuf) {144		self.library_paths.push(path);145	}146}147148/// Create `SourcePath` from path, handling directories/Fifo files (on unix)/etc149fn 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	// Block device/some other magic thing.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}