foundry_compilers_artifacts_solc/remappings/
mod.rsuse serde::{Deserialize, Serialize};
use std::{
    fmt,
    path::{Path, PathBuf},
    str::FromStr,
};
#[cfg(feature = "walkdir")]
mod find;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Remapping {
    pub context: Option<String>,
    pub name: String,
    pub path: String,
}
impl Remapping {
    pub fn into_relative(self, root: &Path) -> RelativeRemapping {
        RelativeRemapping::new(self, root)
    }
    pub fn strip_prefix(&mut self, base: &Path) -> &mut Self {
        if let Ok(stripped) = Path::new(&self.path).strip_prefix(base) {
            self.path = stripped.display().to_string();
        }
        self
    }
}
#[derive(Debug, PartialEq, Eq, PartialOrd, thiserror::Error)]
pub enum RemappingError {
    #[error("invalid remapping format, found `{0}`, expected `<key>=<value>`")]
    InvalidRemapping(String),
    #[error("remapping key can't be empty, found `{0}`, expected `<key>=<value>`")]
    EmptyRemappingKey(String),
    #[error("remapping value must be a path, found `{0}`, expected `<key>=<value>`")]
    EmptyRemappingValue(String),
}
impl FromStr for Remapping {
    type Err = RemappingError;
    fn from_str(remapping: &str) -> Result<Self, Self::Err> {
        let (name, path) = remapping
            .split_once('=')
            .ok_or_else(|| RemappingError::InvalidRemapping(remapping.to_string()))?;
        let (context, name) = name
            .split_once(':')
            .map_or((None, name), |(context, name)| (Some(context.to_string()), name));
        if name.trim().is_empty() {
            return Err(RemappingError::EmptyRemappingKey(remapping.to_string()));
        }
        if path.trim().is_empty() {
            return Err(RemappingError::EmptyRemappingValue(remapping.to_string()));
        }
        let context = context.filter(|c| !c.trim().is_empty());
        Ok(Self { context, name: name.to_string(), path: path.to_string() })
    }
}
impl Serialize for Remapping {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}
impl<'de> Deserialize<'de> for Remapping {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
        let remapping = String::deserialize(deserializer)?;
        Self::from_str(&remapping).map_err(serde::de::Error::custom)
    }
}
impl fmt::Display for Remapping {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut s = String::new();
        if let Some(context) = self.context.as_ref() {
            #[cfg(target_os = "windows")]
            {
                use path_slash::PathExt;
                s.push_str(&std::path::Path::new(context).to_slash_lossy());
            }
            #[cfg(not(target_os = "windows"))]
            {
                s.push_str(context);
            }
            s.push(':');
        }
        let name =
            if !self.name.ends_with('/') { format!("{}/", self.name) } else { self.name.clone() };
        s.push_str(&{
            #[cfg(target_os = "windows")]
            {
                use path_slash::PathExt;
                format!("{}={}", name, std::path::Path::new(&self.path).to_slash_lossy())
            }
            #[cfg(not(target_os = "windows"))]
            {
                format!("{}={}", name, self.path)
            }
        });
        if !s.ends_with('/') {
            s.push('/');
        }
        f.write_str(&s)
    }
}
impl Remapping {
    pub fn slash_path(&mut self) {
        #[cfg(windows)]
        {
            use path_slash::PathExt;
            self.path = Path::new(&self.path).to_slash_lossy().to_string();
            if let Some(context) = self.context.as_mut() {
                *context = Path::new(&context).to_slash_lossy().to_string();
            }
        }
    }
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RelativeRemapping {
    pub context: Option<String>,
    pub name: String,
    pub path: RelativeRemappingPathBuf,
}
impl RelativeRemapping {
    pub fn new(remapping: Remapping, root: &Path) -> Self {
        Self {
            context: remapping.context.map(|c| {
                RelativeRemappingPathBuf::with_root(root, c).path.to_string_lossy().to_string()
            }),
            name: remapping.name,
            path: RelativeRemappingPathBuf::with_root(root, remapping.path),
        }
    }
    pub fn to_remapping(mut self, root: PathBuf) -> Remapping {
        self.path.parent = Some(root);
        self.into()
    }
    pub fn to_relative_remapping(mut self) -> Remapping {
        self.path.parent.take();
        self.into()
    }
}
impl fmt::Display for RelativeRemapping {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut s = String::new();
        if let Some(context) = self.context.as_ref() {
            #[cfg(target_os = "windows")]
            {
                use path_slash::PathExt;
                s.push_str(&std::path::Path::new(context).to_slash_lossy());
            }
            #[cfg(not(target_os = "windows"))]
            {
                s.push_str(context);
            }
            s.push(':');
        }
        s.push_str(&{
            #[cfg(target_os = "windows")]
            {
                use path_slash::PathExt;
                format!("{}={}", self.name, self.path.original().to_slash_lossy())
            }
            #[cfg(not(target_os = "windows"))]
            {
                format!("{}={}", self.name, self.path.original().display())
            }
        });
        if !s.ends_with('/') {
            s.push('/');
        }
        f.write_str(&s)
    }
}
impl From<RelativeRemapping> for Remapping {
    fn from(r: RelativeRemapping) -> Self {
        let RelativeRemapping { context, mut name, path } = r;
        let mut path = path.relative().display().to_string();
        if !path.ends_with('/') {
            path.push('/');
        }
        if !name.ends_with('/') {
            name.push('/');
        }
        Self { context, name, path }
    }
}
impl From<Remapping> for RelativeRemapping {
    fn from(r: Remapping) -> Self {
        Self { context: r.context, name: r.name, path: r.path.into() }
    }
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RelativeRemappingPathBuf {
    pub parent: Option<PathBuf>,
    pub path: PathBuf,
}
impl RelativeRemappingPathBuf {
    pub fn with_root(
        parent: impl AsRef<Path> + Into<PathBuf>,
        path: impl AsRef<Path> + Into<PathBuf>,
    ) -> Self {
        if let Ok(path) = path.as_ref().strip_prefix(parent.as_ref()) {
            Self { parent: Some(parent.into()), path: path.to_path_buf() }
        } else if path.as_ref().has_root() {
            Self { parent: None, path: path.into() }
        } else {
            Self { parent: Some(parent.into()), path: path.into() }
        }
    }
    pub fn original(&self) -> &Path {
        &self.path
    }
    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<P: Into<PathBuf>> From<P> for RelativeRemappingPathBuf {
    fn from(path: P) -> Self {
        Self { parent: None, path: path.into() }
    }
}
impl Serialize for RelativeRemapping {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}
impl<'de> Deserialize<'de> for RelativeRemapping {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
        let remapping = String::deserialize(deserializer)?;
        let remapping = Remapping::from_str(&remapping).map_err(serde::de::Error::custom)?;
        Ok(Self { context: remapping.context, name: remapping.name, path: remapping.path.into() })
    }
}
#[cfg(test)]
mod tests {
    pub use super::*;
    pub use similar_asserts::assert_eq;
    #[test]
    fn relative_remapping() {
        let remapping = "oz=a/b/c/d";
        let remapping = Remapping::from_str(remapping).unwrap();
        let relative = RelativeRemapping::new(remapping.clone(), Path::new("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(), Path::new("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(), Path::new("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());
        let relative = RelativeRemapping::new(remapping, Path::new("/a/b"));
        assert_eq!(relative.to_relative_remapping(), Remapping::from_str("oz/=c/d/").unwrap());
    }
    #[test]
    fn remapping_errors() {
        let remapping = "oz=../b/c/d";
        let remapping = Remapping::from_str(remapping).unwrap();
        assert_eq!(remapping.name, "oz".to_string());
        assert_eq!(remapping.path, "../b/c/d".to_string());
        let err = Remapping::from_str("").unwrap_err();
        matches!(err, RemappingError::InvalidRemapping(_));
        let err = Remapping::from_str("oz=").unwrap_err();
        matches!(err, RemappingError::EmptyRemappingValue(_));
    }
    #[test]
    fn can_resolve_contexts() {
        let remapping = "context:oz=a/b/c/d";
        let remapping = Remapping::from_str(remapping).unwrap();
        assert_eq!(
            remapping,
            Remapping {
                context: Some("context".to_string()),
                name: "oz".to_string(),
                path: "a/b/c/d".to_string(),
            }
        );
        assert_eq!(remapping.to_string(), "context:oz/=a/b/c/d/".to_string());
        let remapping = "context:foo=C:/bar/src/";
        let remapping = Remapping::from_str(remapping).unwrap();
        assert_eq!(
            remapping,
            Remapping {
                context: Some("context".to_string()),
                name: "foo".to_string(),
                path: "C:/bar/src/".to_string()
            }
        );
    }
    #[test]
    fn can_resolve_global_contexts() {
        let remapping = ":oz=a/b/c/d/";
        let remapping = Remapping::from_str(remapping).unwrap();
        assert_eq!(
            remapping,
            Remapping { context: None, name: "oz".to_string(), path: "a/b/c/d/".to_string() }
        );
        assert_eq!(remapping.to_string(), "oz/=a/b/c/d/".to_string());
    }
}