1#![deny(missing_docs)]
48
49use std::error;
50use std::fmt;
51use std::io;
52use std::path::{Path, PathBuf};
53
54pub use crate::walk::{
55 DirEntry, ParallelVisitor, ParallelVisitorBuilder, Walk, WalkBuilder,
56 WalkParallel, WalkState,
57};
58
59mod default_types;
60mod dir;
61pub mod gitignore;
62pub mod overrides;
63mod pathutil;
64pub mod types;
65mod walk;
66
67#[derive(Debug)]
69pub enum Error {
70 Partial(Vec<Error>),
73 WithLineNumber {
75 line: u64,
77 err: Box<Error>,
79 },
80 WithPath {
82 path: PathBuf,
84 err: Box<Error>,
86 },
87 WithDepth {
90 depth: usize,
92 err: Box<Error>,
94 },
95 Loop {
98 ancestor: PathBuf,
100 child: PathBuf,
102 },
103 Io(io::Error),
105 Glob {
107 glob: Option<String>,
114 err: String,
116 },
117 UnrecognizedFileType(String),
119 InvalidDefinition,
121}
122
123impl Clone for Error {
124 fn clone(&self) -> Error {
125 match *self {
126 Error::Partial(ref errs) => Error::Partial(errs.clone()),
127 Error::WithLineNumber { line, ref err } => {
128 Error::WithLineNumber { line: line, err: err.clone() }
129 }
130 Error::WithPath { ref path, ref err } => {
131 Error::WithPath { path: path.clone(), err: err.clone() }
132 }
133 Error::WithDepth { depth, ref err } => {
134 Error::WithDepth { depth: depth, err: err.clone() }
135 }
136 Error::Loop { ref ancestor, ref child } => Error::Loop {
137 ancestor: ancestor.clone(),
138 child: child.clone(),
139 },
140 Error::Io(ref err) => match err.raw_os_error() {
141 Some(e) => Error::Io(io::Error::from_raw_os_error(e)),
142 None => Error::Io(io::Error::new(err.kind(), err.to_string())),
143 },
144 Error::Glob { ref glob, ref err } => {
145 Error::Glob { glob: glob.clone(), err: err.clone() }
146 }
147 Error::UnrecognizedFileType(ref err) => {
148 Error::UnrecognizedFileType(err.clone())
149 }
150 Error::InvalidDefinition => Error::InvalidDefinition,
151 }
152 }
153}
154
155impl Error {
156 pub fn is_partial(&self) -> bool {
162 match *self {
163 Error::Partial(_) => true,
164 Error::WithLineNumber { ref err, .. } => err.is_partial(),
165 Error::WithPath { ref err, .. } => err.is_partial(),
166 Error::WithDepth { ref err, .. } => err.is_partial(),
167 _ => false,
168 }
169 }
170
171 pub fn is_io(&self) -> bool {
173 match *self {
174 Error::Partial(ref errs) => errs.len() == 1 && errs[0].is_io(),
175 Error::WithLineNumber { ref err, .. } => err.is_io(),
176 Error::WithPath { ref err, .. } => err.is_io(),
177 Error::WithDepth { ref err, .. } => err.is_io(),
178 Error::Loop { .. } => false,
179 Error::Io(_) => true,
180 Error::Glob { .. } => false,
181 Error::UnrecognizedFileType(_) => false,
182 Error::InvalidDefinition => false,
183 }
184 }
185
186 pub fn io_error(&self) -> Option<&std::io::Error> {
207 match *self {
208 Error::Partial(ref errs) => {
209 if errs.len() == 1 {
210 errs[0].io_error()
211 } else {
212 None
213 }
214 }
215 Error::WithLineNumber { ref err, .. } => err.io_error(),
216 Error::WithPath { ref err, .. } => err.io_error(),
217 Error::WithDepth { ref err, .. } => err.io_error(),
218 Error::Loop { .. } => None,
219 Error::Io(ref err) => Some(err),
220 Error::Glob { .. } => None,
221 Error::UnrecognizedFileType(_) => None,
222 Error::InvalidDefinition => None,
223 }
224 }
225
226 pub fn into_io_error(self) -> Option<std::io::Error> {
232 match self {
233 Error::Partial(mut errs) => {
234 if errs.len() == 1 {
235 errs.remove(0).into_io_error()
236 } else {
237 None
238 }
239 }
240 Error::WithLineNumber { err, .. } => err.into_io_error(),
241 Error::WithPath { err, .. } => err.into_io_error(),
242 Error::WithDepth { err, .. } => err.into_io_error(),
243 Error::Loop { .. } => None,
244 Error::Io(err) => Some(err),
245 Error::Glob { .. } => None,
246 Error::UnrecognizedFileType(_) => None,
247 Error::InvalidDefinition => None,
248 }
249 }
250
251 pub fn depth(&self) -> Option<usize> {
254 match *self {
255 Error::WithPath { ref err, .. } => err.depth(),
256 Error::WithDepth { depth, .. } => Some(depth),
257 _ => None,
258 }
259 }
260
261 fn with_path<P: AsRef<Path>>(self, path: P) -> Error {
263 Error::WithPath {
264 path: path.as_ref().to_path_buf(),
265 err: Box::new(self),
266 }
267 }
268
269 fn with_depth(self, depth: usize) -> Error {
271 Error::WithDepth { depth: depth, err: Box::new(self) }
272 }
273
274 fn tagged<P: AsRef<Path>>(self, path: P, lineno: u64) -> Error {
277 let errline =
278 Error::WithLineNumber { line: lineno, err: Box::new(self) };
279 if path.as_ref().as_os_str().is_empty() {
280 return errline;
281 }
282 errline.with_path(path)
283 }
284
285 fn from_walkdir(err: walkdir::Error) -> Error {
287 let depth = err.depth();
288 if let (Some(anc), Some(child)) = (err.loop_ancestor(), err.path()) {
289 return Error::WithDepth {
290 depth: depth,
291 err: Box::new(Error::Loop {
292 ancestor: anc.to_path_buf(),
293 child: child.to_path_buf(),
294 }),
295 };
296 }
297 let path = err.path().map(|p| p.to_path_buf());
298 let mut ig_err = Error::Io(io::Error::from(err));
299 if let Some(path) = path {
300 ig_err = Error::WithPath { path: path, err: Box::new(ig_err) };
301 }
302 ig_err
303 }
304}
305
306impl error::Error for Error {
307 #[allow(deprecated)]
308 fn description(&self) -> &str {
309 match *self {
310 Error::Partial(_) => "partial error",
311 Error::WithLineNumber { ref err, .. } => err.description(),
312 Error::WithPath { ref err, .. } => err.description(),
313 Error::WithDepth { ref err, .. } => err.description(),
314 Error::Loop { .. } => "file system loop found",
315 Error::Io(ref err) => err.description(),
316 Error::Glob { ref err, .. } => err,
317 Error::UnrecognizedFileType(_) => "unrecognized file type",
318 Error::InvalidDefinition => "invalid definition",
319 }
320 }
321}
322
323impl fmt::Display for Error {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 match *self {
326 Error::Partial(ref errs) => {
327 let msgs: Vec<String> =
328 errs.iter().map(|err| err.to_string()).collect();
329 write!(f, "{}", msgs.join("\n"))
330 }
331 Error::WithLineNumber { line, ref err } => {
332 write!(f, "line {}: {}", line, err)
333 }
334 Error::WithPath { ref path, ref err } => {
335 write!(f, "{}: {}", path.display(), err)
336 }
337 Error::WithDepth { ref err, .. } => err.fmt(f),
338 Error::Loop { ref ancestor, ref child } => write!(
339 f,
340 "File system loop found: \
341 {} points to an ancestor {}",
342 child.display(),
343 ancestor.display()
344 ),
345 Error::Io(ref err) => err.fmt(f),
346 Error::Glob { glob: None, ref err } => write!(f, "{}", err),
347 Error::Glob { glob: Some(ref glob), ref err } => {
348 write!(f, "error parsing glob '{}': {}", glob, err)
349 }
350 Error::UnrecognizedFileType(ref ty) => {
351 write!(f, "unrecognized file type: {}", ty)
352 }
353 Error::InvalidDefinition => write!(
354 f,
355 "invalid definition (format is type:glob, e.g., \
356 html:*.html)"
357 ),
358 }
359 }
360}
361
362impl From<io::Error> for Error {
363 fn from(err: io::Error) -> Error {
364 Error::Io(err)
365 }
366}
367
368#[derive(Debug, Default)]
369struct PartialErrorBuilder(Vec<Error>);
370
371impl PartialErrorBuilder {
372 fn push(&mut self, err: Error) {
373 self.0.push(err);
374 }
375
376 fn push_ignore_io(&mut self, err: Error) {
377 if !err.is_io() {
378 self.push(err);
379 }
380 }
381
382 fn maybe_push(&mut self, err: Option<Error>) {
383 if let Some(err) = err {
384 self.push(err);
385 }
386 }
387
388 fn maybe_push_ignore_io(&mut self, err: Option<Error>) {
389 if let Some(err) = err {
390 self.push_ignore_io(err);
391 }
392 }
393
394 fn into_error_option(mut self) -> Option<Error> {
395 if self.0.is_empty() {
396 None
397 } else if self.0.len() == 1 {
398 Some(self.0.pop().unwrap())
399 } else {
400 Some(Error::Partial(self.0))
401 }
402 }
403}
404
405#[derive(Clone, Debug)]
412pub enum Match<T> {
413 None,
415 Ignore(T),
418 Whitelist(T),
421}
422
423impl<T> Match<T> {
424 pub fn is_none(&self) -> bool {
426 match *self {
427 Match::None => true,
428 Match::Ignore(_) | Match::Whitelist(_) => false,
429 }
430 }
431
432 pub fn is_ignore(&self) -> bool {
434 match *self {
435 Match::Ignore(_) => true,
436 Match::None | Match::Whitelist(_) => false,
437 }
438 }
439
440 pub fn is_whitelist(&self) -> bool {
443 match *self {
444 Match::Whitelist(_) => true,
445 Match::None | Match::Ignore(_) => false,
446 }
447 }
448
449 pub fn invert(self) -> Match<T> {
452 match self {
453 Match::None => Match::None,
454 Match::Ignore(t) => Match::Whitelist(t),
455 Match::Whitelist(t) => Match::Ignore(t),
456 }
457 }
458
459 pub fn inner(&self) -> Option<&T> {
461 match *self {
462 Match::None => None,
463 Match::Ignore(ref t) => Some(t),
464 Match::Whitelist(ref t) => Some(t),
465 }
466 }
467
468 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Match<U> {
472 match self {
473 Match::None => Match::None,
474 Match::Ignore(t) => Match::Ignore(f(t)),
475 Match::Whitelist(t) => Match::Whitelist(f(t)),
476 }
477 }
478
479 pub fn or(self, other: Self) -> Self {
481 if self.is_none() {
482 other
483 } else {
484 self
485 }
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use std::env;
492 use std::error;
493 use std::fs;
494 use std::path::{Path, PathBuf};
495 use std::result;
496
497 pub type Result<T> =
499 result::Result<T, Box<dyn error::Error + Send + Sync>>;
500
501 macro_rules! err {
502 ($($tt:tt)*) => {
503 Box::<dyn error::Error + Send + Sync>::from(format!($($tt)*))
504 }
505 }
506
507 #[derive(Debug)]
513 pub struct TempDir(PathBuf);
514
515 impl Drop for TempDir {
516 fn drop(&mut self) {
517 fs::remove_dir_all(&self.0).unwrap();
518 }
519 }
520
521 impl TempDir {
522 pub fn new() -> Result<TempDir> {
525 use std::sync::atomic::{AtomicUsize, Ordering};
526
527 static TRIES: usize = 100;
528 static COUNTER: AtomicUsize = AtomicUsize::new(0);
529
530 let tmpdir = env::temp_dir();
531 for _ in 0..TRIES {
532 let count = COUNTER.fetch_add(1, Ordering::SeqCst);
533 let path = tmpdir.join("rust-ignore").join(count.to_string());
534 if path.is_dir() {
535 continue;
536 }
537 fs::create_dir_all(&path).map_err(|e| {
538 err!("failed to create {}: {}", path.display(), e)
539 })?;
540 return Ok(TempDir(path));
541 }
542 Err(err!("failed to create temp dir after {} tries", TRIES))
543 }
544
545 pub fn path(&self) -> &Path {
547 &self.0
548 }
549 }
550}