use crate::model::*; use std::str::FromStr; #[derive(Debug, PartialEq, Eq, Clone)] pub enum QueryFilter { Not(Box), 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 { 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::() .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() )) ); } }