git.delta.rocks / jrsonnet / refs/commits / 5585b94101d8

difftreelog

fix ignore jpath when resolving filename passed to jrsonnet

rqvyxstzYaroslav Bolyukin2024-12-02parent: #772afa0.patch.diff
in: master

4 files changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -11,6 +11,7 @@
 	error::{Error as JrError, ErrorKind},
 	ResultExt, State, Val,
 };
+use jrsonnet_parser::{SourceDefaultIgnoreJpath, SourcePath};
 
 #[cfg(feature = "mimalloc")]
 #[global_allocator]
@@ -182,7 +183,7 @@
 		let input_str = std::str::from_utf8(&input)?;
 		s.evaluate_snippet("<stdin>".to_owned(), input_str)?
 	} else {
-		s.import(input.as_str())?
+		s.import_from(&SourcePath::new(SourceDefaultIgnoreJpath), input.as_str())?
 	};
 
 	let tla = opts.tla.tla_opts()?;
modifiedcrates/jrsonnet-evaluator/src/import.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/import.rs
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_parser::{IStr, SourceDirectory, SourceFifo, SourceFile, SourcePath};1415use crate::{16	bail,17	error::{ErrorKind::*, Result},18};19#[derive(Clone, Debug, Acyclic, Eq, Hash, PartialEq)]20pub enum ResolvePathOwned {21	Str(String),22	Path(PathBuf),23}24impl fmt::Display for ResolvePathOwned {25	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {26		match self {27			ResolvePathOwned::Str(s) => write!(f, "{s}"),28			ResolvePathOwned::Path(p) => write!(f, "{}", p.display()),29		}30	}31}32#[derive(Clone, Copy)]33pub enum ResolvePath<'s> {34	Str(&'s str),35	Path(&'s Path),36}37impl ResolvePath<'_> {38	pub fn to_owned(self) -> ResolvePathOwned {39		match self {40			ResolvePath::Str(s) => ResolvePathOwned::Str(s.to_owned()),41			ResolvePath::Path(p) => ResolvePathOwned::Path(p.to_owned()),42		}43	}44}45impl AsRef<Path> for ResolvePath<'_> {46	fn as_ref(&self) -> &Path {47		match self {48			ResolvePath::Str(s) => s.as_ref(),49			ResolvePath::Path(p) => p,50		}51	}52}53pub trait AsPathLike {54	fn as_path(&self) -> ResolvePath<'_>;55}56impl<T> AsPathLike for &T57where58	T: AsPathLike + ?Sized,59{60	fn as_path(&self) -> ResolvePath<'_> {61		(*self).as_path()62	}63}64impl AsPathLike for str {65	fn as_path(&self) -> ResolvePath<'_> {66		ResolvePath::Str(self)67	}68}69impl AsPathLike for IStr {70	fn as_path(&self) -> ResolvePath<'_> {71		ResolvePath::Str(self)72	}73}74impl AsPathLike for Cow<'_, Path> {75	fn as_path(&self) -> ResolvePath<'_> {76		ResolvePath::Path(self.as_ref())77	}78}79impl AsPathLike for Path {80	fn as_path(&self) -> ResolvePath<'_> {81		ResolvePath::Path(self)82	}83}84impl AsPathLike for ResolvePathOwned {85	fn as_path(&self) -> ResolvePath<'_> {86		match self {87			ResolvePathOwned::Str(s) => ResolvePath::Str(s),88			ResolvePathOwned::Path(path_buf) => ResolvePath::Path(path_buf),89		}90	}91}9293/// Implements file resolution logic for `import` and `importStr`94pub trait ImportResolver: Acyclic + Any {95	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond96	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`97	/// where `${vendor}` is a library path.98	///99	/// `from` should only be returned from [`ImportResolver::resolve`], or from other defined file, any other value100	/// may result in panic101	fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {102		bail!(ImportNotSupported(from.clone(), path.as_path().to_owned()))103	}104	fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {105		self.resolve_from(&SourcePath::default(), path)106	}107108	/// Load resolved file109	/// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],110	/// this cannot be resolved using associated type, as evaluator uses object instead of generic for [`ImportResolver`]111	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;112}113114/// Dummy resolver, can't resolve/load any file115#[derive(Acyclic)]116pub struct DummyImportResolver;117impl ImportResolver for DummyImportResolver {118	fn load_file_contents(&self, _resolved: &SourcePath) -> Result<Vec<u8>> {119		panic!("dummy resolver can't load any file")120	}121}122#[allow(clippy::use_self)]123impl Default for Box<dyn ImportResolver> {124	fn default() -> Self {125		Box::new(DummyImportResolver)126	}127}128129/// File resolver, can load file from both FS and library paths130#[derive(Default, Acyclic)]131pub struct FileImportResolver {132	/// Library directories to search for file.133	/// Referred to as `jpath` in original jsonnet implementation.134	library_paths: Vec<PathBuf>,135}136impl FileImportResolver {137	pub fn new(library_paths: Vec<PathBuf>) -> Self {138		Self { library_paths }139	}140	/// Dynamically add new jpath, used by bindings141	pub fn add_jpath(&mut self, path: PathBuf) {142		self.library_paths.push(path);143	}144}145146/// Create `SourcePath` from path, handling directories/Fifo files (on unix)/etc147fn check_path(path: &Path) -> Result<Option<SourcePath>> {148	let meta = match fs::metadata(path) {149		Ok(v) => v,150		Err(e) if e.kind() == ErrorKind::NotFound => {151			return Ok(None);152		}153		Err(e) => bail!(ImportIo(e.to_string())),154	};155	let ty = meta.file_type();156	if ty.is_file() {157		return Ok(Some(SourcePath::new(SourceFile::new(158			path.canonicalize().map_err(|e| ImportIo(e.to_string()))?,159		))));160	}161	let ty = meta.file_type();162	#[cfg(unix)]163	{164		use std::os::unix::fs::FileTypeExt;165		if ty.is_fifo() {166			let file = fs::read(path).map_err(|e| ImportIo(format!("FIFO read failed: {e}")))?;167			return Ok(Some(SourcePath::new(SourceFifo(168				format!("{}", path.display()),169				IBytes::from(file.as_slice()),170			))));171		}172	}173	// Block device/some other magic thing.174	Err(RuntimeError("special file can't be imported".into()).into())175}176177impl ImportResolver for FileImportResolver {178	fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {179		let path = path.as_path();180		let mut direct = if let Some(f) = from.downcast_ref::<SourceFile>() {181			let mut o = f.path().to_owned();182			o.pop();183			o184		} else if let Some(d) = from.downcast_ref::<SourceDirectory>() {185			d.path().to_owned()186		} else if from.is_default() {187			current_dir().map_err(|e| ImportIo(e.to_string()))?188		} else {189			unreachable!("resolver can't return this path")190		};191192		direct.push(path);193		if let Some(direct) = check_path(&direct)? {194			return Ok(direct);195		}196		for library_path in &self.library_paths {197			let mut cloned = library_path.clone();198			cloned.push(path);199			if let Some(cloned) = check_path(&cloned)? {200				return Ok(cloned);201			}202		}203		bail!(ImportFileNotFound(from.clone(), path.to_owned()))204	}205206	fn load_file_contents(&self, id: &SourcePath) -> Result<Vec<u8>> {207		let path = if let Some(f) = id.downcast_ref::<SourceFile>() {208			f.path()209		} else if id.downcast_ref::<SourceDirectory>().is_some() {210			bail!(ImportIsADirectory(id.clone()))211		} else if let Some(f) = id.downcast_ref::<SourceFifo>() {212			return Ok(f.1.to_vec());213		} else {214			unreachable!("other types are not supported in resolve");215		};216		let mut file = File::open(path).map_err(|_e| ResolvedFileNotFound(id.clone()))?;217		let mut out = Vec::new();218		file.read_to_end(&mut out)219			.map_err(|e| ImportIo(e.to_string()))?;220		Ok(out)221	}222223	fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {224		self.resolve_from(&SourcePath::default(), path)225	}226}
after · crates/jrsonnet-evaluator/src/import.rs
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_parser::{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}
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -12,7 +12,8 @@
 mod unescape;
 pub use location::CodeLocation;
 pub use source::{
-	Source, SourceDirectory, SourceFifo, SourceFile, SourcePath, SourcePathT, SourceVirtual,
+	Source, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath,
+	SourcePathT, SourceVirtual,
 };
 
 pub struct ParserSettings {
modifiedcrates/jrsonnet-parser/src/source.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/source.rs
+++ b/crates/jrsonnet-parser/src/source.rs
@@ -134,6 +134,23 @@
 	any_ext_impl!(SourcePathT);
 }
 
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
+pub struct SourceDefaultIgnoreJpath;
+impl Display for SourceDefaultIgnoreJpath {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "<default (ignoring jpath)>")
+	}
+}
+impl SourcePathT for SourceDefaultIgnoreJpath {
+	fn is_default(&self) -> bool {
+		true
+	}
+	fn path(&self) -> Option<&Path> {
+		None
+	}
+	any_ext_impl!(SourcePathT);
+}
+
 /// Represents path to the file on the disk
 /// Directories shouldn't be put here, as resolution for files differs from resolution for directories:
 ///