difftreelog
feat custom SourcePath trait
in: master
For usage in new import resolvers, this change allows to use custom types within importers, allowing to allow imports from internet
2 files changed
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth11mod source;11mod source;12mod unescape;12mod unescape;13pub use location::CodeLocation;13pub use location::CodeLocation;14pub use source::{Source, SourcePath};14pub use source::{Source, SourceDirectory, SourceFile, SourcePath, SourcePathT, SourceVirtual};151516pub struct ParserSettings {16pub struct ParserSettings {17 pub file_name: Source,17 pub file_name: Source,crates/jrsonnet-parser/src/source.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/source.rs
+++ b/crates/jrsonnet-parser/src/source.rs
@@ -1,7 +1,8 @@
use std::{
- borrow::Cow,
- fmt,
- path::{Component, Path, PathBuf},
+ any::Any,
+ fmt::{self, Debug, Display},
+ hash::{Hash, Hasher},
+ path::{Path, PathBuf},
rc::Rc,
};
@@ -14,31 +15,246 @@
use crate::location::{location_to_offset, offset_to_location, CodeLocation};
-#[cfg_attr(feature = "structdump", derive(Codegen))]
-#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(PartialEq, Eq, Debug, Hash, Clone)]
-pub enum SourcePath {
- /// This file is located on disk
- Path(PathBuf),
- /// This file is located somewhere else (I.e http), but it can refer to relative paths, and is egilible for caching
- Custom(String),
- /// This file is only located in memory, and can't be cached
- Virtual(Cow<'static, str>),
+macro_rules! any_ext_methods {
+ ($T:ident) => {
+ fn as_any(&self) -> &dyn Any;
+ fn dyn_hash(&self, hasher: &mut dyn Hasher);
+ fn dyn_eq(&self, other: &dyn $T) -> bool;
+ fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
+ };
+}
+macro_rules! any_ext_impl {
+ ($T:ident) => {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+ fn dyn_hash(&self, mut hasher: &mut dyn Hasher) {
+ self.hash(&mut hasher)
+ }
+ fn dyn_eq(&self, other: &dyn $T) -> bool {
+ let other = if let Some(v) = other.as_any().downcast_ref::<Self>() {
+ v
+ } else {
+ return false;
+ };
+ let this = <Self as $T>::as_any(self)
+ .downcast_ref::<Self>()
+ .expect("restricted by impl");
+ this == other
+ }
+ fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ <Self as std::fmt::Debug>::fmt(self, fmt)
+ }
+ };
+}
+macro_rules! any_ext {
+ ($T:ident) => {
+ impl Hash for dyn $T {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.dyn_hash(state)
+ }
+ }
+ impl PartialEq for dyn $T {
+ fn eq(&self, other: &Self) -> bool {
+ self.dyn_eq(other)
+ }
+ }
+ impl Eq for dyn $T {}
+ };
+}
+pub trait SourcePathT: Trace + Debug + Display {
+ /// This method should be checked by resolver before panicking with bad SourcePath input
+ /// if `true` - then resolver may threat this path as default, and default is usally a CWD
+ fn is_default(&self) -> bool;
+ fn path(&self) -> Option<&Path>;
+ any_ext_methods!(SourcePathT);
}
+any_ext!(SourcePathT);
+
+/// Represents location of a file
+///
+/// Standard CLI only operates using
+/// - [`SourceFile`] - for any file
+/// - [`SourceDirectory`] - for resolution from CWD
+/// - [`SourceVirtual`] - for stdlib/ext-str
+///
+/// From all of those, only [`SourceVirtual`] may be constructed manually, any other path kind should be only obtained
+/// from assigned `ImportResolver`
+/// However, you should always check `is_default` method return, as it will return true for any paths, where default
+/// search location is applicable
+///
+/// Resolver may also return custom implementations of this trait, for example it may return http url in case of remotely loaded files
+#[derive(Eq, Debug, Clone)]
+pub struct SourcePath(Rc<dyn SourcePathT>);
+impl SourcePath {
+ pub fn new(inner: impl SourcePathT) -> Self {
+ Self(Rc::new(inner))
+ }
+ pub fn downcast_ref<T: SourcePathT>(&self) -> Option<&T> {
+ self.0.as_any().downcast_ref()
+ }
+ pub fn is_default(&self) -> bool {
+ self.0.is_default()
+ }
+ pub fn path(&self) -> Option<&Path> {
+ self.0.path()
+ }
+}
+impl Hash for SourcePath {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.0.hash(state);
+ }
+}
+impl PartialEq for SourcePath {
+ #[allow(clippy::op_ref)]
+ fn eq(&self, other: &Self) -> bool {
+ &*self.0 == &*other.0
+ }
+}
impl Trace for SourcePath {
- fn trace(&self, _tracer: &mut Tracer) {}
+ fn trace(&self, tracer: &mut Tracer) {
+ (*self.0).trace(tracer)
+ }
+
+ fn is_type_tracked() -> bool
+ where
+ Self: Sized,
+ {
+ true
+ }
+}
+impl Display for SourcePath {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+impl Default for SourcePath {
+ fn default() -> Self {
+ Self(Rc::new(SourceDefault))
+ }
+}
+
+#[cfg(feature = "structdump")]
+impl Codegen for SourcePath {
+ fn gen_code(
+ &self,
+ res: &mut structdump::CodegenResult,
+ unique: bool,
+ ) -> structdump::TokenStream {
+ let source_virtual = self
+ .0
+ .as_any()
+ .downcast_ref::<SourceVirtual>()
+ .expect("can only codegen for virtual source paths!")
+ .0
+ .clone();
+ let val = res.add_value(source_virtual, false);
+ res.add_code(
+ structdump::quote! {
+ structdump_import::SourcePath::new(structdump_import::SourceVirtual(#val))
+ },
+ Some(structdump::quote!(SourcePath)),
+ unique,
+ )
+ }
+}
+
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+struct SourceDefault;
+impl Display for SourceDefault {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "<default>")
+ }
+}
+impl SourcePathT for SourceDefault {
+ fn is_default(&self) -> bool {
+ true
+ }
+ fn path(&self) -> Option<&Path> {
+ None
+ }
+ any_ext_impl!(SourcePathT);
+}
- fn is_type_tracked() -> bool {
+/// Represents path to the file on the disk
+/// Directories shouldn't be put here, as resolution for files differs from resolution for directories:
+///
+/// When `file` is being resolved from `SourceFile(a/b/c)`, it should be resolved to `SourceFile(a/b/file)`,
+/// however if it is being resolved from `SourceDirectory(a/b/c)`, then it should be resolved to `SourceDirectory(a/b/c/file)`
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+pub struct SourceFile(PathBuf);
+impl SourceFile {
+ pub fn new(path: PathBuf) -> Self {
+ Self(path)
+ }
+ pub fn path(&self) -> &Path {
+ &self.0
+ }
+}
+impl Display for SourceFile {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0.display())
+ }
+}
+impl SourcePathT for SourceFile {
+ fn is_default(&self) -> bool {
false
}
+ fn path(&self) -> Option<&Path> {
+ Some(&self.0)
+ }
+ any_ext_impl!(SourcePathT);
}
-impl SourcePath {
- /// Should import resolver be able to read file by this path?
- pub fn can_load(&self) -> bool {
- matches!(self, Self::Path(_) | Self::Custom(_))
+/// Represents path to the directory on the disk
+///
+/// See also [`SourceFile`]
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+pub struct SourceDirectory(PathBuf);
+impl SourceDirectory {
+ pub fn new(path: PathBuf) -> Self {
+ Self(path)
+ }
+ pub fn path(&self) -> &Path {
+ &self.0
}
}
+impl Display for SourceDirectory {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0.display())
+ }
+}
+impl SourcePathT for SourceDirectory {
+ fn is_default(&self) -> bool {
+ false
+ }
+ fn path(&self) -> Option<&Path> {
+ Some(&self.0)
+ }
+ any_ext_impl!(SourcePathT);
+}
+
+/// Represents virtual file, whose are located in memory, and shouldn't be cached
+///
+/// It is used for --ext-code=.../--tla-code=.../standard library source code by default,
+/// and user can construct arbitrary values by hand, without asking import resolver
+#[cfg_attr(feature = "structdump", derive(Codegen))]
+#[derive(Trace, Hash, PartialEq, Eq, Debug, Clone)]
+pub struct SourceVirtual(pub IStr);
+impl Display for SourceVirtual {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+impl SourcePathT for SourceVirtual {
+ fn is_default(&self) -> bool {
+ true
+ }
+ fn path(&self) -> Option<&Path> {
+ None
+ }
+ any_ext_impl!(SourcePathT);
+}
/// Either real file, or virtual
/// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut
@@ -57,42 +273,20 @@
}
impl Source {
- /// Fails when path contains inner /../ or /./ references, or not absolute
- pub fn new(path: SourcePath, code: IStr) -> Option<Self> {
- if let SourcePath::Path(path) = &path {
- if !path.is_absolute()
- || path
- .components()
- .any(|c| matches!(c, Component::CurDir | Component::ParentDir))
- {
- return None;
- }
- }
- Some(Self(Rc::new((path, code))))
+ pub fn new(path: SourcePath, code: IStr) -> Self {
+ Self(Rc::new((path, code)))
}
- pub fn new_virtual(n: Cow<'static, str>, code: IStr) -> Self {
- Self(Rc::new((SourcePath::Virtual(n), code)))
- }
-
- pub fn short_display(&self) -> ShortDisplay {
- ShortDisplay(self.clone())
+ pub fn new_virtual(name: IStr, code: IStr) -> Self {
+ Self::new(SourcePath::new(SourceVirtual(name)), code)
}
- /// Returns Some if this file is loaded from FS
- pub fn path(&self) -> Option<&Path> {
- match self.source_path() {
- SourcePath::Path(r) => Some(r),
- SourcePath::Custom(_) => None,
- SourcePath::Virtual(_) => None,
- }
- }
pub fn code(&self) -> &str {
&self.0 .1
}
pub fn source_path(&self) -> &SourcePath {
- &self.0 .0 as &SourcePath
+ &self.0 .0
}
pub fn map_source_locations(&self, locs: &[u32]) -> Vec<CodeLocation> {
@@ -100,21 +294,5 @@
}
pub fn map_from_source_location(&self, line: usize, column: usize) -> Option<usize> {
location_to_offset(&self.0 .1, line, column)
- }
-}
-pub struct ShortDisplay(Source);
-impl fmt::Display for ShortDisplay {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match &self.0 .0 .0 as &SourcePath {
- SourcePath::Path(r) => {
- write!(
- f,
- "{}",
- r.file_name().expect("path is valid").to_string_lossy()
- )
- }
- SourcePath::Custom(r) => write!(f, "{}", r),
- SourcePath::Virtual(n) => write!(f, "{}", n),
- }
}
}