foundry_block_explorers/
source_tree.rs1use crate::Result;
2use std::{
3 fs::create_dir_all,
4 path::{Component, Path, PathBuf},
5};
6
7#[derive(Clone, Debug)]
8pub struct SourceTreeEntry {
9 pub path: PathBuf,
10 pub contents: String,
11}
12
13#[derive(Clone, Debug)]
14pub struct SourceTree {
15 pub entries: Vec<SourceTreeEntry>,
16}
17
18impl SourceTree {
19 pub fn write_to(&self, dir: &Path) -> Result<()> {
22 create_dir_all(dir)?;
23 for entry in &self.entries {
24 let mut sanitized_path = sanitize_path(&entry.path);
25 if sanitized_path.extension().is_none() {
26 let with_extension = sanitized_path.with_extension("sol");
27 if !self.entries.iter().any(|e| e.path == with_extension) {
28 sanitized_path = with_extension;
29 }
30 }
31 let joined = dir.join(sanitized_path);
32 if let Some(parent) = joined.parent() {
33 create_dir_all(parent)?;
34 std::fs::write(joined, &entry.contents)?;
35 }
36 }
37 Ok(())
38 }
39}
40
41pub(crate) fn sanitize_path(path: impl AsRef<Path>) -> PathBuf {
43 let sanitized = path
44 .as_ref()
45 .components()
46 .filter(|x| x.as_os_str() != Component::ParentDir.as_os_str())
47 .collect::<PathBuf>();
48
49 sanitized.strip_prefix("/").map(PathBuf::from).unwrap_or(sanitized)
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use std::fs::read_dir;
57
58 #[test]
61 fn test_source_tree_write() {
62 let tempdir = tempfile::tempdir().unwrap();
63 let st = SourceTree {
64 entries: vec![
65 SourceTreeEntry { path: PathBuf::from("a/a.sol"), contents: String::from("Test") },
66 SourceTreeEntry { path: PathBuf::from("b/b"), contents: String::from("Test 2") },
67 ],
68 };
69 st.write_to(tempdir.path()).unwrap();
70 let a_sol_path = PathBuf::new().join(&tempdir).join("a").join("a.sol");
71 let b_sol_path = PathBuf::new().join(&tempdir).join("b").join("b.sol");
72 assert!(a_sol_path.exists());
73 assert!(b_sol_path.exists());
74 }
75
76 #[test]
79 fn test_malformed_source_tree_write() {
80 let tempdir = tempfile::tempdir().unwrap();
81 let st = SourceTree {
82 entries: vec![
83 SourceTreeEntry {
84 path: PathBuf::from("../a/a.sol"),
85 contents: String::from("Test"),
86 },
87 SourceTreeEntry {
88 path: PathBuf::from("../b/../b.sol"),
89 contents: String::from("Test 2"),
90 },
91 SourceTreeEntry {
92 path: PathBuf::from("/c/c.sol"),
93 contents: String::from("Test 3"),
94 },
95 ],
96 };
97 st.write_to(tempdir.path()).unwrap();
98 let written_paths = read_dir(tempdir.path()).unwrap();
99 let paths: Vec<PathBuf> =
100 written_paths.into_iter().filter_map(|x| x.ok()).map(|x| x.path()).collect();
101 assert_eq!(paths.len(), 3);
102 assert!(paths.contains(&tempdir.path().join("a")));
103 assert!(paths.contains(&tempdir.path().join("b")));
104 assert!(paths.contains(&tempdir.path().join("c")));
105 }
106}