Add negation filters, and filter application
This commit is contained in:
parent
b57fa8b236
commit
2c894266a8
|
@ -36,6 +36,19 @@ pub enum Tag {
|
|||
ProcessCompletion(Option<ExitStatus>),
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub const fn discriminant_name(&self) -> &'static str {
|
||||
match self {
|
||||
Tag::Path(_) => "Path",
|
||||
Tag::FileEventKind(_) => "FileEventKind",
|
||||
Tag::Source(_) => "Source",
|
||||
Tag::Process(_) => "Process",
|
||||
Tag::Signal(_) => "Signal",
|
||||
Tag::ProcessCompletion(_) => "ProcessCompletion",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The general origin of the event.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
|
@ -48,6 +61,19 @@ pub enum Source {
|
|||
Internal,
|
||||
}
|
||||
|
||||
impl fmt::Display for Source {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
Self::Filesystem => "filesystem",
|
||||
Self::Keyboard => "keyboard",
|
||||
Self::Mouse => "mouse",
|
||||
Self::Os => "os",
|
||||
Self::Time => "time",
|
||||
Self::Internal => "internal",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Return all paths in the event's tags.
|
||||
pub fn paths(&self) -> impl Iterator<Item = &Path> {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{error::RuntimeError, event::Event};
|
||||
|
||||
pub mod globset;
|
||||
|
@ -12,3 +14,9 @@ impl Filterer for () {
|
|||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Filterer> Filterer for Arc<T> {
|
||||
fn check_event(&self, event: &Event) -> Result<bool, RuntimeError> {
|
||||
Arc::as_ref(self).check_event(event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,183 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use globset::GlobMatcher;
|
||||
use regex::Regex;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::event::Event;
|
||||
use crate::event::{Event, Tag};
|
||||
use crate::filter::Filterer;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use types::*;
|
||||
|
||||
mod parse;
|
||||
mod types;
|
||||
|
||||
pub struct TaggedFilterer {
|
||||
/// The directory the project is in, its "root".
|
||||
///
|
||||
/// This is used to resolve absolute paths without an `in_path` context.
|
||||
_root: PathBuf,
|
||||
|
||||
/// Where the program is running from.
|
||||
///
|
||||
/// This is used to resolve relative paths without an `in_path` context.
|
||||
_workdir: PathBuf,
|
||||
|
||||
/// All filters that are applied, in order, by matcher.
|
||||
filters: HashMap<Matcher, Vec<Filter>>,
|
||||
}
|
||||
|
||||
impl Filterer for TaggedFilterer {
|
||||
fn check_event(&self, _event: &Event) -> Result<bool, RuntimeError> {
|
||||
todo!()
|
||||
fn check_event(&self, event: &Event) -> Result<bool, RuntimeError> { // TODO: trace logging
|
||||
if self.filters.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
for tag in &event.tags {
|
||||
if let Some(tag_filters) = self.filters.get(&tag.into()) {
|
||||
if tag_filters.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut tag_match = true;
|
||||
for filter in tag_filters {
|
||||
if let Some(app) = self.match_tag(filter, tag)? {
|
||||
if filter.negate {
|
||||
if app {
|
||||
tag_match = true;
|
||||
}
|
||||
} else {
|
||||
tag_match &= app;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !tag_match {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Filter {
|
||||
/// Path the filter applies from.
|
||||
pub in_path: Option<PathBuf>,
|
||||
|
||||
/// Which tag the filter applies to.
|
||||
pub on: Matcher,
|
||||
|
||||
/// The operation to perform on the tag's value.
|
||||
pub op: Op,
|
||||
|
||||
/// The pattern to match against the tag's value.
|
||||
pub pat: Pattern,
|
||||
|
||||
/// If true, a positive match with this filter will override negative matches from previous
|
||||
/// filters on the same tag, and negative matches will be ignored.
|
||||
pub negate: bool,
|
||||
}
|
||||
|
||||
impl TaggedFilterer {
|
||||
fn match_tag(&self, filter: &Filter, tag: &Tag) -> Result<Option<bool>, RuntimeError> {
|
||||
match (tag, filter.on) {
|
||||
(tag, Matcher::Tag) => filter.matches(tag.discriminant_name()),
|
||||
(Tag::Path(_path), Matcher::Path) => todo!("tagged filterer: path matcher"),
|
||||
(Tag::FileEventKind(kind), Matcher::FileEventKind) => filter.matches(format!("{:?}", kind)),
|
||||
(Tag::Source(src), Matcher::Source) => filter.matches(src.to_string()),
|
||||
(Tag::Process(pid), Matcher::Process) => filter.matches(pid.to_string()),
|
||||
(Tag::Signal(_sig), Matcher::Signal) => todo!("tagged filterer: signal matcher"),
|
||||
(Tag::ProcessCompletion(_oes), Matcher::ProcessCompletion) => todo!("tagged filterer: completion matcher"),
|
||||
_ => return Ok(None),
|
||||
}.map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
pub fn matches(&self, subject: impl AsRef<str>) -> Result<bool, RuntimeError> {
|
||||
let subject = subject.as_ref();
|
||||
|
||||
// TODO: cache compiled globs
|
||||
|
||||
match (self.op, &self.pat) {
|
||||
(Op::Equal, Pattern::Exact(pat)) => Ok(subject == pat),
|
||||
(Op::NotEqual, Pattern::Exact(pat)) => Ok(subject != pat),
|
||||
(Op::Regex, Pattern::Regex(pat)) => Ok(pat.is_match(subject)),
|
||||
(Op::NotRegex, Pattern::Regex(pat)) => Ok(!pat.is_match(subject)),
|
||||
(Op::Glob, Pattern::Glob(pat)) => Ok(pat.is_match(subject)),
|
||||
(Op::NotGlob, Pattern::Glob(pat)) => Ok(!pat.is_match(subject)),
|
||||
(Op::InSet, Pattern::Set(set)) => Ok(set.contains(subject)),
|
||||
(Op::InSet, Pattern::Exact(pat)) => Ok(subject == pat),
|
||||
(Op::NotInSet, Pattern::Set(set)) => Ok(!set.contains(subject)),
|
||||
(Op::NotInSet, Pattern::Exact(pat)) => Ok(subject != pat),
|
||||
(op, pat) => {
|
||||
warn!("trying to match pattern {:?} with op {:?}, that cannot work", pat, op);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum Matcher {
|
||||
Tag,
|
||||
Path,
|
||||
FileEventKind,
|
||||
Source,
|
||||
Process,
|
||||
Signal,
|
||||
ProcessCompletion,
|
||||
}
|
||||
|
||||
impl From<&Tag> for Matcher {
|
||||
fn from(tag: &Tag) -> Self {
|
||||
match tag {
|
||||
Tag::Path(_) => Matcher::Path,
|
||||
Tag::FileEventKind(_) => Matcher::FileEventKind,
|
||||
Tag::Source(_) => Matcher::Source,
|
||||
Tag::Process(_) => Matcher::Process,
|
||||
Tag::Signal(_) => Matcher::Signal,
|
||||
Tag::ProcessCompletion(_) => Matcher::ProcessCompletion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Op {
|
||||
Auto, // =
|
||||
Equal, // ==
|
||||
NotEqual, // !=
|
||||
Regex, // ~=
|
||||
NotRegex, // ~!
|
||||
Glob, // *=
|
||||
NotGlob, // *!
|
||||
InSet, // :=
|
||||
NotInSet, // :!
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum Pattern {
|
||||
Exact(String),
|
||||
Regex(Regex),
|
||||
Glob(GlobMatcher),
|
||||
Set(HashSet<String>),
|
||||
}
|
||||
|
||||
impl PartialEq<Self> for Pattern {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Exact(l), Self::Exact(r)) => l == r,
|
||||
(Self::Regex(l), Self::Regex(r)) => l.as_str() == r.as_str(),
|
||||
(Self::Glob(l), Self::Glob(r)) => l.glob() == r.glob(),
|
||||
(Self::Set(l), Self::Set(r)) => l == r,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Pattern {}
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use globset::Glob;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{is_not, tag, tag_no_case, take_while1},
|
||||
character::complete::char,
|
||||
combinator::map_res,
|
||||
sequence::{delimited, tuple},
|
||||
Finish, IResult,
|
||||
};
|
||||
use nom::{Finish, IResult, branch::alt, bytes::complete::{is_not, tag, tag_no_case, take_while1}, character::complete::char, combinator::{map_res, opt}, sequence::{delimited, tuple}};
|
||||
use regex::Regex;
|
||||
|
||||
use super::*;
|
||||
|
@ -59,7 +52,9 @@ impl FromStr for Filter {
|
|||
"==" => Ok(Op::Equal),
|
||||
"!=" => Ok(Op::NotEqual),
|
||||
"~=" => Ok(Op::Regex),
|
||||
"~!" => Ok(Op::NotRegex),
|
||||
"*=" => Ok(Op::Glob),
|
||||
"*!" => Ok(Op::NotGlob),
|
||||
":=" => Ok(Op::InSet),
|
||||
":!" => Ok(Op::NotInSet),
|
||||
"=" => Ok(Op::Auto),
|
||||
|
@ -79,8 +74,8 @@ impl FromStr for Filter {
|
|||
|
||||
fn filter(i: &str) -> IResult<&str, Filter> {
|
||||
map_res(
|
||||
tuple((matcher, op, pattern)),
|
||||
|(m, o, p)| -> Result<_, ()> {
|
||||
tuple((opt(tag("!")), matcher, op, pattern)),
|
||||
|(n, m, o, p)| -> Result<_, ()> {
|
||||
Ok(Filter {
|
||||
in_path: None,
|
||||
on: m,
|
||||
|
@ -94,15 +89,16 @@ impl FromStr for Filter {
|
|||
pat: match (o, m) {
|
||||
// TODO: carry regex/glob errors through
|
||||
(Op::Auto | Op::Glob, Matcher::Path) => {
|
||||
Pattern::Glob(Glob::new(p).map_err(drop)?)
|
||||
Pattern::Glob(Glob::new(p).map_err(drop)?.compile_matcher())
|
||||
}
|
||||
(Op::Equal | Op::NotEqual, _) => Pattern::Exact(p.to_string()),
|
||||
(Op::Glob, _) => Pattern::Glob(Glob::new(p).map_err(drop)?),
|
||||
(Op::Regex, _) => Pattern::Regex(Regex::new(p).map_err(drop)?),
|
||||
(Op::Glob | Op::NotGlob, _) => Pattern::Glob(Glob::new(p).map_err(drop)?.compile_matcher()),
|
||||
(Op::Regex | Op::NotRegex, _) => Pattern::Regex(Regex::new(p).map_err(drop)?),
|
||||
(Op::Auto | Op::InSet | Op::NotInSet, _) => {
|
||||
Pattern::Set(p.split(',').map(|s| s.trim().to_string()).collect())
|
||||
}
|
||||
},
|
||||
negate: n.is_some(),
|
||||
})
|
||||
},
|
||||
)(i)
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
use globset::Glob;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Filter {
|
||||
pub in_path: Option<PathBuf>,
|
||||
pub on: Matcher,
|
||||
pub op: Op,
|
||||
pub pat: Pattern,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Matcher {
|
||||
Tag,
|
||||
Path,
|
||||
FileEventKind,
|
||||
Source,
|
||||
Process,
|
||||
Signal,
|
||||
ProcessCompletion,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Op {
|
||||
Auto, // =
|
||||
Equal, // ==
|
||||
NotEqual, // !=
|
||||
Regex, // ~=
|
||||
Glob, // *=
|
||||
InSet, // :=
|
||||
NotInSet, // :!
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum Pattern {
|
||||
Exact(String),
|
||||
Regex(Regex),
|
||||
Glob(Glob),
|
||||
Set(HashSet<String>),
|
||||
}
|
||||
|
||||
impl PartialEq<Self> for Pattern {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Exact(l), Self::Exact(r)) => l == r,
|
||||
(Self::Regex(l), Self::Regex(r)) => l.as_str() == r.as_str(),
|
||||
(Self::Glob(l), Self::Glob(r)) => l == r,
|
||||
(Self::Set(l), Self::Set(r)) => l == r,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Pattern {}
|
Loading…
Reference in New Issue