foundry_compilers_artifacts_solc/remappings/
mod.rs1use serde::{Deserialize, Serialize};
2use std::{
3 fmt,
4 path::{Path, PathBuf},
5 str::FromStr,
6};
7
8#[cfg(feature = "walkdir")]
9mod find;
10
11#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
52pub struct Remapping {
53 pub context: Option<String>,
54 pub name: String,
55 pub path: String,
56}
57
58impl Remapping {
59 pub fn into_relative(self, root: &Path) -> RelativeRemapping {
61 RelativeRemapping::new(self, root)
62 }
63
64 pub fn strip_prefix(&mut self, base: &Path) -> &mut Self {
66 if let Ok(stripped) = Path::new(&self.path).strip_prefix(base) {
67 self.path = stripped.display().to_string();
68 }
69 self
70 }
71}
72
73#[derive(Debug, PartialEq, Eq, PartialOrd, thiserror::Error)]
74pub enum RemappingError {
75 #[error("invalid remapping format, found `{0}`, expected `<key>=<value>`")]
76 InvalidRemapping(String),
77 #[error("remapping key can't be empty, found `{0}`, expected `<key>=<value>`")]
78 EmptyRemappingKey(String),
79 #[error("remapping value must be a path, found `{0}`, expected `<key>=<value>`")]
80 EmptyRemappingValue(String),
81}
82
83impl FromStr for Remapping {
84 type Err = RemappingError;
85
86 fn from_str(remapping: &str) -> Result<Self, Self::Err> {
87 let (name, path) = remapping
88 .split_once('=')
89 .ok_or_else(|| RemappingError::InvalidRemapping(remapping.to_string()))?;
90 let (context, name) = name
91 .split_once(':')
92 .map_or((None, name), |(context, name)| (Some(context.to_string()), name));
93 if name.trim().is_empty() {
94 return Err(RemappingError::EmptyRemappingKey(remapping.to_string()));
95 }
96 if path.trim().is_empty() {
97 return Err(RemappingError::EmptyRemappingValue(remapping.to_string()));
98 }
99 let context = context.filter(|c| !c.trim().is_empty());
101 Ok(Self { context, name: name.to_string(), path: path.to_string() })
102 }
103}
104
105impl Serialize for Remapping {
106 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
107 where
108 S: serde::ser::Serializer,
109 {
110 serializer.serialize_str(&self.to_string())
111 }
112}
113
114impl<'de> Deserialize<'de> for Remapping {
115 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
116 where
117 D: serde::de::Deserializer<'de>,
118 {
119 let remapping = String::deserialize(deserializer)?;
120 Self::from_str(&remapping).map_err(serde::de::Error::custom)
121 }
122}
123
124impl fmt::Display for Remapping {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 let mut s = String::new();
128 if let Some(context) = self.context.as_ref() {
129 #[cfg(target_os = "windows")]
130 {
131 use path_slash::PathExt;
133 s.push_str(&std::path::Path::new(context).to_slash_lossy());
134 }
135 #[cfg(not(target_os = "windows"))]
136 {
137 s.push_str(context);
138 }
139 s.push(':');
140 }
141 let name =
142 if !self.name.ends_with('/') { format!("{}/", self.name) } else { self.name.clone() };
143 s.push_str(&{
144 #[cfg(target_os = "windows")]
145 {
146 use path_slash::PathExt;
148 format!("{}={}", name, std::path::Path::new(&self.path).to_slash_lossy())
149 }
150 #[cfg(not(target_os = "windows"))]
151 {
152 format!("{}={}", name, self.path)
153 }
154 });
155
156 if !s.ends_with('/') {
157 s.push('/');
158 }
159 f.write_str(&s)
160 }
161}
162
163impl Remapping {
164 pub fn slash_path(&mut self) {
166 #[cfg(windows)]
167 {
168 use path_slash::PathExt;
169 self.path = Path::new(&self.path).to_slash_lossy().to_string();
170 if let Some(context) = self.context.as_mut() {
171 *context = Path::new(&context).to_slash_lossy().to_string();
172 }
173 }
174 }
175}
176
177#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
181pub struct RelativeRemapping {
182 pub context: Option<String>,
183 pub name: String,
184 pub path: RelativeRemappingPathBuf,
185}
186
187impl RelativeRemapping {
188 pub fn new(remapping: Remapping, root: &Path) -> Self {
190 Self {
191 context: remapping.context.map(|c| {
192 RelativeRemappingPathBuf::with_root(root, c).path.to_string_lossy().to_string()
193 }),
194 name: remapping.name,
195 path: RelativeRemappingPathBuf::with_root(root, remapping.path),
196 }
197 }
198
199 pub fn to_remapping(mut self, root: PathBuf) -> Remapping {
203 self.path.parent = Some(root);
204 self.into()
205 }
206
207 pub fn to_relative_remapping(mut self) -> Remapping {
209 self.path.parent.take();
210 self.into()
211 }
212}
213
214impl fmt::Display for RelativeRemapping {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 let mut s = String::new();
218 if let Some(context) = self.context.as_ref() {
219 #[cfg(target_os = "windows")]
220 {
221 use path_slash::PathExt;
223 s.push_str(&std::path::Path::new(context).to_slash_lossy());
224 }
225 #[cfg(not(target_os = "windows"))]
226 {
227 s.push_str(context);
228 }
229 s.push(':');
230 }
231 s.push_str(&{
232 #[cfg(target_os = "windows")]
233 {
234 use path_slash::PathExt;
236 format!("{}={}", self.name, self.path.original().to_slash_lossy())
237 }
238 #[cfg(not(target_os = "windows"))]
239 {
240 format!("{}={}", self.name, self.path.original().display())
241 }
242 });
243
244 if !s.ends_with('/') {
245 s.push('/');
246 }
247 f.write_str(&s)
248 }
249}
250
251impl From<RelativeRemapping> for Remapping {
252 fn from(r: RelativeRemapping) -> Self {
253 let RelativeRemapping { context, mut name, path } = r;
254 let mut path = path.relative().display().to_string();
255 if !path.ends_with('/') {
256 path.push('/');
257 }
258 if !name.ends_with('/') {
259 name.push('/');
260 }
261 Self { context, name, path }
262 }
263}
264
265impl From<Remapping> for RelativeRemapping {
266 fn from(r: Remapping) -> Self {
267 Self { context: r.context, name: r.name, path: r.path.into() }
268 }
269}
270
271#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
278pub struct RelativeRemappingPathBuf {
279 pub parent: Option<PathBuf>,
280 pub path: PathBuf,
281}
282
283impl RelativeRemappingPathBuf {
284 pub fn with_root(
287 parent: impl AsRef<Path> + Into<PathBuf>,
288 path: impl AsRef<Path> + Into<PathBuf>,
289 ) -> Self {
290 if let Ok(path) = path.as_ref().strip_prefix(parent.as_ref()) {
291 Self { parent: Some(parent.into()), path: path.to_path_buf() }
292 } else if path.as_ref().has_root() {
293 Self { parent: None, path: path.into() }
294 } else {
295 Self { parent: Some(parent.into()), path: path.into() }
296 }
297 }
298
299 pub fn original(&self) -> &Path {
301 &self.path
302 }
303
304 pub fn relative(&self) -> PathBuf {
308 if self.original().has_root() {
309 return self.original().into();
310 }
311 self.parent
312 .as_ref()
313 .map(|p| p.join(self.original()))
314 .unwrap_or_else(|| self.original().into())
315 }
316}
317
318impl<P: Into<PathBuf>> From<P> for RelativeRemappingPathBuf {
319 fn from(path: P) -> Self {
320 Self { parent: None, path: path.into() }
321 }
322}
323
324impl Serialize for RelativeRemapping {
325 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
326 where
327 S: serde::ser::Serializer,
328 {
329 serializer.serialize_str(&self.to_string())
330 }
331}
332
333impl<'de> Deserialize<'de> for RelativeRemapping {
334 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
335 where
336 D: serde::de::Deserializer<'de>,
337 {
338 let remapping = String::deserialize(deserializer)?;
339 let remapping = Remapping::from_str(&remapping).map_err(serde::de::Error::custom)?;
340 Ok(Self { context: remapping.context, name: remapping.name, path: remapping.path.into() })
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 pub use super::*;
347 pub use similar_asserts::assert_eq;
348
349 #[test]
350 fn relative_remapping() {
351 let remapping = "oz=a/b/c/d";
352 let remapping = Remapping::from_str(remapping).unwrap();
353
354 let relative = RelativeRemapping::new(remapping.clone(), Path::new("a/b/c"));
355 assert_eq!(relative.path.relative(), Path::new(&remapping.path));
356 assert_eq!(relative.path.original(), Path::new("d"));
357
358 let relative = RelativeRemapping::new(remapping.clone(), Path::new("x/y"));
359 assert_eq!(relative.path.relative(), Path::new("x/y/a/b/c/d"));
360 assert_eq!(relative.path.original(), Path::new(&remapping.path));
361
362 let remapping = "oz=/a/b/c/d";
363 let remapping = Remapping::from_str(remapping).unwrap();
364 let relative = RelativeRemapping::new(remapping.clone(), Path::new("a/b"));
365 assert_eq!(relative.path.relative(), Path::new(&remapping.path));
366 assert_eq!(relative.path.original(), Path::new(&remapping.path));
367 assert!(relative.path.parent.is_none());
368
369 let relative = RelativeRemapping::new(remapping, Path::new("/a/b"));
370 assert_eq!(relative.to_relative_remapping(), Remapping::from_str("oz/=c/d/").unwrap());
371 }
372
373 #[test]
374 fn remapping_errors() {
375 let remapping = "oz=../b/c/d";
376 let remapping = Remapping::from_str(remapping).unwrap();
377 assert_eq!(remapping.name, "oz".to_string());
378 assert_eq!(remapping.path, "../b/c/d".to_string());
379
380 let err = Remapping::from_str("").unwrap_err();
381 matches!(err, RemappingError::InvalidRemapping(_));
382
383 let err = Remapping::from_str("oz=").unwrap_err();
384 matches!(err, RemappingError::EmptyRemappingValue(_));
385 }
386
387 #[test]
388 fn can_resolve_contexts() {
389 let remapping = "context:oz=a/b/c/d";
390 let remapping = Remapping::from_str(remapping).unwrap();
391
392 assert_eq!(
393 remapping,
394 Remapping {
395 context: Some("context".to_string()),
396 name: "oz".to_string(),
397 path: "a/b/c/d".to_string(),
398 }
399 );
400 assert_eq!(remapping.to_string(), "context:oz/=a/b/c/d/".to_string());
401
402 let remapping = "context:foo=C:/bar/src/";
403 let remapping = Remapping::from_str(remapping).unwrap();
404
405 assert_eq!(
406 remapping,
407 Remapping {
408 context: Some("context".to_string()),
409 name: "foo".to_string(),
410 path: "C:/bar/src/".to_string()
411 }
412 );
413 }
414
415 #[test]
416 fn can_resolve_global_contexts() {
417 let remapping = ":oz=a/b/c/d/";
418 let remapping = Remapping::from_str(remapping).unwrap();
419
420 assert_eq!(
421 remapping,
422 Remapping { context: None, name: "oz".to_string(), path: "a/b/c/d/".to_string() }
423 );
424 assert_eq!(remapping.to_string(), "oz/=a/b/c/d/".to_string());
425 }
426}