Add release year and filter for it

This commit is contained in:
kageru 2023-02-02 11:34:58 +01:00
parent eee93f0d6e
commit 892bbb1cc0
7 changed files with 5648 additions and 36 deletions

1
Cargo.lock generated

@ -209,6 +209,7 @@ dependencies = [
"serde",
"serde_json",
"test-case",
"time",
]
[[package]]

@ -9,6 +9,7 @@ serde = { version = "1.0", features = ["derive"] }
nom = "7.1.3"
actix-web = { version = "4.3.0", default_features = false, features = ["macros"] }
itertools = "0.10.5"
time = { version = "0.3.17", features = ["serde", "serde-human-readable"] }
[dev-dependencies]
test-case = "2.2.2"

5569
sets.json Normal file

File diff suppressed because it is too large Load Diff

@ -1,5 +1,8 @@
use serde::Deserialize;
use std::fmt::{self, Display, Write};
use time::Date;
use crate::SETS_BY_NAME;
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct CardInfo {
@ -37,12 +40,22 @@ pub struct CardSet {
pub set_rarity: String,
}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct Set {
pub set_name: String,
pub tcg_date: Option<Date>,
}
impl Card {
pub fn extended_info(&self) -> Result<String, fmt::Error> {
let mut s = String::with_capacity(1000);
s.push_str("<h3>Printings:</h3>");
for printing in &self.card_sets {
write!(s, "{}: {} ({})<br/>", printing.set_name, printing.set_code, printing.set_rarity)?;
write!(s, "{}: {} ({})", printing.set_name, printing.set_code, printing.set_rarity)?;
if let Some(date) = SETS_BY_NAME.get(&printing.set_name.to_lowercase()).and_then(|s| s.tcg_date) {
write!(s, " - {}", date)?;
}
s.push_str("<br/>");
}
Ok(s)
}

@ -1,41 +1,53 @@
use time::Date;
use crate::{
data::Card,
parser::{Field, Operator, RawCardFilter, Value, OPERATOR_CHARS},
SETS_BY_NAME,
};
/// A struct derived from `Card` that has all fields lowercased for easier search
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SearchCard {
pub id: usize,
card_type: String,
name: String,
text: String,
atk: Option<i32>,
def: Option<i32>,
attribute: Option<String>,
r#type: String,
pub 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>>,
sets: Vec<String>,
level: Option<i32>,
link_rating: Option<i32>,
link_arrows: Option<Vec<String>>,
sets: Vec<String>,
original_year: Option<i32>,
}
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()),
sets: card.card_sets.iter().filter_map(|s| s.set_code.split('-').next().map(str::to_lowercase)).collect(),
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()),
sets: card.card_sets.iter().filter_map(|s| s.set_code.split('-').next().map(str::to_lowercase)).collect(),
original_year: card
.card_sets
.iter()
.filter_map(|s| {
SETS_BY_NAME.get(&s.set_name.to_lowercase()).unwrap_or_else(|| panic!("Set {} not found", s.set_name)).tcg_date
})
.map(Date::year)
.min(),
}
}
}
@ -64,6 +76,7 @@ pub fn build_filter(query: RawCardFilter) -> Result<CardFilter, String> {
}
RawCardFilter(Field::Level, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.level, n)),
RawCardFilter(Field::LinkRating, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.link_rating, n)),
RawCardFilter(Field::Year, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.original_year, n)),
RawCardFilter(Field::Type, Operator::Equal, 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),
RawCardFilter(Field::Attribute, Operator::Equal, Value::String(s)) => Box::new(move |card| card.attribute.contains(&s)),

@ -1,10 +1,11 @@
#![feature(option_result_contains, once_cell)]
use actix_web::{get, http::header, web, App, Either, HttpResponse, HttpServer};
use data::{Card, CardInfo};
use data::{Card, CardInfo, Set};
use filter::SearchCard;
use itertools::Itertools;
use serde::Deserialize;
use std::{collections::HashMap, fmt::Write, fs::File, io::BufReader, net::Ipv4Addr, sync::LazyLock, time::Instant};
use time::Date;
mod data;
mod filter;
@ -13,13 +14,24 @@ mod parser;
const RESULT_LIMIT: usize = 100;
static CARDS: LazyLock<Vec<Card>> = LazyLock::new(|| {
serde_json::from_reader::<_, CardInfo>(BufReader::new(File::open("cards.json").expect("cards.json not found")))
let mut cards = serde_json::from_reader::<_, CardInfo>(BufReader::new(File::open("cards.json").expect("cards.json not found")))
.expect("Could not deserialize cards")
.data
.data;
cards.iter_mut().for_each(|c| {
c.card_sets.sort_unstable_by_key(|s| SETS_BY_NAME.get(&s.set_name.to_lowercase()).and_then(|s| s.tcg_date).unwrap_or(Date::MAX))
});
cards
});
static CARDS_BY_ID: LazyLock<HashMap<usize, Card>> =
LazyLock::new(|| CARDS.iter().map(|c| (c.id, Card { text: c.text.replace('\r', "").replace('\n', "<br/>"), ..c.clone() })).collect());
static SEARCH_CARDS: LazyLock<Vec<SearchCard>> = LazyLock::new(|| CARDS.iter().map(SearchCard::from).collect());
static SETS_BY_NAME: LazyLock<HashMap<String, Set>> = LazyLock::new(|| {
serde_json::from_reader::<_, Vec<Set>>(BufReader::new(File::open("sets.json").expect("sets.json not found")))
.expect("Could not deserialize sets")
.into_iter()
.map(|s| (s.set_name.to_lowercase(), s))
.collect()
});
static IMG_HOST: LazyLock<String> = LazyLock::new(|| std::env::var("IMG_HOST").unwrap_or_else(|_| String::new()));

@ -64,14 +64,15 @@ fn value(input: &str) -> IResult<&str, Value> {
pub enum Field {
Atk = 1,
Def = 2,
Level = 3,
LinkRating = 4,
Set = 5,
Type = 6,
Attribute = 7,
Class = 8,
Name = 9,
Text = 10,
Level = 4,
LinkRating = 6,
Year = 8,
Set = 10,
Type = 12,
Attribute = 14,
Class = 16,
Name = 18,
Text = 20,
}
impl Display for Field {
@ -87,6 +88,7 @@ impl Display for Field {
Self::Def => "DEF",
Self::LinkRating => "link rating",
Self::Set => "set",
Self::Year => "year",
})
}
}
@ -105,6 +107,7 @@ impl FromStr for Field {
"lr" | "linkrating" => Self::LinkRating,
"name" => Self::Name,
"set" | "s" => Self::Set,
"year" | "y" => Self::Year,
_ => Err(s.to_string())?,
})
}