1#![deny(missing_docs)]
48
49extern crate crossbeam_channel as channel;
50extern crate globset;
51#[macro_use]
52extern crate lazy_static;
53#[macro_use]
54extern crate log;
55extern crate memchr;
56extern crate regex;
57extern crate same_file;
58extern crate thread_local;
59extern crate walkdir;
60#[cfg(windows)]
61extern crate winapi_util;
62
63use std::error;
64use std::fmt;
65use std::io;
66use std::path::{Path, PathBuf};
67
68pub use walk::{DirEntry, Walk, WalkBuilder, WalkParallel, WalkState};
69
70mod dir;
71pub mod gitignore;
72mod pathutil;
73pub mod overrides;
74pub mod types;
75mod walk;
76
77#[derive(Debug)]
79pub enum Error {
80 Partial(Vec<Error>),
83 WithLineNumber {
85 line: u64,
87 err: Box<Error>,
89 },
90 WithPath {
92 path: PathBuf,
94 err: Box<Error>,
96 },
97 WithDepth {
100 depth: usize,
102 err: Box<Error>,
104 },
105 Loop {
108 ancestor: PathBuf,
110 child: PathBuf,
112 },
113 Io(io::Error),
115 Glob {
117 glob: Option<String>,
124 err: String,
126 },
127 UnrecognizedFileType(String),
129 InvalidDefinition,
131}
132
133impl Clone for Error {
134 fn clone(&self) -> Error {
135 match *self {
136 Error::Partial(ref errs) => Error::Partial(errs.clone()),
137 Error::WithLineNumber { line, ref err } => {
138 Error::WithLineNumber { line: line, err: err.clone() }
139 }
140 Error::WithPath { ref path, ref err } => {
141 Error::WithPath { path: path.clone(), err: err.clone() }
142 }
143 Error::WithDepth { depth, ref err } => {
144 Error::WithDepth { depth: depth, err: err.clone() }
145 }
146 Error::Loop { ref ancestor, ref child } => {
147 Error::Loop {
148 ancestor: ancestor.clone(),
149 child: child.clone()
150 }
151 }
152 Error::Io(ref err) => {
153 match err.raw_os_error() {
154 Some(e) => Error::Io(io::Error::from_raw_os_error(e)),
155 None => {
156 Error::Io(io::Error::new(err.kind(), err.to_string()))
157 }
158 }
159 }
160 Error::Glob { ref glob, ref err } => {
161 Error::Glob { glob: glob.clone(), err: err.clone() }
162 }
163 Error::UnrecognizedFileType(ref err) => {
164 Error::UnrecognizedFileType(err.clone())
165 }
166 Error::InvalidDefinition => Error::InvalidDefinition,
167 }
168 }
169}
170
171impl Error {
172 pub fn is_partial(&self) -> bool {
178 match *self {
179 Error::Partial(_) => true,
180 Error::WithLineNumber { ref err, .. } => err.is_partial(),
181 Error::WithPath { ref err, .. } => err.is_partial(),
182 Error::WithDepth { ref err, .. } => err.is_partial(),
183 _ => false,
184 }
185 }
186
187 pub fn is_io(&self) -> bool {
189 match *self {
190 Error::Partial(ref errs) => errs.len() == 1 && errs[0].is_io(),
191 Error::WithLineNumber { ref err, .. } => err.is_io(),
192 Error::WithPath { ref err, .. } => err.is_io(),
193 Error::WithDepth { ref err, .. } => err.is_io(),
194 Error::Loop { .. } => false,
195 Error::Io(_) => true,
196 Error::Glob { .. } => false,
197 Error::UnrecognizedFileType(_) => false,
198 Error::InvalidDefinition => false,
199 }
200 }
201
202 pub fn depth(&self) -> Option<usize> {
205 match *self {
206 Error::WithPath { ref err, .. } => err.depth(),
207 Error::WithDepth { depth, .. } => Some(depth),
208 _ => None,
209 }
210 }
211
212 fn with_path<P: AsRef<Path>>(self, path: P) -> Error {
214 Error::WithPath {
215 path: path.as_ref().to_path_buf(),
216 err: Box::new(self),
217 }
218 }
219
220 fn with_depth(self, depth: usize) -> Error {
222 Error::WithDepth {
223 depth: depth,
224 err: Box::new(self),
225 }
226 }
227
228 fn tagged<P: AsRef<Path>>(self, path: P, lineno: u64) -> Error {
231 let errline = Error::WithLineNumber {
232 line: lineno,
233 err: Box::new(self),
234 };
235 if path.as_ref().as_os_str().is_empty() {
236 return errline;
237 }
238 errline.with_path(path)
239 }
240
241 fn from_walkdir(err: walkdir::Error) -> Error {
243 let depth = err.depth();
244 if let (Some(anc), Some(child)) = (err.loop_ancestor(), err.path()) {
245 return Error::WithDepth {
246 depth: depth,
247 err: Box::new(Error::Loop {
248 ancestor: anc.to_path_buf(),
249 child: child.to_path_buf(),
250 }),
251 };
252 }
253 let path = err.path().map(|p| p.to_path_buf());
254 let mut ig_err = Error::Io(io::Error::from(err));
255 if let Some(path) = path {
256 ig_err = Error::WithPath {
257 path: path,
258 err: Box::new(ig_err),
259 };
260 }
261 ig_err
262 }
263}
264
265impl error::Error for Error {
266 fn description(&self) -> &str {
267 match *self {
268 Error::Partial(_) => "partial error",
269 Error::WithLineNumber { ref err, .. } => err.description(),
270 Error::WithPath { ref err, .. } => err.description(),
271 Error::WithDepth { ref err, .. } => err.description(),
272 Error::Loop { .. } => "file system loop found",
273 Error::Io(ref err) => err.description(),
274 Error::Glob { ref err, .. } => err,
275 Error::UnrecognizedFileType(_) => "unrecognized file type",
276 Error::InvalidDefinition => "invalid definition",
277 }
278 }
279}
280
281impl fmt::Display for Error {
282 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283 match *self {
284 Error::Partial(ref errs) => {
285 let msgs: Vec<String> =
286 errs.iter().map(|err| err.to_string()).collect();
287 write!(f, "{}", msgs.join("\n"))
288 }
289 Error::WithLineNumber { line, ref err } => {
290 write!(f, "line {}: {}", line, err)
291 }
292 Error::WithPath { ref path, ref err } => {
293 write!(f, "{}: {}", path.display(), err)
294 }
295 Error::WithDepth { ref err, .. } => err.fmt(f),
296 Error::Loop { ref ancestor, ref child } => {
297 write!(f, "File system loop found: \
298 {} points to an ancestor {}",
299 child.display(), ancestor.display())
300 }
301 Error::Io(ref err) => err.fmt(f),
302 Error::Glob { glob: None, ref err } => write!(f, "{}", err),
303 Error::Glob { glob: Some(ref glob), ref err } => {
304 write!(f, "error parsing glob '{}': {}", glob, err)
305 }
306 Error::UnrecognizedFileType(ref ty) => {
307 write!(f, "unrecognized file type: {}", ty)
308 }
309 Error::InvalidDefinition => {
310 write!(f, "invalid definition (format is type:glob, e.g., \
311 html:*.html)")
312 }
313 }
314 }
315}
316
317impl From<io::Error> for Error {
318 fn from(err: io::Error) -> Error {
319 Error::Io(err)
320 }
321}
322
323#[derive(Debug, Default)]
324struct PartialErrorBuilder(Vec<Error>);
325
326impl PartialErrorBuilder {
327 fn push(&mut self, err: Error) {
328 self.0.push(err);
329 }
330
331 fn push_ignore_io(&mut self, err: Error) {
332 if !err.is_io() {
333 self.push(err);
334 }
335 }
336
337 fn maybe_push(&mut self, err: Option<Error>) {
338 if let Some(err) = err {
339 self.push(err);
340 }
341 }
342
343 fn maybe_push_ignore_io(&mut self, err: Option<Error>) {
344 if let Some(err) = err {
345 self.push_ignore_io(err);
346 }
347 }
348
349 fn into_error_option(mut self) -> Option<Error> {
350 if self.0.is_empty() {
351 None
352 } else if self.0.len() == 1 {
353 Some(self.0.pop().unwrap())
354 } else {
355 Some(Error::Partial(self.0))
356 }
357 }
358}
359
360#[derive(Clone, Debug)]
367pub enum Match<T> {
368 None,
370 Ignore(T),
373 Whitelist(T),
376}
377
378impl<T> Match<T> {
379 pub fn is_none(&self) -> bool {
381 match *self {
382 Match::None => true,
383 Match::Ignore(_) | Match::Whitelist(_) => false,
384 }
385 }
386
387 pub fn is_ignore(&self) -> bool {
389 match *self {
390 Match::Ignore(_) => true,
391 Match::None | Match::Whitelist(_) => false,
392 }
393 }
394
395 pub fn is_whitelist(&self) -> bool {
398 match *self {
399 Match::Whitelist(_) => true,
400 Match::None | Match::Ignore(_) => false,
401 }
402 }
403
404 pub fn invert(self) -> Match<T> {
407 match self {
408 Match::None => Match::None,
409 Match::Ignore(t) => Match::Whitelist(t),
410 Match::Whitelist(t) => Match::Ignore(t),
411 }
412 }
413
414 pub fn inner(&self) -> Option<&T> {
416 match *self {
417 Match::None => None,
418 Match::Ignore(ref t) => Some(t),
419 Match::Whitelist(ref t) => Some(t),
420 }
421 }
422
423 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Match<U> {
427 match self {
428 Match::None => Match::None,
429 Match::Ignore(t) => Match::Ignore(f(t)),
430 Match::Whitelist(t) => Match::Whitelist(f(t)),
431 }
432 }
433
434 pub fn or(self, other: Self) -> Self {
436 if self.is_none() {
437 other
438 } else {
439 self
440 }
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use std::env;
447 use std::error;
448 use std::fs;
449 use std::path::{Path, PathBuf};
450 use std::result;
451
452 pub type Result<T> =
454 result::Result<T, Box<dyn error::Error + Send + Sync>>;
455
456 macro_rules! err {
457 ($($tt:tt)*) => {
458 Box::<dyn error::Error + Send + Sync>::from(format!($($tt)*))
459 }
460 }
461
462 #[derive(Debug)]
468 pub struct TempDir(PathBuf);
469
470 impl Drop for TempDir {
471 fn drop(&mut self) {
472 fs::remove_dir_all(&self.0).unwrap();
473 }
474 }
475
476 impl TempDir {
477 pub fn new() -> Result<TempDir> {
480 use std::sync::atomic::{AtomicUsize, Ordering};
481
482 static TRIES: usize = 100;
483 static COUNTER: AtomicUsize = AtomicUsize::new(0);
484
485 let tmpdir = env::temp_dir();
486 for _ in 0..TRIES {
487 let count = COUNTER.fetch_add(1, Ordering::SeqCst);
488 let path = tmpdir.join("rust-ignore").join(count.to_string());
489 if path.is_dir() {
490 continue;
491 }
492 fs::create_dir_all(&path).map_err(|e| {
493 err!("failed to create {}: {}", path.display(), e)
494 })?;
495 return Ok(TempDir(path));
496 }
497 Err(err!("failed to create temp dir after {} tries", TRIES))
498 }
499
500 pub fn path(&self) -> &Path {
502 &self.0
503 }
504 }
505}