ethers_etherscan/
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 sanitized_path.set_extension("sol");
27 }
28 let joined = dir.join(sanitized_path);
29 if let Some(parent) = joined.parent() {
30 create_dir_all(parent)?;
31 std::fs::write(joined, &entry.contents)?;
32 }
33 }
34 Ok(())
35 }
36}
37
38fn sanitize_path(path: &Path) -> PathBuf {
40 let sanitized = Path::new(path)
41 .components()
42 .filter(|x| x.as_os_str() != Component::ParentDir.as_os_str())
43 .collect::<PathBuf>();
44
45 sanitized.strip_prefix("/").map(PathBuf::from).unwrap_or(sanitized)
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use std::fs::read_dir;
53
54 #[test]
57 fn test_source_tree_write() {
58 let tempdir = tempfile::tempdir().unwrap();
59 let st = SourceTree {
60 entries: vec![
61 SourceTreeEntry { path: PathBuf::from("a/a.sol"), contents: String::from("Test") },
62 SourceTreeEntry { path: PathBuf::from("b/b"), contents: String::from("Test 2") },
63 ],
64 };
65 st.write_to(tempdir.path()).unwrap();
66 let a_sol_path = PathBuf::new().join(&tempdir).join("a").join("a.sol");
67 let b_sol_path = PathBuf::new().join(&tempdir).join("b").join("b.sol");
68 assert!(a_sol_path.exists());
69 assert!(b_sol_path.exists());
70 }
71
72 #[test]
75 fn test_malformed_source_tree_write() {
76 let tempdir = tempfile::tempdir().unwrap();
77 let st = SourceTree {
78 entries: vec![
79 SourceTreeEntry {
80 path: PathBuf::from("../a/a.sol"),
81 contents: String::from("Test"),
82 },
83 SourceTreeEntry {
84 path: PathBuf::from("../b/../b.sol"),
85 contents: String::from("Test 2"),
86 },
87 SourceTreeEntry {
88 path: PathBuf::from("/c/c.sol"),
89 contents: String::from("Test 3"),
90 },
91 ],
92 };
93 st.write_to(tempdir.path()).unwrap();
94 let written_paths = read_dir(tempdir.path()).unwrap();
95 let paths: Vec<PathBuf> =
96 written_paths.into_iter().filter_map(|x| x.ok()).map(|x| x.path()).collect();
97 assert_eq!(paths.len(), 3);
98 assert!(paths.contains(&tempdir.path().join("a")));
99 assert!(paths.contains(&tempdir.path().join("b")));
100 assert!(paths.contains(&tempdir.path().join("c")));
101 }
102}