mirror of
https://github.com/watchexec/watchexec.git
synced 2024-09-28 22:21:33 +02:00
Match path globs
This commit is contained in:
parent
758ac2dc89
commit
fb4f136c0d
@ -9,6 +9,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt,
|
fmt,
|
||||||
|
fs::FileType,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::ExitStatus,
|
process::ExitStatus,
|
||||||
};
|
};
|
||||||
@ -28,7 +29,10 @@ pub struct Event {
|
|||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Tag {
|
pub enum Tag {
|
||||||
Path(PathBuf),
|
Path {
|
||||||
|
path: PathBuf,
|
||||||
|
file_type: Option<FileType>,
|
||||||
|
},
|
||||||
FileEventKind(EventKind),
|
FileEventKind(EventKind),
|
||||||
Source(Source),
|
Source(Source),
|
||||||
Process(u32),
|
Process(u32),
|
||||||
@ -39,7 +43,7 @@ pub enum Tag {
|
|||||||
impl Tag {
|
impl Tag {
|
||||||
pub const fn discriminant_name(&self) -> &'static str {
|
pub const fn discriminant_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Tag::Path(_) => "Path",
|
Tag::Path { .. } => "Path",
|
||||||
Tag::FileEventKind(_) => "FileEventKind",
|
Tag::FileEventKind(_) => "FileEventKind",
|
||||||
Tag::Source(_) => "Source",
|
Tag::Source(_) => "Source",
|
||||||
Tag::Process(_) => "Process",
|
Tag::Process(_) => "Process",
|
||||||
@ -94,7 +98,7 @@ impl Event {
|
|||||||
/// Return all paths in the event's tags.
|
/// Return all paths in the event's tags.
|
||||||
pub fn paths(&self) -> impl Iterator<Item = &Path> {
|
pub fn paths(&self) -> impl Iterator<Item = &Path> {
|
||||||
self.tags.iter().filter_map(|p| match p {
|
self.tags.iter().filter_map(|p| match p {
|
||||||
Tag::Path(p) => Some(p.as_path()),
|
Tag::Path { path, .. } => Some(path.as_path()),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -121,7 +125,24 @@ impl fmt::Display for Event {
|
|||||||
write!(f, "Event")?;
|
write!(f, "Event")?;
|
||||||
for p in &self.tags {
|
for p in &self.tags {
|
||||||
match p {
|
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::FileEventKind(kind) => write!(f, " kind={:?}", kind)?,
|
||||||
Tag::Source(s) => write!(f, " source={:?}", s)?,
|
Tag::Source(s) => write!(f, " source={:?}", s)?,
|
||||||
Tag::Process(p) => write!(f, " process={}", p)?,
|
Tag::Process(p) => write!(f, " process={}", p)?,
|
||||||
|
@ -4,6 +4,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
use ignore::gitignore::{Gitignore, GitignoreBuilder};
|
use ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||||
|
use ignore::Match;
|
||||||
use tokio::fs::read_to_string;
|
use tokio::fs::read_to_string;
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
@ -52,7 +53,8 @@ impl Filterer for TaggedFilterer {
|
|||||||
|
|
||||||
impl TaggedFilterer {
|
impl TaggedFilterer {
|
||||||
fn check(&self, event: &Event) -> Result<bool, TaggedFiltererError> {
|
fn check(&self, event: &Event) -> Result<bool, TaggedFiltererError> {
|
||||||
// TODO: trace logging
|
// TODO: tracing with spans
|
||||||
|
|
||||||
if self.filters.borrow().is_empty() {
|
if self.filters.borrow().is_empty() {
|
||||||
trace!("no filters, skipping entire check (pass)");
|
trace!("no filters, skipping entire check (pass)");
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
@ -72,6 +74,48 @@ impl TaggedFilterer {
|
|||||||
trace!(?tag, filters=%tag_filters.len(), "found some filters for this tag");
|
trace!(?tag, filters=%tag_filters.len(), "found some filters for this tag");
|
||||||
|
|
||||||
let mut tag_match = true;
|
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 {
|
for filter in &tag_filters {
|
||||||
trace!(?filter, ?tag, "checking filter againt tag");
|
trace!(?filter, ?tag, "checking filter againt tag");
|
||||||
if let Some(app) = self.match_tag(filter, 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");
|
trace!(?tag, matcher=?filter.on, "matching filter to tag");
|
||||||
match (tag, filter.on) {
|
match (tag, filter.on) {
|
||||||
(tag, Matcher::Tag) => filter.matches(tag.discriminant_name()),
|
(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 {
|
let resolved = if let Some(ctx) = &filter.in_path {
|
||||||
if let Ok(suffix) = path.strip_prefix(ctx) {
|
if let Ok(suffix) = path.strip_prefix(ctx) {
|
||||||
suffix.strip_prefix("/").unwrap_or(suffix)
|
suffix.strip_prefix("/").unwrap_or(suffix)
|
||||||
@ -152,7 +196,9 @@ impl TaggedFilterer {
|
|||||||
trace!(?resolved, "resolved path to match filter against");
|
trace!(?resolved, "resolved path to match filter against");
|
||||||
|
|
||||||
if matches!(filter.op, Op::Glob | Op::NotGlob) {
|
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 {
|
} else {
|
||||||
filter.matches(resolved.to_string_lossy())
|
filter.matches(resolved.to_string_lossy())
|
||||||
}
|
}
|
||||||
@ -369,7 +415,7 @@ pub enum Matcher {
|
|||||||
impl From<&Tag> for Matcher {
|
impl From<&Tag> for Matcher {
|
||||||
fn from(tag: &Tag) -> Self {
|
fn from(tag: &Tag) -> Self {
|
||||||
match tag {
|
match tag {
|
||||||
Tag::Path(_) => Matcher::Path,
|
Tag::Path { .. } => Matcher::Path,
|
||||||
Tag::FileEventKind(_) => Matcher::FileEventKind,
|
Tag::FileEventKind(_) => Matcher::FileEventKind,
|
||||||
Tag::Source(_) => Matcher::Source,
|
Tag::Source(_) => Matcher::Source,
|
||||||
Tag::Process(_) => Matcher::Process,
|
Tag::Process(_) => Matcher::Process,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
fs::metadata,
|
||||||
mem::take,
|
mem::take,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
@ -236,7 +237,11 @@ fn process_event(
|
|||||||
tags.push(Tag::FileEventKind(nev.kind));
|
tags.push(Tag::FileEventKind(nev.kind));
|
||||||
|
|
||||||
for path in nev.paths {
|
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() {
|
if let Some(pid) = nev.attrs.process_id() {
|
||||||
|
Loading…
Reference in New Issue
Block a user