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 | |