add readable query and basic styling
This commit is contained in:
parent
e94930d767
commit
bc276ddd5d
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -204,6 +204,7 @@ name = "aro"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"itertools",
|
||||||
"nom",
|
"nom",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -306,6 +307,12 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.31"
|
version = "0.8.31"
|
||||||
@ -458,6 +465,15 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -8,6 +8,7 @@ serde_json = "1.0"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
actix-web = { version = "4.3.0", default_features = false, features = ["macros"] }
|
actix-web = { version = "4.3.0", default_features = false, features = ["macros"] }
|
||||||
|
itertools = "0.10.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-case = "2.2.2"
|
test-case = "2.2.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
data::Card,
|
data::Card,
|
||||||
parser::{Field, Operator, Value, OPERATOR_CHARS},
|
parser::{Field, Operator, RawCardFilter, Value, OPERATOR_CHARS},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A struct derived from `Card` that has all fields lowercased for easier search
|
/// A struct derived from `Card` that has all fields lowercased for easier search
|
||||||
@ -39,35 +39,36 @@ impl From<&Card> for SearchCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub type CardFilter = Box<dyn Fn(&SearchCard) -> bool>;
|
pub type CardFilter = Box<dyn Fn(&SearchCard) -> bool>;
|
||||||
pub type RawCardFilter = (Field, Operator, Value);
|
|
||||||
|
|
||||||
pub fn fallback_filter(query: &str) -> Result<RawCardFilter, String> {
|
pub fn fallback_filter(query: &str) -> Result<RawCardFilter, String> {
|
||||||
if query.contains(OPERATOR_CHARS) {
|
if query.contains(OPERATOR_CHARS) {
|
||||||
return Err(format!("Invalid query: {query}"));
|
return Err(format!("Invalid query: {query}"));
|
||||||
}
|
}
|
||||||
let q = query.to_lowercase();
|
let q = query.to_lowercase();
|
||||||
Ok((Field::Name, Operator::Equal, Value::String(q)))
|
Ok(RawCardFilter(Field::Name, Operator::Equal, Value::String(q)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_filter(query: RawCardFilter) -> Result<CardFilter, String> {
|
pub fn build_filter(query: RawCardFilter) -> Result<CardFilter, String> {
|
||||||
Ok(match query {
|
Ok(match query {
|
||||||
(Field::Atk, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.atk, n)),
|
RawCardFilter(Field::Atk, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.atk, n)),
|
||||||
(Field::Def, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.def, n)),
|
RawCardFilter(Field::Def, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.def, n)),
|
||||||
// ? ATK/DEF is modeled as None in the source json. At least for some monsters.
|
// ? ATK/DEF is modeled as None in the source json. At least for some monsters.
|
||||||
// Let’s at least find those.
|
// Let’s at least find those.
|
||||||
(Field::Atk, _, Value::String(s)) if s == "?" => Box::new(move |card| card.atk.is_none() && card.card_type.contains("monster")),
|
RawCardFilter(Field::Atk, _, Value::String(s)) if s == "?" => {
|
||||||
(Field::Def, _, Value::String(s)) if s == "?" => {
|
Box::new(move |card| card.atk.is_none() && card.card_type.contains("monster"))
|
||||||
|
}
|
||||||
|
RawCardFilter(Field::Def, _, Value::String(s)) if s == "?" => {
|
||||||
Box::new(move |card| card.def.is_none() && card.link_rating.is_none() && card.card_type.contains("monster"))
|
Box::new(move |card| card.def.is_none() && card.link_rating.is_none() && card.card_type.contains("monster"))
|
||||||
}
|
}
|
||||||
(Field::Level, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.level, n)),
|
RawCardFilter(Field::Level, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.level, n)),
|
||||||
(Field::Type, Operator::Equal, Value::String(s)) => Box::new(move |card| card.r#type == s),
|
RawCardFilter(Field::Type, Operator::Equal, Value::String(s)) => Box::new(move |card| card.r#type == s),
|
||||||
(Field::Type, Operator::NotEqual, Value::String(s)) => Box::new(move |card| card.r#type != s),
|
RawCardFilter(Field::Type, Operator::NotEqual, Value::String(s)) => Box::new(move |card| card.r#type != s),
|
||||||
(Field::Attribute, Operator::Equal, Value::String(s)) => Box::new(move |card| card.attribute.contains(&s)),
|
RawCardFilter(Field::Attribute, Operator::Equal, Value::String(s)) => Box::new(move |card| card.attribute.contains(&s)),
|
||||||
(Field::Attribute, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.attribute.contains(&s)),
|
RawCardFilter(Field::Attribute, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.attribute.contains(&s)),
|
||||||
(Field::Class, Operator::Equal, Value::String(s)) => Box::new(move |card| card.card_type.contains(&s)),
|
RawCardFilter(Field::Class, Operator::Equal, Value::String(s)) => Box::new(move |card| card.card_type.contains(&s)),
|
||||||
(Field::Class, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.card_type.contains(&s)),
|
RawCardFilter(Field::Class, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.card_type.contains(&s)),
|
||||||
(Field::Text, Operator::Equal, Value::String(s)) => Box::new(move |card| card.text.contains(&s)),
|
RawCardFilter(Field::Text, Operator::Equal, Value::String(s)) => Box::new(move |card| card.text.contains(&s)),
|
||||||
(Field::Name, Operator::Equal, Value::String(s)) => Box::new(move |card| card.name.contains(&s)),
|
RawCardFilter(Field::Name, Operator::Equal, Value::String(s)) => Box::new(move |card| card.name.contains(&s)),
|
||||||
q => Err(format!("unknown query: {q:?}"))?,
|
q => Err(format!("unknown query: {q:?}"))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -81,8 +82,8 @@ mod tests {
|
|||||||
fn level_filter_test() {
|
fn level_filter_test() {
|
||||||
let lacooda = SearchCard::from(&serde_json::from_str::<Card>(RAW_MONSTER).unwrap());
|
let lacooda = SearchCard::from(&serde_json::from_str::<Card>(RAW_MONSTER).unwrap());
|
||||||
let filter_level_3 = parse_filters("l=3").unwrap();
|
let filter_level_3 = parse_filters("l=3").unwrap();
|
||||||
assert!(filter_level_3[0](&lacooda));
|
assert!(filter_level_3[0].1(&lacooda));
|
||||||
let filter_level_5 = parse_filters("l=5").unwrap();
|
let filter_level_5 = parse_filters("l=5").unwrap();
|
||||||
assert!(!filter_level_5[0](&lacooda));
|
assert!(!filter_level_5[0].1(&lacooda));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
src/main.rs
43
src/main.rs
@ -2,6 +2,7 @@
|
|||||||
use actix_web::{get, http::header, web, App, Either, HttpResponse, HttpServer};
|
use actix_web::{get, http::header, web, App, Either, HttpResponse, HttpServer};
|
||||||
use data::{Card, CardInfo};
|
use data::{Card, CardInfo};
|
||||||
use filter::SearchCard;
|
use filter::SearchCard;
|
||||||
|
use itertools::Itertools;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, fmt::Write, fs::File, io::BufReader, net::Ipv4Addr, sync::LazyLock, time::Instant};
|
use std::{collections::HashMap, fmt::Write, fs::File, io::BufReader, net::Ipv4Addr, sync::LazyLock, time::Instant};
|
||||||
|
|
||||||
@ -46,11 +47,33 @@ async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> Resul
|
|||||||
write!(
|
write!(
|
||||||
res,
|
res,
|
||||||
r#"
|
r#"
|
||||||
<html><body>
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
html {{
|
||||||
|
padding-top: 2em;
|
||||||
|
background-color: #060404;
|
||||||
|
color: #ececef;
|
||||||
|
font-family: 'Lato', 'Segoe UI', sans-serif;
|
||||||
|
font-size: 14pt;
|
||||||
|
line-height: 130%;
|
||||||
|
}}
|
||||||
|
body {{
|
||||||
|
background-color: #241e1e;
|
||||||
|
border-radius:2em;
|
||||||
|
padding: 5%;
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
}}
|
||||||
|
em {{
|
||||||
|
font-size: 75%;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
<form action="/">
|
<form action="/">
|
||||||
<label for="fname">Search query:</label><br>
|
<input style="width: 80%" type="text" name="q" id="searchbox" placeholder="Enter search query" value="{}">
|
||||||
<input type="text" name="q" value="{}"><br>
|
<input style="width: 15%; right: 0" type="submit" value="Submit">
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>"#,
|
</form>"#,
|
||||||
match &q {
|
match &q {
|
||||||
Some(q) => q,
|
Some(q) => q,
|
||||||
@ -62,14 +85,20 @@ async fn search(q: Option<Either<web::Query<Query>, web::Form<Query>>>) -> Resul
|
|||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let matches: Vec<&Card> = SEARCH_CARDS
|
let matches: Vec<&Card> = SEARCH_CARDS
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|card| query.iter().all(|q| q(card)))
|
.filter(|card| query.iter().all(|(_, q)| q(card)))
|
||||||
.map(|c| CARDS_BY_ID.get(&c.id).unwrap())
|
.map(|c| CARDS_BY_ID.get(&c.id).unwrap())
|
||||||
.take(RESULT_LIMIT)
|
.take(RESULT_LIMIT)
|
||||||
.collect();
|
.collect();
|
||||||
write!(res, "Showing {} results (took {:?})<br/><br/>", matches.len(), now.elapsed())?;
|
write!(
|
||||||
|
res,
|
||||||
|
"<em>Showing {} results where {} (took {:?})</em><br/><hr/><br/>",
|
||||||
|
matches.len(),
|
||||||
|
query.iter().map(|(f, _)| f.to_string()).join(" and "),
|
||||||
|
now.elapsed()
|
||||||
|
)?;
|
||||||
for card in matches {
|
for card in matches {
|
||||||
res.push_str(&card.to_string());
|
res.push_str(&card.to_string());
|
||||||
res.push_str("<br/><br/>");
|
res.push_str("<br/><hr/><br/>");
|
||||||
}
|
}
|
||||||
write!(res, "</body></html>")?;
|
write!(res, "</body></html>")?;
|
||||||
} else {
|
} else {
|
||||||
|
113
src/parser.rs
113
src/parser.rs
@ -1,21 +1,24 @@
|
|||||||
use std::str::FromStr;
|
use std::{
|
||||||
|
fmt::{self, Display},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::filter::{build_filter, fallback_filter, CardFilter, RawCardFilter};
|
use crate::filter::{build_filter, fallback_filter, CardFilter};
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{take_until1, take_while, take_while_m_n},
|
bytes::complete::{take_until1, take_while, take_while_m_n},
|
||||||
character::complete::{char, multispace0},
|
character::complete::{char, multispace0},
|
||||||
combinator::{complete, map_res, rest, verify},
|
combinator::{complete, map, map_res, rest, verify},
|
||||||
multi::many_m_n,
|
multi::many_m_n,
|
||||||
sequence::{delimited, preceded, tuple},
|
sequence::{delimited, preceded, tuple},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse_filters(input: &str) -> Result<Vec<CardFilter>, String> {
|
pub fn parse_filters(input: &str) -> Result<Vec<(RawCardFilter, CardFilter)>, String> {
|
||||||
parse_raw_filters(input).map_err(|e| format!("Error while parsing filters “{input}”: {e:?}")).and_then(|(rest, mut v)| {
|
parse_raw_filters(input).map_err(|e| format!("Error while parsing filters “{input}”: {e:?}")).and_then(|(rest, mut v)| {
|
||||||
if rest.is_empty() {
|
if rest.is_empty() {
|
||||||
v.sort_unstable_by_key(|(f, _, _)| *f as u8);
|
v.sort_unstable_by_key(|RawCardFilter(f, _, _)| *f as u8);
|
||||||
v.into_iter().map(build_filter).collect()
|
v.into_iter().map(|r| build_filter(r.clone()).map(|f| (r, f))).collect()
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Input was not fully parsed. Left over: “{rest}”"))
|
Err(format!("Input was not fully parsed. Left over: “{rest}”"))
|
||||||
}
|
}
|
||||||
@ -31,7 +34,10 @@ fn word_non_empty(input: &str) -> IResult<&str, &str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_raw_filter(input: &str) -> IResult<&str, RawCardFilter> {
|
fn parse_raw_filter(input: &str) -> IResult<&str, RawCardFilter> {
|
||||||
preceded(multispace0, alt((complete(tuple((field, operator, value))), map_res(word_non_empty, fallback_filter))))(input)
|
preceded(
|
||||||
|
multispace0,
|
||||||
|
alt((map(complete(tuple((field, operator, value))), |(f, o, v)| RawCardFilter(f, o, v)), map_res(word_non_empty, fallback_filter))),
|
||||||
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn field(input: &str) -> IResult<&str, Field> {
|
fn field(input: &str) -> IResult<&str, Field> {
|
||||||
@ -56,14 +62,29 @@ fn value(input: &str) -> IResult<&str, Value> {
|
|||||||
/// This is used to sort filters before applying them.
|
/// This is used to sort filters before applying them.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum Field {
|
pub enum Field {
|
||||||
Text = 0,
|
Atk = 1,
|
||||||
Name = 1,
|
Def = 2,
|
||||||
Class = 2,
|
Level = 3,
|
||||||
Attribute = 3,
|
|
||||||
Type = 4,
|
Type = 4,
|
||||||
Level = 5,
|
Attribute = 5,
|
||||||
Atk = 6,
|
Class = 6,
|
||||||
Def = 7,
|
Name = 7,
|
||||||
|
Text = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Field {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::Text => "text",
|
||||||
|
Self::Name => "name",
|
||||||
|
Self::Class => "card type",
|
||||||
|
Self::Attribute => "attribute",
|
||||||
|
Self::Type => "type",
|
||||||
|
Self::Level => "level/rank",
|
||||||
|
Self::Atk => "ATK",
|
||||||
|
Self::Def => "DEF",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Field {
|
impl FromStr for Field {
|
||||||
@ -124,25 +145,56 @@ impl FromStr for Operator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Operator {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::Equal => "is",
|
||||||
|
Self::NotEqual => "is not",
|
||||||
|
Self::Less => "<",
|
||||||
|
Self::LessEqual => "<=",
|
||||||
|
Self::Greater => ">",
|
||||||
|
Self::GreaterEqual => ">=",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct RawCardFilter(pub Field, pub Operator, pub Value);
|
||||||
|
|
||||||
|
impl Display for RawCardFilter {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{} {} {}", self.0, self.1, self.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
String(String),
|
String(String),
|
||||||
Numerical(i32),
|
Numerical(i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Value {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match &self {
|
||||||
|
Self::String(s) => f.write_str(s),
|
||||||
|
Self::Numerical(n) => write!(f, "{}", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
#[test_case("t=pyro" => Ok(("", (Field::Type, Operator::Equal, Value::String("pyro".into())))))]
|
#[test_case("t=pyro" => Ok(("", RawCardFilter(Field::Type, Operator::Equal, Value::String("pyro".into())))))]
|
||||||
#[test_case("t:PYro" => Ok(("", (Field::Type, Operator::Equal, Value::String("pyro".into())))); "input is lowercased")]
|
#[test_case("t:PYro" => Ok(("", RawCardFilter(Field::Type, Operator::Equal, Value::String("pyro".into())))); "input is lowercased")]
|
||||||
#[test_case("t==warrior" => Ok(("", (Field::Type, Operator::Equal, Value::String("warrior".into())))))]
|
#[test_case("t==warrior" => Ok(("", RawCardFilter(Field::Type, Operator::Equal, Value::String("warrior".into())))))]
|
||||||
#[test_case("atk>=100" => Ok(("", (Field::Atk, Operator::GreaterEqual, Value::Numerical(100)))))]
|
#[test_case("atk>=100" => Ok(("", RawCardFilter(Field::Atk, Operator::GreaterEqual, Value::Numerical(100)))))]
|
||||||
#[test_case("Necrovalley" => Ok(("", (Field::Name, Operator::Equal, Value::String("necrovalley".into())))))]
|
#[test_case("Necrovalley" => Ok(("", RawCardFilter(Field::Name, Operator::Equal, Value::String("necrovalley".into())))))]
|
||||||
#[test_case("l=10" => Ok(("", (Field::Level, Operator::Equal, Value::Numerical(10)))))]
|
#[test_case("l=10" => Ok(("", RawCardFilter(Field::Level, Operator::Equal, Value::Numerical(10)))))]
|
||||||
#[test_case("Ib" => Ok(("", (Field::Name, Operator::Equal, Value::String("ib".to_owned())))))]
|
#[test_case("Ib" => Ok(("", RawCardFilter(Field::Name, Operator::Equal, Value::String("ib".to_owned())))))]
|
||||||
#[test_case("c!=synchro" => Ok(("", (Field::Class, Operator::NotEqual, Value::String("synchro".to_owned())))))]
|
#[test_case("c!=synchro" => Ok(("", RawCardFilter(Field::Class, Operator::NotEqual, Value::String("synchro".to_owned())))))]
|
||||||
fn successful_parsing_test(input: &str) -> IResult<&str, RawCardFilter> {
|
fn successful_parsing_test(input: &str) -> IResult<&str, RawCardFilter> {
|
||||||
parse_raw_filter(input)
|
parse_raw_filter(input)
|
||||||
}
|
}
|
||||||
@ -159,14 +211,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn sequential_parsing_test() {
|
fn sequential_parsing_test() {
|
||||||
let (rest, filter) = parse_raw_filter("atk>=100 l:4").unwrap();
|
let (rest, filter) = parse_raw_filter("atk>=100 l:4").unwrap();
|
||||||
assert_eq!(filter, (Field::Atk, Operator::GreaterEqual, Value::Numerical(100)));
|
assert_eq!(filter, RawCardFilter(Field::Atk, Operator::GreaterEqual, Value::Numerical(100)));
|
||||||
assert_eq!(parse_raw_filter(rest), Ok(("", (Field::Level, Operator::Equal, Value::Numerical(4)))));
|
assert_eq!(parse_raw_filter(rest), Ok(("", RawCardFilter(Field::Level, Operator::Equal, Value::Numerical(4)))));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_raw_filters("atk>=100 l=4"),
|
parse_raw_filters("atk>=100 l=4"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
vec![(Field::Atk, Operator::GreaterEqual, Value::Numerical(100)), (Field::Level, Operator::Equal, Value::Numerical(4))]
|
vec![
|
||||||
|
RawCardFilter(Field::Atk, Operator::GreaterEqual, Value::Numerical(100)),
|
||||||
|
RawCardFilter(Field::Level, Operator::Equal, Value::Numerical(4))
|
||||||
|
]
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -175,9 +230,9 @@ mod tests {
|
|||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
vec![
|
vec![
|
||||||
(Field::Type, Operator::Equal, Value::String("counter".into())),
|
RawCardFilter(Field::Type, Operator::Equal, Value::String("counter".into())),
|
||||||
(Field::Class, Operator::Equal, Value::String("trap".into())),
|
RawCardFilter(Field::Class, Operator::Equal, Value::String("trap".into())),
|
||||||
(Field::Text, Operator::Equal, Value::String("negate the summon".into())),
|
RawCardFilter(Field::Text, Operator::Equal, Value::String("negate the summon".into())),
|
||||||
]
|
]
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
@ -187,6 +242,6 @@ mod tests {
|
|||||||
fn quoted_value_test() {
|
fn quoted_value_test() {
|
||||||
let (rest, filter) = parse_raw_filter(r#"o:"destroy that target""#).unwrap();
|
let (rest, filter) = parse_raw_filter(r#"o:"destroy that target""#).unwrap();
|
||||||
assert_eq!(rest, "");
|
assert_eq!(rest, "");
|
||||||
assert_eq!(filter, (Field::Text, Operator::Equal, Value::String("destroy that target".into())));
|
assert_eq!(filter, RawCardFilter(Field::Text, Operator::Equal, Value::String("destroy that target".into())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user