implement query_filter stuff.
This commit is contained in:
parent
66fc54cb3e
commit
b53b3af8c0
|
@ -1,93 +1,68 @@
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use crate::model::*;
|
use crate::model::*;
|
||||||
|
|
||||||
|
mod query_filter;
|
||||||
|
pub use self::query_filter::QueryFilterSyntaxError;
|
||||||
|
use self::query_filter::*;
|
||||||
|
|
||||||
pub struct Query {
|
pub struct Query {
|
||||||
filters: Vec<QueryFilter>,
|
filters: Vec<QueryFilter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type QueryParserResult = Result<Query, QuerySyntaxError>;
|
|
||||||
pub enum QuerySyntaxError {
|
|
||||||
UnknownFilter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Query {
|
impl Query {
|
||||||
pub fn parse(input: &[String]) -> QueryParserResult {
|
pub fn search(&self, input: &mut Iterator<Item = &Texture>) -> Vec<Texture> {
|
||||||
|
let mut results: Vec<(u64, &Texture)> = Vec::new();
|
||||||
|
|
||||||
|
// We use pseudo decimal fixed point numbers here.
|
||||||
|
// 1.000_000 = 1_000_000;
|
||||||
|
|
||||||
|
// This is done, since algorithms like quicksort can fail on floats.
|
||||||
|
let required_score = self.required_score() * 1_000_000u64;
|
||||||
|
|
||||||
|
for texture in input {
|
||||||
|
let mut score = 0u64;
|
||||||
|
|
||||||
|
for (pos, filter) in self.filters.iter().enumerate() {
|
||||||
|
match filter.score(texture) {
|
||||||
|
Score::RequiredMatch(true) => (),
|
||||||
|
Score::RequiredMatch(false) => {
|
||||||
|
// skip this texture
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Score::Match(true) => {
|
||||||
|
score += 1_000_000u64 * (1 + (0.1 / f64::sqrt(pos as f64 + 1.0)) as u64);
|
||||||
|
}
|
||||||
|
Score::Match(false) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if score >= required_score {
|
||||||
|
results.push((score, texture))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results.sort_by_key(|(score, _)| *score);
|
||||||
|
|
||||||
|
results
|
||||||
|
.iter()
|
||||||
|
.map(|(_, texture)| Texture::clone(texture))
|
||||||
|
.collect::<Vec<Texture>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn required_score(&self) -> u64 {
|
||||||
|
let non_special = self.filters.iter().filter(|f| !f.is_special()).count() as u64;
|
||||||
|
|
||||||
|
// ceil(non_special / 2)
|
||||||
|
(non_special + 1) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(input: &[String]) -> Result<Query, QueryFilterSyntaxError> {
|
||||||
let mut result = Query { filters: vec![] };
|
let mut result = Query { filters: vec![] };
|
||||||
|
|
||||||
for fltr in input {
|
for fltr in input {
|
||||||
let qryfltr = Self::parse_single(fltr)?;
|
let qryfltr = fltr.parse::<QueryFilter>()?;
|
||||||
|
|
||||||
result.filters.push(qryfltr);
|
result.filters.push(qryfltr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_single(input: &str) -> Result<QueryFilter, QuerySyntaxError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn search(input: &[Texture], query: &Query) -> Vec<Texture> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
enum QueryFilter {
|
|
||||||
Not(Box<QueryFilter>),
|
|
||||||
Tag(String),
|
|
||||||
SpecialInName(String),
|
|
||||||
SpecialBeforeDate(Date),
|
|
||||||
SpecialAfterDate(Date),
|
|
||||||
SpecialMinResolution(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Score {
|
|
||||||
RequiredMatch(bool),
|
|
||||||
Match(bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
use crate::model::*;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub enum QueryFilter {
|
||||||
|
Not(Box<QueryFilter>),
|
||||||
|
Tag(String),
|
||||||
|
SpecialInName(String),
|
||||||
|
SpecialBeforeDate(Date),
|
||||||
|
SpecialAfterDate(Date),
|
||||||
|
SpecialMinResolution(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Score {
|
||||||
|
RequiredMatch(bool),
|
||||||
|
Match(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum QueryFilterSyntaxError {
|
||||||
|
UnknownSpecialFilter,
|
||||||
|
ResolutionArgumentInvalid,
|
||||||
|
DateArgumentInvalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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" => Ok(QueryFilter::SpecialInName(filter_name.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)
|
||||||
|
.ok_or(QueryFilterSyntaxError::DateArgumentInvalid)?;
|
||||||
|
|
||||||
|
Ok(QueryFilter::SpecialAfterDate(date))
|
||||||
|
}
|
||||||
|
"b" | "bef" | "before" => {
|
||||||
|
let date = Date::from_str(filter_arg)
|
||||||
|
.ok_or(QueryFilterSyntaxError::DateArgumentInvalid)?;
|
||||||
|
|
||||||
|
Ok(QueryFilter::SpecialBeforeDate(date))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(QueryFilterSyntaxError::UnknownSpecialFilter),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A Tag
|
||||||
|
Ok(QueryFilter::Tag(input.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue