208 lines
6.3 KiB
Rust
208 lines
6.3 KiB
Rust
use crate::model::*;
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
pub enum QueryFilter {
|
|
Not(Box<QueryFilter>),
|
|
Tag(String),
|
|
SpecialInName(String),
|
|
SpecialBeforeDate(Date),
|
|
SpecialAfterDate(Date),
|
|
SpecialMinResolution(u64),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
pub enum Score {
|
|
RequiredMatch(bool),
|
|
Match(bool),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
pub enum QueryFilterSyntaxError {
|
|
UnknownSpecialFilter,
|
|
ResolutionArgumentInvalid,
|
|
DateArgumentInvalid,
|
|
NameArgumentInvalid,
|
|
ArgumentInvalid,
|
|
}
|
|
|
|
use std::ops::Not;
|
|
impl Not for Score {
|
|
type Output = Score;
|
|
|
|
fn not(self) -> Score {
|
|
match self {
|
|
Score::RequiredMatch(b) => Score::RequiredMatch(!b),
|
|
Score::Match(b) => Score::Match(!b),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl QueryFilter {
|
|
pub fn is_special(&self) -> bool {
|
|
use QueryFilter::*;
|
|
match self {
|
|
Not(inner) => inner.is_special(),
|
|
Tag(_) => false,
|
|
SpecialInName(_) => true,
|
|
SpecialBeforeDate(_) => true,
|
|
SpecialAfterDate(_) => true,
|
|
SpecialMinResolution(_) => true,
|
|
}
|
|
}
|
|
|
|
pub fn score(&self, texture: &Texture) -> Score {
|
|
use QueryFilter::*;
|
|
match self {
|
|
Not(inner) => inner.score(texture).not(),
|
|
|
|
Tag(tag) => Score::Match(
|
|
texture
|
|
.tags
|
|
.iter()
|
|
.find(|tt| tt.to_lowercase() == tag.to_lowercase())
|
|
.is_some(),
|
|
),
|
|
|
|
SpecialInName(name) => Score::RequiredMatch(
|
|
//
|
|
texture.name.contains(name),
|
|
),
|
|
|
|
SpecialBeforeDate(date) => Score::RequiredMatch(texture.added_on <= *date),
|
|
|
|
SpecialAfterDate(date) => Score::RequiredMatch(texture.added_on > *date),
|
|
|
|
SpecialMinResolution(required) => {
|
|
let smaller_resolution = u64::min(texture.resolution.0, texture.resolution.1);
|
|
|
|
Score::RequiredMatch(smaller_resolution >= *required)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for QueryFilter {
|
|
type Err = QueryFilterSyntaxError;
|
|
|
|
fn from_str(input: &str) -> Result<QueryFilter, QueryFilterSyntaxError> {
|
|
const NEGATION_CHAR: char = '!';
|
|
const SPLIT_CHAR: char = ':';
|
|
|
|
if input.is_empty() {
|
|
return Err(QueryFilterSyntaxError::ArgumentInvalid);
|
|
} else if input.starts_with(NEGATION_CHAR) {
|
|
// A Not.
|
|
let inner = Self::from_str(&input[1..])?;
|
|
return Ok(QueryFilter::Not(Box::new(inner)));
|
|
} else if input.contains(SPLIT_CHAR) {
|
|
// A Special Filter
|
|
let mut parts = input.splitn(2, SPLIT_CHAR);
|
|
let filter_name = parts.next().unwrap().to_lowercase();
|
|
let filter_arg = parts.next().unwrap();
|
|
|
|
match filter_name.as_str() {
|
|
"n" | "name" => {
|
|
if filter_arg.is_empty() {
|
|
return Err(QueryFilterSyntaxError::NameArgumentInvalid);
|
|
}
|
|
Ok(QueryFilter::SpecialInName(filter_arg.to_string()))
|
|
}
|
|
"r" | "res" | "resolution" => {
|
|
let num: u64;
|
|
|
|
if filter_arg.ends_with("k") {
|
|
num = *&filter_arg[0..(filter_arg.len() - 1)]
|
|
.parse::<u32>()
|
|
.map(|n| (n as u64) * 1024)
|
|
.map_err(|_e| QueryFilterSyntaxError::ResolutionArgumentInvalid)?;
|
|
} else {
|
|
num = filter_arg
|
|
.parse()
|
|
.map_err(|_e| QueryFilterSyntaxError::ResolutionArgumentInvalid)?;
|
|
}
|
|
|
|
Ok(QueryFilter::SpecialMinResolution(num))
|
|
}
|
|
"a" | "after" => {
|
|
let date = Date::from_str(filter_arg)
|
|
.map_err(|_| QueryFilterSyntaxError::DateArgumentInvalid)?;
|
|
|
|
Ok(QueryFilter::SpecialAfterDate(date))
|
|
}
|
|
"b" | "bef" | "before" => {
|
|
let date = Date::from_str(filter_arg)
|
|
.map_err(|_| QueryFilterSyntaxError::DateArgumentInvalid)?;
|
|
|
|
Ok(QueryFilter::SpecialBeforeDate(date))
|
|
}
|
|
|
|
_ => Err(QueryFilterSyntaxError::UnknownSpecialFilter),
|
|
}
|
|
} else {
|
|
// A Tag
|
|
Ok(QueryFilter::Tag(input.to_string()))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn parsing() {
|
|
assert!(QueryFilter::from_str("cats:meep").is_err());
|
|
assert!(QueryFilter::from_str("name:").is_err());
|
|
assert!(QueryFilter::from_str("res:-400k").is_err());
|
|
assert!(QueryFilter::from_str("res:4647846846846864864846868446864846844684784k").is_err());
|
|
assert!(QueryFilter::from_str("!!!!a:80-50-50").is_err());
|
|
|
|
assert_eq!(
|
|
QueryFilter::from_str("n:hello"),
|
|
Ok(QueryFilter::SpecialInName("hello".to_string()))
|
|
);
|
|
|
|
assert_eq!(
|
|
QueryFilter::from_str("NaMe:hello"),
|
|
Ok(QueryFilter::SpecialInName("hello".to_string()))
|
|
);
|
|
|
|
assert_eq!(
|
|
QueryFilter::from_str("res:4k"),
|
|
Ok(QueryFilter::SpecialMinResolution(4096))
|
|
);
|
|
|
|
assert_eq!(
|
|
QueryFilter::from_str("res:4096"),
|
|
Ok(QueryFilter::SpecialMinResolution(4096))
|
|
);
|
|
|
|
assert_eq!(
|
|
QueryFilter::from_str("a:2019-10-10"),
|
|
Ok(QueryFilter::SpecialAfterDate(Date::new(2019, 10, 10)))
|
|
);
|
|
|
|
assert_eq!(
|
|
QueryFilter::from_str("!before:2019-10-10"),
|
|
Ok(QueryFilter::Not(
|
|
QueryFilter::SpecialBeforeDate(Date::new(2019, 10, 10)).into()
|
|
))
|
|
);
|
|
|
|
assert_eq!(
|
|
QueryFilter::from_str("!before:2019-10-10"),
|
|
Ok(QueryFilter::Not(
|
|
QueryFilter::SpecialBeforeDate(Date::new(2019, 10, 10)).into()
|
|
))
|
|
);
|
|
|
|
assert_eq!(
|
|
QueryFilter::from_str("!Wood"),
|
|
Ok(QueryFilter::Not(
|
|
QueryFilter::Tag("Wood".to_string()).into()
|
|
))
|
|
);
|
|
}
|
|
}
|