1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//! A Watchexec Filterer implementation for ignore files.
//!
//! This filterer is meant to be used as a backing filterer inside a more complex or complete
//! filterer, and not as a standalone filterer.
//!
//! This is a fairly simple wrapper around the [`ignore_files`] crate, which is probably where you
//! want to look for any detail or to use this outside of Watchexec.

#![doc(html_favicon_url = "https://watchexec.github.io/logo:watchexec.svg")]
#![doc(html_logo_url = "https://watchexec.github.io/logo:watchexec.svg")]
#![warn(clippy::unwrap_used, missing_docs)]
#![deny(rust_2018_idioms)]

use ignore::Match;
use ignore_files::IgnoreFilter;
use normalize_path::NormalizePath;
use tracing::{trace, trace_span};
use watchexec::{error::RuntimeError, filter::Filterer};
use watchexec_events::{Event, FileType, Priority};

/// A Watchexec [`Filterer`] implementation for [`IgnoreFilter`].
#[derive(Clone, Debug)]
pub struct IgnoreFilterer(pub IgnoreFilter);

impl Filterer for IgnoreFilterer {
	/// Filter an event.
	///
	/// This implementation never errors. It returns `Ok(false)` if the event is ignored according
	/// to the ignore files, and `Ok(true)` otherwise. It ignores event priority.
	fn check_event(&self, event: &Event, _priority: Priority) -> Result<bool, RuntimeError> {
		let _span = trace_span!("filterer_check").entered();
		let mut pass = true;

		for (path, file_type) in event.paths() {
			let path = dunce::simplified(path).normalize();
			let path = path.as_path();
			let _span = trace_span!("checking_against_compiled", ?path, ?file_type).entered();
			let is_dir = file_type.map_or(false, |t| matches!(t, FileType::Dir));

			match self.0.match_path(path, is_dir) {
				Match::None => {
					trace!("no match (pass)");
					pass &= true;
				}
				Match::Ignore(glob) => {
					if glob.from().map_or(true, |f| path.strip_prefix(f).is_ok()) {
						trace!(?glob, "positive match (fail)");
						pass &= false;
					} else {
						trace!(?glob, "positive match, but not in scope (ignore)");
					}
				}
				Match::Whitelist(glob) => {
					trace!(?glob, "negative match (pass)");
					pass = true;
				}
			}
		}

		trace!(?pass, "verdict");
		Ok(pass)
	}
}