use std::path::PathBuf;
use maplit::hashmap;
use serde::Serialize;
use serde_bser::value::Value;
use crate::pdu::*;
#[derive(Serialize, Debug, Clone)]
#[serde(into = "Value")]
pub enum Expr {
True,
False,
Not(Box<Expr>),
All(Vec<Expr>),
Any(Vec<Expr>),
DirName(DirNameTerm),
Empty,
Exists,
Match(MatchTerm),
Name(NameTerm),
Pcre(PcreTerm),
Since(SinceTerm),
Size(RelOp),
Suffix(Vec<PathBuf>),
FileType(FileType),
}
impl From<Expr> for Value {
fn from(val: Expr) -> Self {
match val {
Expr::True => "true".into(),
Expr::False => "false".into(),
Expr::Not(expr) => Value::Array(vec!["not".into(), (*expr).into()]),
Expr::All(expr) => {
let mut expr: Vec<Value> = expr.into_iter().map(Into::into).collect();
expr.insert(0, "allof".into());
Value::Array(expr)
}
Expr::Any(expr) => {
let mut expr: Vec<Value> = expr.into_iter().map(Into::into).collect();
expr.insert(0, "anyof".into());
Value::Array(expr)
}
Expr::DirName(term) => {
let mut expr: Vec<Value> = vec!["dirname".into(), term.path.try_into().unwrap()];
if let Some(depth) = term.depth {
expr.push(depth.into_term("depth"));
}
expr.into()
}
Expr::Empty => "empty".into(),
Expr::Exists => "exists".into(),
Expr::Match(term) => vec![
"match".into(),
term.glob.into(),
if term.wholename {
"wholename"
} else {
"basename"
}
.into(),
Value::Object(hashmap! {
"includedotfiles".to_string() => term.include_dot_files.into(),
"noescape".to_string() => term.no_escape.into()
}),
]
.into(),
Expr::Name(term) => vec![
"name".into(),
Value::Array(
term.paths
.into_iter()
.map(|p| p.try_into().unwrap())
.collect(),
),
if term.wholename {
"wholename"
} else {
"basename"
}
.into(),
]
.into(),
Expr::Pcre(term) => vec![
"pcre".into(),
term.pattern.into(),
if term.wholename {
"wholename"
} else {
"basename"
}
.into(),
]
.into(),
Expr::Since(term) => match term {
SinceTerm::ObservedClock(c) => {
vec!["since".into(), c.into(), "oclock".into()].into()
}
SinceTerm::CreatedClock(c) => {
vec!["since".into(), c.into(), "cclock".into()].into()
}
SinceTerm::MTime(c) => {
vec!["since".into(), c.to_string().into(), "mtime".into()].into()
}
SinceTerm::CTime(c) => {
vec!["since".into(), c.to_string().into(), "ctime".into()].into()
}
},
Expr::Size(term) => term.into_term("size"),
Expr::Suffix(term) => vec![
"suffix".into(),
Value::Array(term.into_iter().map(|p| p.try_into().unwrap()).collect()),
]
.into(),
Expr::FileType(term) => vec!["type".into(), term.to_string().into()].into(),
}
}
}
#[derive(Clone, Debug)]
pub struct NameTerm {
pub paths: Vec<PathBuf>,
pub wholename: bool,
}
#[derive(Clone, Debug)]
pub struct DirNameTerm {
pub path: PathBuf,
pub depth: Option<RelOp>,
}
#[derive(Clone, Debug, Default)]
pub struct PcreTerm {
pub pattern: String,
pub wholename: bool,
}
#[derive(Clone, Debug, Default)]
pub struct MatchTerm {
pub glob: String,
pub wholename: bool,
pub include_dot_files: bool,
pub no_escape: bool,
}
#[derive(Clone, Debug)]
pub enum RelOp {
Equal(usize),
NotEqual(usize),
Greater(usize),
GreaterOrEqual(usize),
Less(usize),
LessOrEqual(usize),
}
impl RelOp {
fn into_term(self, field: &str) -> Value {
let (op, value) = match self {
Self::Equal(value) => ("eq", value),
Self::NotEqual(value) => ("ne", value),
Self::Greater(value) => ("gt", value),
Self::GreaterOrEqual(value) => ("ge", value),
Self::Less(value) => ("lt", value),
Self::LessOrEqual(value) => ("le", value),
};
Value::Array(vec![field.into(), op.into(), value.try_into().unwrap()])
}
}
#[derive(Clone, Debug)]
pub enum SinceTerm {
ObservedClock(ClockSpec),
CreatedClock(ClockSpec),
MTime(i64),
CTime(i64),
}
#[cfg(test)]
mod tests {
use super::*;
fn val(expr: Expr) -> Value {
expr.into()
}
#[test]
fn exprs() {
assert_eq!(val(Expr::True), "true".into());
assert_eq!(val(Expr::False), "false".into());
assert_eq!(val(Expr::Empty), "empty".into());
assert_eq!(val(Expr::Exists), "exists".into());
assert_eq!(
val(Expr::Not(Box::new(Expr::False))),
vec!["not".into(), "false".into()].into()
);
assert_eq!(
val(Expr::All(vec![Expr::True, Expr::False])),
vec!["allof".into(), "true".into(), "false".into()].into()
);
assert_eq!(
val(Expr::Any(vec![Expr::True, Expr::False])),
vec!["anyof".into(), "true".into(), "false".into()].into()
);
assert_eq!(
val(Expr::DirName(DirNameTerm {
path: "foo".into(),
depth: None,
})),
vec!["dirname".into(), Value::ByteString("foo".into())].into()
);
assert_eq!(
val(Expr::DirName(DirNameTerm {
path: "foo".into(),
depth: Some(RelOp::GreaterOrEqual(1)),
})),
vec![
"dirname".into(),
Value::ByteString("foo".into()),
vec!["depth".into(), "ge".into(), 1.into()].into()
]
.into()
);
assert_eq!(
val(Expr::Match(MatchTerm {
glob: "*.txt".into(),
..Default::default()
})),
vec![
"match".into(),
"*.txt".into(),
"basename".into(),
hashmap! {
"includedotfiles".to_string() => Value::Bool(false),
"noescape".to_string() => Value::Bool(false),
}
.into()
]
.into()
);
assert_eq!(
val(Expr::Match(MatchTerm {
glob: "*.txt".into(),
wholename: true,
include_dot_files: true,
..Default::default()
})),
vec![
"match".into(),
"*.txt".into(),
"wholename".into(),
hashmap! {
"includedotfiles".to_string() => Value::Bool(true),
"noescape".to_string() => Value::Bool(false),
}
.into()
]
.into()
);
assert_eq!(
val(Expr::Name(NameTerm {
paths: vec!["foo".into()],
wholename: true,
})),
vec![
"name".into(),
vec![Value::ByteString("foo".into())].into(),
"wholename".into()
]
.into()
);
assert_eq!(
val(Expr::Pcre(PcreTerm {
pattern: "foo$".into(),
wholename: true,
})),
vec!["pcre".into(), "foo$".into(), "wholename".into()].into()
);
assert_eq!(
val(Expr::FileType(FileType::Regular)),
vec!["type".into(), "f".into()].into()
);
assert_eq!(
val(Expr::Suffix(vec!["php".into(), "js".into()])),
vec![
"suffix".into(),
vec![
Value::ByteString("php".into()),
Value::ByteString("js".into())
]
.into()
]
.into()
);
assert_eq!(
val(Expr::Since(SinceTerm::ObservedClock(ClockSpec::null()))),
vec!["since".into(), "c:0:0".into(), "oclock".into()].into()
);
}
}