Add parser for filters
This commit is contained in:
parent
6a55f5cc6d
commit
84dc77f787
|
@ -732,6 +732,19 @@ version = "0.24.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
|
@ -954,6 +967,12 @@ dependencies = [
|
|||
"syn 1.0.73",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c835948974f68e0bd58636fc6c5b1fbff7b297e3046f11b3b3c18bbac012c6d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
|
@ -1032,6 +1051,17 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.0.0-pre.12"
|
||||
|
@ -1691,7 +1721,7 @@ checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
|
|||
dependencies = [
|
||||
"dirs 2.0.2",
|
||||
"fnv",
|
||||
"nom",
|
||||
"nom 5.1.2",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
@ -1982,7 +2012,9 @@ dependencies = [
|
|||
"derive_builder",
|
||||
"dunce",
|
||||
"futures",
|
||||
"globset",
|
||||
"miette",
|
||||
"nom 7.0.0",
|
||||
"notify",
|
||||
"once_cell",
|
||||
"regex",
|
||||
|
|
|
@ -21,7 +21,9 @@ clearscreen = "1.0.6"
|
|||
derive_builder = "0.10.2"
|
||||
dunce = "1.0.2"
|
||||
futures = "0.3.16"
|
||||
globset = "0.4.8"
|
||||
miette = "1.0.0-beta.1"
|
||||
nom = "7.0.0"
|
||||
notify = "5.0.0-pre.12"
|
||||
once_cell = "1.8.0"
|
||||
regex = "1.5.4"
|
||||
|
|
|
@ -157,6 +157,15 @@ pub enum RuntimeError {
|
|||
#[error("clear screen: {0}")]
|
||||
#[diagnostic(code(watchexec::runtime::clearscreen))]
|
||||
Clearscreen(#[from] clearscreen::Error),
|
||||
|
||||
/// Error received when a filter cannot be parsed.
|
||||
#[error("cannot parse filter `{src}`: {err:?}")]
|
||||
#[diagnostic(code(watchexec::runtime::filter_parse))]
|
||||
FilterParse {
|
||||
src: String,
|
||||
err: nom::error::ErrorKind,
|
||||
// TODO: use miette's source snippet feature
|
||||
},
|
||||
}
|
||||
|
||||
/// Errors occurring from reconfigs.
|
||||
|
|
|
@ -1,33 +1,160 @@
|
|||
use std::{collections::HashSet, path::PathBuf};
|
||||
use std::{collections::HashSet, path::PathBuf, 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 regex::Regex;
|
||||
|
||||
use crate::event::Tag;
|
||||
use crate::error::RuntimeError;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Filter {
|
||||
pub in_path: Option<PathBuf>,
|
||||
pub on: Tag,
|
||||
pub on: Matcher,
|
||||
pub op: Op,
|
||||
pub pat: Pattern,
|
||||
}
|
||||
|
||||
impl FromStr for Filter {
|
||||
type Err = RuntimeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
fn matcher(i: &str) -> IResult<&str, Matcher> {
|
||||
map_res(
|
||||
alt((
|
||||
tag_no_case("tag"),
|
||||
tag_no_case("path"),
|
||||
tag_no_case("kind"),
|
||||
tag_no_case("source"),
|
||||
tag_no_case("src"),
|
||||
tag_no_case("process"),
|
||||
tag_no_case("signal"),
|
||||
tag_no_case("exit"),
|
||||
)),
|
||||
|m: &str| match m.to_ascii_lowercase().as_str() {
|
||||
"tag" => Ok(Matcher::Tag),
|
||||
"path" => Ok(Matcher::Path),
|
||||
"kind" => Ok(Matcher::FileEventKind),
|
||||
"source" => Ok(Matcher::Source),
|
||||
"src" => Ok(Matcher::Source),
|
||||
"process" => Ok(Matcher::Process),
|
||||
"signal" => Ok(Matcher::Signal),
|
||||
"exit" => Ok(Matcher::ProcessCompletion),
|
||||
m => Err(format!("unknown matcher: {}", m)),
|
||||
},
|
||||
)(i)
|
||||
}
|
||||
|
||||
fn op(i: &str) -> IResult<&str, Op> {
|
||||
map_res(
|
||||
alt((
|
||||
tag("=="),
|
||||
tag("!="),
|
||||
tag("~="),
|
||||
tag("*="),
|
||||
tag(":="),
|
||||
tag(":!"),
|
||||
tag("="),
|
||||
)),
|
||||
|o: &str| match o {
|
||||
"==" => Ok(Op::Equal),
|
||||
"!=" => Ok(Op::NotEqual),
|
||||
"~=" => Ok(Op::Regex),
|
||||
"*=" => Ok(Op::Glob),
|
||||
":=" => Ok(Op::InSet),
|
||||
":!" => Ok(Op::NotInSet),
|
||||
"=" => Ok(Op::Auto),
|
||||
o => Err(format!("unknown op: `{}`", o)),
|
||||
},
|
||||
)(i)
|
||||
}
|
||||
|
||||
fn pattern(i: &str) -> IResult<&str, &str> {
|
||||
alt((
|
||||
// TODO: escapes
|
||||
delimited(char('"'), is_not("\""), char('"')),
|
||||
delimited(char('\''), is_not("'"), char('\'')),
|
||||
take_while1(|_| true),
|
||||
))(i)
|
||||
}
|
||||
|
||||
fn filter(i: &str) -> IResult<&str, Filter> {
|
||||
map_res(
|
||||
tuple((matcher, op, pattern)),
|
||||
|(m, o, p)| -> Result<_, ()> {
|
||||
Ok(Filter {
|
||||
in_path: None,
|
||||
on: m,
|
||||
op: match o {
|
||||
Op::Auto => match m {
|
||||
Matcher::Path => Op::Glob,
|
||||
_ => Op::InSet,
|
||||
},
|
||||
o => o,
|
||||
},
|
||||
pat: match (o, m) {
|
||||
// TODO: carry regex/glob errors through
|
||||
(Op::Auto | Op::Glob, Matcher::Path) => {
|
||||
Pattern::Glob(Glob::new(p).map_err(drop)?)
|
||||
}
|
||||
(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::Auto | Op::InSet | Op::NotInSet, _) => {
|
||||
Pattern::Set(p.split(',').map(|s| s.trim().to_string()).collect())
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
)(i)
|
||||
}
|
||||
|
||||
filter(s)
|
||||
.finish()
|
||||
.map(|(_, f)| f)
|
||||
.map_err(|e| RuntimeError::FilterParse {
|
||||
src: s.to_string(),
|
||||
err: e.code,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
Equal,
|
||||
NotEqual,
|
||||
Regex,
|
||||
Glob,
|
||||
Includes,
|
||||
Excludes,
|
||||
InSet,
|
||||
OutSet,
|
||||
Auto, // =
|
||||
Equal, // ==
|
||||
NotEqual, // !=
|
||||
Regex, // ~=
|
||||
Glob, // *=
|
||||
InSet, // :=
|
||||
NotInSet, // :!
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum Pattern {
|
||||
Exact(String),
|
||||
Regex(Regex),
|
||||
Glob(Glob),
|
||||
Set(HashSet<String>),
|
||||
}
|
||||
|
||||
|
@ -36,6 +163,7 @@ impl PartialEq<Self> for Pattern {
|
|||
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,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue