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