| 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. |
| 30 | pub fn errs(&self) -> &[Error] { |
| 31 | &self.errs |
| 32 | } |
| 33 | |
| 34 | /// Assert that no errors have occurred. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 84 | pub fn tmp() -> Dir { |
| 85 | let dir = TempDir::new().unwrap(); |
| 86 | Dir { dir } |
| 87 | } |
| 88 | |
| 89 | /// Return the path to this directory. |
| 90 | pub fn path(&self) -> &Path { |
| 91 | self.dir.path() |
| 92 | } |
| 93 | |
| 94 | /// Return a path joined to the path to this directory. |
| 95 | 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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 { |
| 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. |
| 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. |
| 249 | pub fn path(&self) -> &Path { |
| 250 | &self.0 |
| 251 | } |
| 252 | } |
| 253 | |