diff --git a/lib/src/event.rs b/lib/src/event.rs index 1bce012..dccdcc8 100644 --- a/lib/src/event.rs +++ b/lib/src/event.rs @@ -9,6 +9,7 @@ use std::{ collections::HashMap, fmt, + fs::FileType, path::{Path, PathBuf}, process::ExitStatus, }; @@ -28,7 +29,10 @@ pub struct Event { #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum Tag { - Path(PathBuf), + Path { + path: PathBuf, + file_type: Option, + }, FileEventKind(EventKind), Source(Source), Process(u32), @@ -39,7 +43,7 @@ pub enum Tag { impl Tag { pub const fn discriminant_name(&self) -> &'static str { match self { - Tag::Path(_) => "Path", + Tag::Path { .. } => "Path", Tag::FileEventKind(_) => "FileEventKind", Tag::Source(_) => "Source", Tag::Process(_) => "Process", @@ -94,7 +98,7 @@ impl Event { /// Return all paths in the event's tags. pub fn paths(&self) -> impl Iterator { self.tags.iter().filter_map(|p| match p { - Tag::Path(p) => Some(p.as_path()), + Tag::Path { path, .. } => Some(path.as_path()), _ => None, }) } @@ -121,7 +125,24 @@ impl fmt::Display for Event { write!(f, "Event")?; for p in &self.tags { match p { - Tag::Path(p) => write!(f, " path={}", p.display())?, + Tag::Path { path, file_type } => { + write!(f, " path={}", path.display())?; + if let Some(ft) = file_type { + write!( + f, + " filetype={}", + if ft.is_file() { + "file" + } else if ft.is_dir() { + "dir" + } else if ft.is_symlink() { + "symlink" + } else { + "special" + } + )?; + } + } Tag::FileEventKind(kind) => write!(f, " kind={:?}", kind)?, Tag::Source(s) => write!(f, " source={:?}", s)?, Tag::Process(p) => write!(f, " process={}", p)?, diff --git a/lib/src/filter/tagged.rs b/lib/src/filter/tagged.rs index 441bb6b..3003311 100644 --- a/lib/src/filter/tagged.rs +++ b/lib/src/filter/tagged.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use dunce::canonicalize; use ignore::gitignore::{Gitignore, GitignoreBuilder}; +use ignore::Match; use tokio::fs::read_to_string; use tracing::{debug, trace, warn}; use unicase::UniCase; @@ -52,7 +53,8 @@ impl Filterer for TaggedFilterer { impl TaggedFilterer { fn check(&self, event: &Event) -> Result { - // TODO: trace logging + // TODO: tracing with spans + if self.filters.borrow().is_empty() { trace!("no filters, skipping entire check (pass)"); return Ok(true); @@ -72,6 +74,48 @@ impl TaggedFilterer { trace!(?tag, filters=%tag_filters.len(), "found some filters for this tag"); let mut tag_match = true; + + if let Tag::Path { path, file_type } = tag { + let is_dir = file_type.map_or(false, |ft| ft.is_dir()); + + let gc = self.glob_compiled.borrow(); + if let Some(igs) = gc.as_ref() { + trace!(?tag, "checking against compiled Glob filters"); + match igs.matched(path, is_dir) { + Match::None => { + trace!(?tag, "no match (fail)"); + tag_match = false; + } + Match::Ignore(glob) => { + trace!(?tag, ?glob, "positive match (pass)"); + tag_match = true; + } + Match::Whitelist(glob) => { + trace!(?tag, ?glob, "negative match (ignore)"); + } + } + } + + let ngc = self.not_glob_compiled.borrow(); + if let Some(ngs) = ngc.as_ref() { + trace!(?tag, "checking against compiled NotGlob filters"); + match ngs.matched(path, is_dir) { + Match::None => { + trace!(?tag, "no match (pass)"); + tag_match = true; + } + Match::Ignore(glob) => { + trace!(?tag, ?glob, "positive match (fail)"); + tag_match = false; + } + Match::Whitelist(glob) => { + trace!(?tag, ?glob, "negative match (pass)"); + tag_match = true; + } + } + } + } + for filter in &tag_filters { trace!(?filter, ?tag, "checking filter againt tag"); if let Some(app) = self.match_tag(filter, tag)? { @@ -134,7 +178,7 @@ impl TaggedFilterer { trace!(?tag, matcher=?filter.on, "matching filter to tag"); match (tag, filter.on) { (tag, Matcher::Tag) => filter.matches(tag.discriminant_name()), - (Tag::Path(path), Matcher::Path) => { + (Tag::Path { path, .. }, Matcher::Path) => { let resolved = if let Some(ctx) = &filter.in_path { if let Ok(suffix) = path.strip_prefix(ctx) { suffix.strip_prefix("/").unwrap_or(suffix) @@ -152,7 +196,9 @@ impl TaggedFilterer { trace!(?resolved, "resolved path to match filter against"); if matches!(filter.op, Op::Glob | Op::NotGlob) { - todo!("glob match using compiled ignores"); + unreachable!( + "path glob match with match_tag is too late; should be handled above" + ); } else { filter.matches(resolved.to_string_lossy()) } @@ -369,7 +415,7 @@ pub enum Matcher { impl From<&Tag> for Matcher { fn from(tag: &Tag) -> Self { match tag { - Tag::Path(_) => Matcher::Path, + Tag::Path { .. } => Matcher::Path, Tag::FileEventKind(_) => Matcher::FileEventKind, Tag::Source(_) => Matcher::Source, Tag::Process(_) => Matcher::Process, diff --git a/lib/src/fs.rs b/lib/src/fs.rs index e09071c..503cb43 100644 --- a/lib/src/fs.rs +++ b/lib/src/fs.rs @@ -2,6 +2,7 @@ use std::{ collections::{HashMap, HashSet}, + fs::metadata, mem::take, path::PathBuf, sync::{Arc, Mutex}, @@ -236,7 +237,11 @@ fn process_event( tags.push(Tag::FileEventKind(nev.kind)); for path in nev.paths { - tags.push(Tag::Path(dunce::canonicalize(path)?)); + // possibly pull file_type from whatever notify (or the native driver) returns? + tags.push(Tag::Path { + file_type: metadata(&path).ok().map(|m| m.file_type()), + path: dunce::canonicalize(path)?, + }); } if let Some(pid) = nev.attrs.process_id() {