git.delta.rocks / jrsonnet / refs/commits / 5f620e2b9aa7

difftreelog

source

crates/jrsonnet-evaluator/src/import.rs4.8 KiBsourcehistory
1use std::{2	any::Any,3	cell::RefCell,4	env::current_dir,5	fs,6	io::{ErrorKind, Read},7	path::{Path, PathBuf},8};910use fs::File;11use jrsonnet_gcmodule::Trace;12use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath};1314use crate::{15	error::{16		Error::{self, *},17		Result,18	},19	throw,20};2122/// Implements file resolution logic for `import` and `importStr`23pub trait ImportResolver: Trace {24	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond25	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`26	/// where `${vendor}` is a library path.27	///28	/// `from` should only be returned from [`ImportResolver::resolve`], or from other defined file, any other value29	/// may result in panic30	fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {31		throw!(ImportNotSupported(from.clone(), path.into()))32	}33	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {34		self.resolve_from(&SourcePath::default(), path)35	}36	/// Resolves absolute path, doesn't supports jpath and other fancy things37	fn resolve(&self, path: &Path) -> Result<SourcePath> {38		throw!(AbsoluteImportNotSupported(path.to_owned()))39	}4041	/// Load resolved file42	/// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],43	/// this cannot be resolved using associated type, as evaluator uses object instead of generic for [`ImportResolver`]44	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;4546	/// For downcasts47	fn as_any(&self) -> &dyn Any;48}4950/// Dummy resolver, can't resolve/load any file51#[derive(Trace)]52pub struct DummyImportResolver;53impl ImportResolver for DummyImportResolver {54	fn load_file_contents(&self, _resolved: &SourcePath) -> Result<Vec<u8>> {55		panic!("dummy resolver can't load any file")56	}5758	fn as_any(&self) -> &dyn Any {59		self60	}61}62#[allow(clippy::use_self)]63impl Default for Box<dyn ImportResolver> {64	fn default() -> Self {65		Box::new(DummyImportResolver)66	}67}6869/// File resolver, can load file from both FS and library paths70#[derive(Default, Trace)]71pub struct FileImportResolver {72	/// Library directories to search for file.73	/// Referred to as `jpath` in original jsonnet implementation.74	library_paths: RefCell<Vec<PathBuf>>,75}76impl FileImportResolver {77	pub fn new(jpath: Vec<PathBuf>) -> Self {78		Self {79			library_paths: RefCell::new(jpath),80		}81	}82	/// Dynamically add new jpath, used by bindings83	pub fn add_jpath(&self, path: PathBuf) {84		self.library_paths.borrow_mut().push(path);85	}86}8788impl ImportResolver for FileImportResolver {89	fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {90		let mut direct = if let Some(f) = from.downcast_ref::<SourceFile>() {91			let mut o = f.path().to_owned();92			o.pop();93			o94		} else if let Some(d) = from.downcast_ref::<SourceDirectory>() {95			d.path().to_owned()96		} else if from.is_default() {97			current_dir().map_err(|e| Error::ImportIo(e.to_string()))?98		} else {99			unreachable!("resolver can't return this path")100		};101		direct.push(path);102		if direct.is_file() {103			Ok(SourcePath::new(SourceFile::new(104				direct.canonicalize().map_err(|e| ImportIo(e.to_string()))?,105			)))106		} else {107			for library_path in self.library_paths.borrow().iter() {108				let mut cloned = library_path.clone();109				cloned.push(path);110				if cloned.exists() {111					return Ok(SourcePath::new(SourceFile::new(112						cloned.canonicalize().map_err(|e| ImportIo(e.to_string()))?,113					)));114				}115			}116			throw!(ImportFileNotFound(from.clone(), path.to_owned()))117		}118	}119	fn resolve(&self, path: &Path) -> Result<SourcePath> {120		let meta = match fs::metadata(path) {121			Ok(v) => v,122			Err(e) if e.kind() == ErrorKind::NotFound => {123				throw!(AbsoluteImportFileNotFound(path.to_owned()))124			}125			Err(e) => throw!(Error::ImportIo(e.to_string())),126		};127		if meta.is_file() {128			Ok(SourcePath::new(SourceFile::new(129				path.canonicalize().map_err(|e| ImportIo(e.to_string()))?,130			)))131		} else if meta.is_dir() {132			Ok(SourcePath::new(SourceDirectory::new(133				path.canonicalize().map_err(|e| ImportIo(e.to_string()))?,134			)))135		} else {136			unreachable!("this can't be a symlink")137		}138	}139140	fn load_file_contents(&self, id: &SourcePath) -> Result<Vec<u8>> {141		let path = if let Some(f) = id.downcast_ref::<SourceFile>() {142			f.path()143		} else if id.downcast_ref::<SourceDirectory>().is_some() || id.is_default() {144			throw!(Error::ImportIsADirectory(id.clone()))145		} else {146			unreachable!("other types are not supported in resolve");147		};148		let mut file = File::open(path).map_err(|_e| ResolvedFileNotFound(id.clone()))?;149		let mut out = Vec::new();150		file.read_to_end(&mut out)151			.map_err(|e| ImportIo(e.to_string()))?;152		Ok(out)153	}154155	fn as_any(&self) -> &dyn Any {156		self157	}158159	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {160		self.resolve_from(&SourcePath::default(), path)161	}162}