diff --git a/ethers-solc/src/remappings.rs b/ethers-solc/src/remappings.rs index 7d3f4a99..576d3fbd 100644 --- a/ethers-solc/src/remappings.rs +++ b/ethers-solc/src/remappings.rs @@ -190,6 +190,124 @@ impl Remapping { } } +/// A relative [`Remapping`] that's aware of the current location +/// +/// See [`RelativeRemappingPathBuf`] +#[derive(Clone, Debug, PartialEq)] +pub struct RelativeRemapping { + pub name: String, + pub path: RelativeRemappingPathBuf, +} + +impl RelativeRemapping { + /// Creates a new `RelativeRemapping` starting prefixed with `root` + pub fn new(remapping: Remapping, root: impl AsRef) -> Self { + Self { + name: remapping.name, + path: RelativeRemappingPathBuf::with_root(root, remapping.path), + } + } + + /// Converts this relative remapping into an absolute remapping + /// + /// This sets to root of the remapping to the given `root` path + pub fn to_remapping(mut self, root: PathBuf) -> Remapping { + self.path.parent = Some(root); + self.into() + } +} + +// Remappings are printed as `prefix=target` +impl fmt::Display for RelativeRemapping { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}={}", self.name, self.path.original().display()) + } +} + +impl From for Remapping { + fn from(r: RelativeRemapping) -> Self { + Remapping { name: r.name, path: r.path.relative().to_string_lossy().to_string() } + } +} + +impl From for RelativeRemapping { + fn from(r: Remapping) -> Self { + Self { name: r.name, path: r.path.into() } + } +} + +/// The path part of the [`Remapping`] that knows the path of the file it was configured in, if any. +/// +/// A [`Remapping`] is intended to be absolute, but paths in configuration files are often desired +/// to be relative to the configuration file itself. For example, a path of +/// `weird-erc20/=lib/weird-erc20/src/` configured in a file `/var/foundry.toml` might be desired to +/// resolve as a `weird-erc20/=/var/lib/weird-erc20/src/` remapping. +#[derive(Debug, Clone, PartialEq)] +pub struct RelativeRemappingPathBuf { + parent: Option, + path: PathBuf, +} + +impl RelativeRemappingPathBuf { + /// Creates a new `RelativeRemappingPathBuf` that checks if the `path` is a child path of + /// `parent`. + pub fn with_root(parent: impl AsRef, path: impl AsRef) -> Self { + let parent = parent.as_ref(); + let path = path.as_ref(); + if let Ok(path) = path.strip_prefix(parent) { + Self { parent: Some(parent.to_path_buf()), path: path.to_path_buf() } + } else if path.has_root() { + Self { parent: None, path: path.to_path_buf() } + } else { + Self { parent: Some(parent.to_path_buf()), path: path.to_path_buf() } + } + } + + /// Returns the path as it was declared, without modification. + pub fn original(&self) -> &Path { + &self.path + } + + /// Returns this path relative to the file it was delcared in, if any. + /// Returns the original if this path was not declared in a file or if the + /// path has a root. + pub fn relative(&self) -> PathBuf { + if self.original().has_root() { + return self.original().into() + } + self.parent + .as_ref() + .map(|p| p.join(self.original())) + .unwrap_or_else(|| self.original().into()) + } +} + +impl> From

for RelativeRemappingPathBuf { + fn from(path: P) -> RelativeRemappingPathBuf { + Self { parent: None, path: path.as_ref().to_path_buf() } + } +} + +impl Serialize for RelativeRemapping { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for RelativeRemapping { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::de::Deserializer<'de>, + { + let remapping = String::deserialize(deserializer)?; + let remapping = Remapping::from_str(&remapping).map_err(serde::de::Error::custom)?; + Ok(RelativeRemapping { name: remapping.name, path: remapping.path.into() }) + } +} + #[derive(Debug, Clone)] struct Candidate { /// dir that opened the window @@ -358,6 +476,27 @@ mod tests { use super::*; use crate::utils::tempdir; + #[test] + fn relative_remapping() { + let remapping = "oz=a/b/c/d"; + let remapping = Remapping::from_str(remapping).unwrap(); + + let relative = RelativeRemapping::new(remapping.clone(), "a/b/c"); + assert_eq!(relative.path.relative(), Path::new(&remapping.path)); + assert_eq!(relative.path.original(), Path::new("d")); + + let relative = RelativeRemapping::new(remapping.clone(), "x/y"); + assert_eq!(relative.path.relative(), Path::new("x/y/a/b/c/d")); + assert_eq!(relative.path.original(), Path::new(&remapping.path)); + + let remapping = "oz=/a/b/c/d"; + let remapping = Remapping::from_str(remapping).unwrap(); + let relative = RelativeRemapping::new(remapping.clone(), "a/b"); + assert_eq!(relative.path.relative(), Path::new(&remapping.path)); + assert_eq!(relative.path.original(), Path::new(&remapping.path)); + assert!(relative.path.parent.is_none()); + } + #[test] fn serde() { let remapping = "oz=../b/c/d";