foundry_compilers/compile/output/
info.rs

1//! Commonly used identifiers for contracts in the compiled output.
2
3use std::{borrow::Cow, fmt, path::Path, str::FromStr};
4
5#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
6#[error("{0}")]
7pub struct ParseContractInfoError(String);
8
9/// Represents the common contract argument pattern for `<path>:<contractname>` where `<path>:` is
10/// optional.
11#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub struct ContractInfo {
13    /// Location of the contract
14    pub path: Option<String>,
15    /// Name of the contract
16    pub name: String,
17}
18
19// === impl ContractInfo ===
20
21impl ContractInfo {
22    /// Creates a new `ContractInfo` from the `info` str.
23    ///
24    /// This will attempt `ContractInfo::from_str`, if `info` matches the `<path>:<name>` format,
25    /// the `ContractInfo`'s `path` will be set.
26    ///
27    /// otherwise the `name` of the new object will be `info`.
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// use foundry_compilers::info::ContractInfo;
33    ///
34    /// let info = ContractInfo::new("src/Greeter.sol:Greeter");
35    /// assert_eq!(
36    ///     info,
37    ///     ContractInfo { path: Some("src/Greeter.sol".to_string()), name: "Greeter".to_string() }
38    /// );
39    /// ```
40    pub fn new(info: &str) -> Self {
41        info.parse().unwrap_or_else(|_| Self { path: None, name: info.to_string() })
42    }
43
44    /// Returns the path to the contract source file if provided.
45    pub fn path(&self) -> Option<&Path> {
46        self.path.as_deref().map(Path::new)
47    }
48}
49
50impl fmt::Display for ContractInfo {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        if let Some(path) = &self.path {
53            write!(f, "{path}:")?;
54        }
55        f.write_str(&self.name)
56    }
57}
58
59impl FromStr for ContractInfo {
60    type Err = ParseContractInfoError;
61
62    fn from_str(s: &str) -> Result<Self, Self::Err> {
63        let err = || {
64            ParseContractInfoError(
65                "contract source info format must be `<path>:<contractname>` or `<contractname>`"
66                    .to_string(),
67            )
68        };
69        let mut iter = s.rsplit(':');
70        let name = iter.next().ok_or_else(err)?.trim().to_string();
71        let path = iter.next().map(str::to_string);
72
73        if name.ends_with(".sol") || name.contains('/') {
74            return Err(err());
75        }
76
77        Ok(Self { path, name })
78    }
79}
80
81impl From<FullContractInfo> for ContractInfo {
82    fn from(info: FullContractInfo) -> Self {
83        let FullContractInfo { path, name } = info;
84        Self { path: Some(path), name }
85    }
86}
87
88/// The reference type for `ContractInfo`
89#[derive(Clone, Debug, PartialEq, Eq, Hash)]
90pub struct ContractInfoRef<'a> {
91    pub path: Option<Cow<'a, str>>,
92    pub name: Cow<'a, str>,
93}
94
95impl From<ContractInfo> for ContractInfoRef<'_> {
96    fn from(info: ContractInfo) -> Self {
97        ContractInfoRef { path: info.path.map(Into::into), name: info.name.into() }
98    }
99}
100
101impl<'a> From<&'a ContractInfo> for ContractInfoRef<'a> {
102    fn from(info: &'a ContractInfo) -> Self {
103        ContractInfoRef {
104            path: info.path.as_deref().map(Into::into),
105            name: info.name.as_str().into(),
106        }
107    }
108}
109impl From<FullContractInfo> for ContractInfoRef<'_> {
110    fn from(info: FullContractInfo) -> Self {
111        ContractInfoRef { path: Some(info.path.into()), name: info.name.into() }
112    }
113}
114
115impl<'a> From<&'a FullContractInfo> for ContractInfoRef<'a> {
116    fn from(info: &'a FullContractInfo) -> Self {
117        ContractInfoRef { path: Some(info.path.as_str().into()), name: info.name.as_str().into() }
118    }
119}
120
121/// Represents the common contract argument pattern `<path>:<contractname>`
122#[derive(Clone, Debug, PartialEq, Eq, Hash)]
123pub struct FullContractInfo {
124    /// Location of the contract
125    pub path: String,
126    /// Name of the contract
127    pub name: String,
128}
129
130impl fmt::Display for FullContractInfo {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        write!(f, "{}:{}", self.path, self.name)
133    }
134}
135
136impl FromStr for FullContractInfo {
137    type Err = ParseContractInfoError;
138
139    fn from_str(s: &str) -> Result<Self, Self::Err> {
140        let (path, name) = s.split_once(':').ok_or_else(|| {
141            ParseContractInfoError("Expected `<path>:<contractname>`, got `{s}`".to_string())
142        })?;
143        Ok(Self { path: path.to_string(), name: name.trim().to_string() })
144    }
145}
146
147impl TryFrom<ContractInfo> for FullContractInfo {
148    type Error = ParseContractInfoError;
149
150    fn try_from(value: ContractInfo) -> Result<Self, Self::Error> {
151        let ContractInfo { path, name } = value;
152        Ok(Self {
153            path: path.ok_or_else(|| {
154                ParseContractInfoError("path to contract must be present".to_string())
155            })?,
156            name,
157        })
158    }
159}