1 use std::env; 2 use std::error; 3 use std::fs::{self, File}; 4 use std::io; 5 use std::path::{Path, PathBuf}; 6 use std::result; 7 8 use crate::{DirEntry, Error}; 9 10 /// Create an error from a format!-like syntax. 11 #[macro_export] 12 macro_rules! err { 13 ($($tt:tt)*) => { 14 Box::<dyn error::Error + Send + Sync>::from(format!($($tt)*)) 15 } 16 } 17 18 /// A convenient result type alias. 19 pub type Result<T> = result::Result<T, Box<dyn error::Error + Send + Sync>>; 20 21 /// The result of running a recursive directory iterator on a single directory. 22 #[derive(Debug)] 23 pub struct RecursiveResults { 24 ents: Vec<DirEntry>, 25 errs: Vec<Error>, 26 } 27 28 impl RecursiveResults { 29 /// Return all of the errors encountered during traversal. errs(&self) -> &[Error]30 pub fn errs(&self) -> &[Error] { 31 &self.errs 32 } 33 34 /// Assert that no errors have occurred. assert_no_errors(&self)35 pub fn assert_no_errors(&self) { 36 assert!( 37 self.errs.is_empty(), 38 "expected to find no errors, but found: {:?}", 39 self.errs 40 ); 41 } 42 43 /// Return all the successfully retrieved directory entries in the order 44 /// in which they were retrieved. ents(&self) -> &[DirEntry]45 pub fn ents(&self) -> &[DirEntry] { 46 &self.ents 47 } 48 49 /// Return all paths from all successfully retrieved directory entries. 50 /// 51 /// This does not include paths that correspond to an error. paths(&self) -> Vec<PathBuf>52 pub fn paths(&self) -> Vec<PathBuf> { 53 self.ents.iter().map(|d| d.path().to_path_buf()).collect() 54 } 55 56 /// Return all the successfully retrieved directory entries, sorted 57 /// lexicographically by their full file path. sorted_ents(&self) -> Vec<DirEntry>58 pub fn sorted_ents(&self) -> Vec<DirEntry> { 59 let mut ents = self.ents.clone(); 60 ents.sort_by(|e1, e2| e1.path().cmp(e2.path())); 61 ents 62 } 63 64 /// Return all paths from all successfully retrieved directory entries, 65 /// sorted lexicographically. 66 /// 67 /// This does not include paths that correspond to an error. sorted_paths(&self) -> Vec<PathBuf>68 pub fn sorted_paths(&self) -> Vec<PathBuf> { 69 self.sorted_ents().into_iter().map(|d| d.into_path()).collect() 70 } 71 } 72 73 /// A helper for managing a directory in which to run tests. 74 /// 75 /// When manipulating paths within this directory, paths are interpreted 76 /// relative to this directory. 77 #[derive(Debug)] 78 pub struct Dir { 79 dir: TempDir, 80 } 81 82 impl Dir { 83 /// Create a new empty temporary directory. tmp() -> Dir84 pub fn tmp() -> Dir { 85 let dir = TempDir::new().unwrap(); 86 Dir { dir } 87 } 88 89 /// Return the path to this directory. path(&self) -> &Path90 pub fn path(&self) -> &Path { 91 self.dir.path() 92 } 93 94 /// Return a path joined to the path to this directory. join<P: AsRef<Path>>(&self, path: P) -> PathBuf95 pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf { 96 self.path().join(path) 97 } 98 99 /// Run the given iterator and return the result as a distinct collection 100 /// of directory entries and errors. run_recursive<I>(&self, it: I) -> RecursiveResults where I: IntoIterator<Item = result::Result<DirEntry, Error>>,101 pub fn run_recursive<I>(&self, it: I) -> RecursiveResults 102 where 103 I: IntoIterator<Item = result::Result<DirEntry, Error>>, 104 { 105 let mut results = RecursiveResults { ents: vec![], errs: vec![] }; 106 for result in it { 107 match result { 108 Ok(ent) => results.ents.push(ent), 109 Err(err) => results.errs.push(err), 110 } 111 } 112 results 113 } 114 115 /// Create a directory at the given path, while creating all intermediate 116 /// directories as needed. mkdirp<P: AsRef<Path>>(&self, path: P)117 pub fn mkdirp<P: AsRef<Path>>(&self, path: P) { 118 let full = self.join(path); 119 fs::create_dir_all(&full) 120 .map_err(|e| { 121 err!("failed to create directory {}: {}", full.display(), e) 122 }) 123 .unwrap(); 124 } 125 126 /// Create an empty file at the given path. All ancestor directories must 127 /// already exists. touch<P: AsRef<Path>>(&self, path: P)128 pub fn touch<P: AsRef<Path>>(&self, path: P) { 129 let full = self.join(path); 130 File::create(&full) 131 .map_err(|e| { 132 err!("failed to create file {}: {}", full.display(), e) 133 }) 134 .unwrap(); 135 } 136 137 /// Create empty files at the given paths. All ancestor directories must 138 /// already exists. touch_all<P: AsRef<Path>>(&self, paths: &[P])139 pub fn touch_all<P: AsRef<Path>>(&self, paths: &[P]) { 140 for p in paths { 141 self.touch(p); 142 } 143 } 144 145 /// Create a file symlink to the given src with the given link name. symlink_file<P1: AsRef<Path>, P2: AsRef<Path>>( &self, src: P1, link_name: P2, )146 pub fn symlink_file<P1: AsRef<Path>, P2: AsRef<Path>>( 147 &self, 148 src: P1, 149 link_name: P2, 150 ) { 151 #[cfg(windows)] 152 fn imp(src: &Path, link_name: &Path) -> io::Result<()> { 153 use std::os::windows::fs::symlink_file; 154 symlink_file(src, link_name) 155 } 156 157 #[cfg(unix)] 158 fn imp(src: &Path, link_name: &Path) -> io::Result<()> { 159 use std::os::unix::fs::symlink; 160 symlink(src, link_name) 161 } 162 163 let (src, link_name) = (self.join(src), self.join(link_name)); 164 imp(&src, &link_name) 165 .map_err(|e| { 166 err!( 167 "failed to symlink file {} with target {}: {}", 168 src.display(), 169 link_name.display(), 170 e 171 ) 172 }) 173 .unwrap() 174 } 175 176 /// Create a directory symlink to the given src with the given link name. symlink_dir<P1: AsRef<Path>, P2: AsRef<Path>>( &self, src: P1, link_name: P2, )177 pub fn symlink_dir<P1: AsRef<Path>, P2: AsRef<Path>>( 178 &self, 179 src: P1, 180 link_name: P2, 181 ) { 182 #[cfg(windows)] 183 fn imp(src: &Path, link_name: &Path) -> io::Result<()> { 184 use std::os::windows::fs::symlink_dir; 185 symlink_dir(src, link_name) 186 } 187 188 #[cfg(unix)] 189 fn imp(src: &Path, link_name: &Path) -> io::Result<()> { 190 use std::os::unix::fs::symlink; 191 symlink(src, link_name) 192 } 193 194 let (src, link_name) = (self.join(src), self.join(link_name)); 195 imp(&src, &link_name) 196 .map_err(|e| { 197 err!( 198 "failed to symlink directory {} with target {}: {}", 199 src.display(), 200 link_name.display(), 201 e 202 ) 203 }) 204 .unwrap() 205 } 206 } 207 208 /// A simple wrapper for creating a temporary directory that is automatically 209 /// deleted when it's dropped. 210 /// 211 /// We use this in lieu of tempfile because tempfile brings in too many 212 /// dependencies. 213 #[derive(Debug)] 214 pub struct TempDir(PathBuf); 215 216 impl Drop for TempDir { drop(&mut self)217 fn drop(&mut self) { 218 fs::remove_dir_all(&self.0).unwrap(); 219 } 220 } 221 222 impl TempDir { 223 /// Create a new empty temporary directory under the system's configured 224 /// temporary directory. new() -> Result<TempDir>225 pub fn new() -> Result<TempDir> { 226 #[allow(deprecated)] 227 use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; 228 229 static TRIES: usize = 100; 230 #[allow(deprecated)] 231 static COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; 232 233 let tmpdir = env::temp_dir(); 234 for _ in 0..TRIES { 235 let count = COUNTER.fetch_add(1, Ordering::SeqCst); 236 let path = tmpdir.join("rust-walkdir").join(count.to_string()); 237 if path.is_dir() { 238 continue; 239 } 240 fs::create_dir_all(&path).map_err(|e| { 241 err!("failed to create {}: {}", path.display(), e) 242 })?; 243 return Ok(TempDir(path)); 244 } 245 Err(err!("failed to create temp dir after {} tries", TRIES)) 246 } 247 248 /// Return the underlying path to this temporary directory. path(&self) -> &Path249 pub fn path(&self) -> &Path { 250 &self.0 251 } 252 } 253