2023-01-26 15:04:39 +01:00
#![ feature(option_result_contains) ]
use nom ::{
2023-01-26 15:04:39 +01:00
branch ::alt ,
2023-01-26 15:04:39 +01:00
bytes ::{ complete ::take_while_m_n , streaming ::take_while } ,
combinator ::{ map_res , rest } ,
sequence ::tuple ,
IResult ,
} ;
2023-01-26 15:04:39 +01:00
use serde ::Deserialize ;
2023-01-26 15:04:39 +01:00
use std ::{
2023-01-26 15:04:39 +01:00
collections ::HashMap ,
2023-01-26 15:04:39 +01:00
fmt ::{ self , Display } ,
str ::FromStr ,
} ;
2022-11-11 15:00:07 +01:00
2023-01-26 15:04:39 +01:00
type CardFilter = Box < dyn Fn ( & SearchCard ) -> bool > ;
2022-11-11 15:00:07 +01:00
fn main ( ) -> Result < ( ) , Box < dyn std ::error ::Error > > {
2023-01-26 15:04:39 +01:00
let cards = serde_json ::from_reader ::< _ , CardInfo > ( std ::io ::BufReader ::new ( std ::fs ::File ::open ( " cards.json " ) ? ) ) ? . data ;
2023-01-26 15:04:39 +01:00
let search_cards : Vec < _ > = cards . iter ( ) . map ( SearchCard ::from ) . collect ( ) ;
let cards_by_id : HashMap < _ , _ > = cards . into_iter ( ) . map ( | c | ( c . id , c ) ) . collect ( ) ;
let mut query = Vec ::new ( ) ;
for q in std ::env ::args ( ) . skip ( 1 ) {
match query_arg ( & q ) {
Ok ( ( _ , filter ) ) = > query . push ( filter ) ,
Err ( e ) = > Err ( format! ( " Malformed query fragment {q} : {e:?} " ) ) ? ,
}
}
2023-01-26 15:04:39 +01:00
2023-01-26 15:04:39 +01:00
search_cards . iter ( ) . filter ( | card | query . iter ( ) . all ( | q | q ( card ) ) ) . for_each ( | c | println! ( " {} " , cards_by_id . get ( & c . id ) . unwrap ( ) ) ) ;
2023-01-26 15:04:39 +01:00
2022-11-11 15:00:07 +01:00
Ok ( ( ) )
2022-10-05 17:57:01 +02:00
}
2023-01-26 15:04:39 +01:00
fn query_arg ( input : & str ) -> IResult < & str , CardFilter > {
alt ( ( map_res ( tuple ( ( field , operator , value ) ) , | t | build_filter ( t ) ) , map_res ( rest , | q : & str | fallback_filter ( q ) ) ) ) ( input )
}
fn fallback_filter ( query : & str ) -> Result < CardFilter , String > {
if query . contains ( & OPERATOR_CHARS [ .. ] ) {
return Err ( format! ( " Invalid query: {query} " ) ) ;
}
println! ( " Trying to match {} as card name " , query ) ;
let q = query . to_lowercase ( ) ;
Ok ( Box ::new ( move | card : & SearchCard | card . name . contains ( & q ) ) )
2023-01-26 15:04:39 +01:00
}
2023-01-26 15:04:39 +01:00
fn build_filter ( query : ( Field , Operator , Value ) ) -> Result < CardFilter , String > {
dbg! ( & query ) ;
Ok ( match query {
2023-01-26 15:04:39 +01:00
( 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 ) ) ,
( Field ::Level , op , Value ::Numerical ( n ) ) = > Box ::new ( move | card | op . filter_number ( card . level , n ) ) ,
2023-01-26 15:04:39 +01:00
( Field ::Type , Operator ::Equals , Value ::String ( s ) ) = > Box ::new ( move | card | card . r#type = = s ) ,
( Field ::Class , Operator ::Equals , Value ::String ( s ) ) = > Box ::new ( move | card | card . card_type . contains ( & s ) ) ,
q = > Err ( format! ( " unknown query: {q:?} " ) ) ? ,
} )
2023-01-26 15:04:39 +01:00
}
fn field ( input : & str ) -> IResult < & str , Field > {
map_res ( take_while ( char ::is_alphabetic ) , str ::parse ) ( input )
}
const OPERATOR_CHARS : & [ char ] = & [ '=' , '<' , '>' , ':' ] ;
fn operator ( input : & str ) -> IResult < & str , Operator > {
map_res ( take_while_m_n ( 1 , 2 , | c | OPERATOR_CHARS . contains ( & c ) ) , str ::parse ) ( input )
}
fn value ( input : & str ) -> IResult < & str , Value > {
map_res ( rest , | i : & str | match i . parse ( ) {
2023-01-26 15:04:39 +01:00
Ok ( n ) = > Ok ( Value ::Numerical ( n ) ) ,
Err ( _ ) if i . is_empty ( ) = > Err ( " empty filter argument " ) ,
Err ( _ ) = > Ok ( Value ::String ( i . to_lowercase ( ) ) ) ,
2023-01-26 15:04:39 +01:00
} ) ( input )
}
#[ derive(Debug, PartialEq, Eq, Clone, Copy) ]
enum Field {
Atk ,
Def ,
Level ,
Type ,
Attribute ,
Class ,
}
impl FromStr for Field {
type Err = String ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
Ok ( match s . to_lowercase ( ) . as_ref ( ) {
" atk " = > Self ::Atk ,
" def " = > Self ::Def ,
" level " | " l " = > Self ::Level ,
" type " | " t " = > Self ::Type ,
" attribute " | " attr " | " a " = > Self ::Attribute ,
" c " | " class " = > Self ::Class ,
_ = > Err ( s . to_string ( ) ) ? ,
} )
}
}
#[ derive(Debug, PartialEq, Eq, Clone, Copy) ]
enum Operator {
Equals ,
Less ,
LessEqual ,
Greater ,
GreaterEqual ,
}
impl Operator {
pub fn filter_number ( & self , a : Option < i32 > , b : i32 ) -> bool {
if let Some ( a ) = a {
match self {
Self ::Equals = > a = = b ,
Self ::Less = > a < b ,
Self ::LessEqual = > a < = b ,
Self ::Greater = > a > b ,
Self ::GreaterEqual = > a > = b ,
}
} else {
false
}
}
}
impl FromStr for Operator {
type Err = String ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
Ok ( match s {
" = " | " == " | " : " = > Self ::Equals ,
" >= " | " => " = > Self ::GreaterEqual ,
" <= " | " =< " = > Self ::LessEqual ,
" > " = > Self ::Greater ,
" < " = > Self ::Less ,
_ = > Err ( s . to_owned ( ) ) ? ,
} )
}
}
#[ derive(Debug, PartialEq, Eq, Clone) ]
enum Value {
String ( String ) ,
Numerical ( i32 ) ,
2022-10-05 17:57:01 +02:00
}
2023-01-26 15:04:39 +01:00
#[ derive(Debug, Deserialize, PartialEq, Eq, Clone) ]
2023-01-26 15:04:39 +01:00
struct CardInfo {
data : Vec < Card > ,
2023-01-26 15:04:39 +01:00
}
2023-01-26 15:04:39 +01:00
#[ derive(Debug, Deserialize, PartialEq, Eq, Clone, Default) ]
struct Card {
2023-01-26 15:04:39 +01:00
id : usize ,
#[ serde(rename = " type " ) ]
card_type : String ,
2023-01-26 15:04:39 +01:00
name : String ,
#[ serde(rename = " desc " ) ]
text : String ,
// Will also be None for ?
atk : Option < i32 > ,
def : Option < i32 > ,
attribute : Option < String > ,
2023-01-26 15:04:39 +01:00
#[ serde(rename = " race " ) ]
2023-01-26 15:04:39 +01:00
r#type : String ,
2023-01-26 15:04:39 +01:00
// also includes rank
2023-01-26 15:04:39 +01:00
level : Option < i32 > ,
#[ serde(rename = " linkval " ) ]
link_rating : Option < i32 > ,
2023-01-26 15:04:39 +01:00
#[ serde(rename = " linkmarkers " ) ]
link_arrows : Option < Vec < String > > ,
}
/// A struct derived from `Card` that has all fields lowercased for easier search
#[ derive(Debug, PartialEq, Eq, Clone) ]
struct SearchCard {
id : usize ,
card_type : String ,
name : String ,
text : String ,
atk : Option < i32 > ,
def : Option < i32 > ,
attribute : Option < String > ,
r#type : String ,
// also includes rank
level : Option < i32 > ,
link_rating : Option < i32 > ,
link_arrows : Option < Vec < String > > ,
}
impl From < & Card > for SearchCard {
fn from ( card : & Card ) -> Self {
Self {
id : card . id ,
card_type : card . card_type . to_lowercase ( ) ,
name : card . name . to_lowercase ( ) ,
text : card . text . to_lowercase ( ) ,
atk : card . atk ,
def : card . def ,
attribute : card . attribute . as_ref ( ) . map ( | s | s . to_lowercase ( ) ) ,
r#type : card . r#type . to_lowercase ( ) ,
level : card . level ,
link_rating : card . link_rating ,
link_arrows : card . link_arrows . as_ref ( ) . map ( | arrows | arrows . iter ( ) . map ( | a | a . to_lowercase ( ) ) . collect ( ) ) ,
}
}
2022-10-05 17:57:01 +02:00
}
2023-01-26 15:04:39 +01:00
impl Display for Card {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
write! ( f , " {} ( " , & self . name ) ? ;
if let Some ( level ) = self . level {
write! ( f , " Level {level} " ) ? ;
} else if let Some ( lr ) = self . link_rating {
write! ( f , " Link {lr} " ) ? ;
}
if let Some ( attr ) = & self . attribute {
write! ( f , " {attr}/ " ) ? ;
}
2023-01-26 15:04:39 +01:00
write! ( f , " {} {}) " , self . r#type , self . card_type ) ? ;
2023-01-26 15:04:39 +01:00
if self . card_type . contains ( & String ::from ( " Monster " ) ) {
match ( self . atk , self . def ) {
( Some ( atk ) , Some ( def ) ) = > write! ( f , " {atk} ATK / {def} DEF " ) ? ,
( Some ( atk ) , None ) if self . link_rating . is_some ( ) = > write! ( f , " {atk} ATK " ) ? ,
( None , Some ( def ) ) = > write! ( f , " ? ATK / {def} DEF " ) ? ,
( Some ( atk ) , None ) = > write! ( f , " {atk} ATK / ? DEF " ) ? ,
( None , None ) = > write! ( f , " ? ATK / ? DEF " ) ? ,
}
}
Ok ( ( ) )
}
2022-10-05 17:57:01 +02:00
}
2023-01-26 15:04:39 +01:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2022-10-05 17:57:01 +02:00
2023-01-26 15:04:39 +01:00
const RAW_SPELL : & str = r #"
{
" id " : 41142615 ,
" name " : " The Cheerful Coffin " ,
" type " : " Spell Card " ,
" desc " : " Discard up to 3 Monster Cards from your hand to the Graveyard. " ,
" race " : " Normal "
} " #;
const RAW_MONSTER : & str = r #"
{
" id " : 2326738 ,
" name " : " Des Lacooda " ,
" type " : " Effect Monster " ,
" desc " : " Once per turn: You can change this card to face-down Defense Position. When this card is Flip Summoned: Draw 1 card. " ,
" atk " : 500 ,
" def " : 600 ,
" level " : 3 ,
" race " : " Zombie " ,
" attribute " : " EARTH "
} " #;
2023-01-26 15:04:39 +01:00
#[ test ]
fn test_spell ( ) {
2023-01-26 15:04:39 +01:00
let coffin : Card = serde_json ::from_str ( RAW_SPELL ) . unwrap ( ) ;
2023-01-26 15:04:39 +01:00
assert_eq! (
coffin ,
2023-01-26 15:04:39 +01:00
Card {
2023-01-26 15:04:39 +01:00
id : 41142615 ,
card_type : " Spell Card " . to_owned ( ) ,
2023-01-26 15:04:39 +01:00
name : " The Cheerful Coffin " . to_owned ( ) ,
text : " Discard up to 3 Monster Cards from your hand to the Graveyard. " . to_owned ( ) ,
r#type : " Normal " . to_owned ( ) ,
.. Default ::default ( )
2023-01-26 15:04:39 +01:00
}
)
}
#[ test ]
fn test_monster ( ) {
2023-01-26 15:04:39 +01:00
let munch : Card = serde_json ::from_str ( RAW_MONSTER ) . unwrap ( ) ;
2023-01-26 15:04:39 +01:00
assert_eq! (
munch ,
2023-01-26 15:04:39 +01:00
Card {
2023-01-26 15:04:39 +01:00
id : 2326738 ,
card_type : " Effect Monster " . to_owned ( ) ,
2023-01-26 15:04:39 +01:00
name : " Des Lacooda " . to_owned ( ) ,
text :
" Once per turn: You can change this card to face-down Defense Position. When this card is Flip Summoned: Draw 1 card. "
. to_owned ( ) ,
atk : Some ( 500 ) ,
def : Some ( 600 ) ,
level : Some ( 3 ) ,
r#type : " Zombie " . to_owned ( ) ,
attribute : Some ( " EARTH " . to_owned ( ) ) ,
.. Default ::default ( )
2023-01-26 15:04:39 +01:00
} ,
)
}
2023-01-26 15:04:39 +01:00
#[ test ]
fn level_filter_test ( ) {
let lacooda = SearchCard ::from ( & serde_json ::from_str ::< Card > ( RAW_MONSTER ) . unwrap ( ) ) ;
let filter_level_3 = query_arg ( " l=3 " ) . unwrap ( ) . 1 ;
assert! ( filter_level_3 ( & lacooda ) ) ;
let filter_level_5 = query_arg ( " l=5 " ) . unwrap ( ) . 1 ;
assert! ( ! filter_level_5 ( & lacooda ) ) ;
let filter_level_incorrect = query_arg ( " l===5 " ) . unwrap ( ) . 1 ;
assert! ( ! filter_level_incorrect ( & lacooda ) ) ;
}
2022-10-05 17:57:01 +02:00
}