Add release year and filter for it
This commit is contained in:
parent
eee93f0d6e
commit
892bbb1cc0
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -209,6 +209,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"test-case",
|
"test-case",
|
||||||
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -9,6 +9,7 @@ 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"
|
itertools = "0.10.5"
|
||||||
|
time = { version = "0.3.17", features = ["serde", "serde-human-readable"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-case = "2.2.2"
|
test-case = "2.2.2"
|
||||||
|
15
src/data.rs
15
src/data.rs
@ -1,5 +1,8 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fmt::{self, Display, Write};
|
use std::fmt::{self, Display, Write};
|
||||||
|
use time::Date;
|
||||||
|
|
||||||
|
use crate::SETS_BY_NAME;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
||||||
pub struct CardInfo {
|
pub struct CardInfo {
|
||||||
@ -37,12 +40,22 @@ pub struct CardSet {
|
|||||||
pub set_rarity: String,
|
pub set_rarity: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Set {
|
||||||
|
pub set_name: String,
|
||||||
|
pub tcg_date: Option<Date>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Card {
|
impl Card {
|
||||||
pub fn extended_info(&self) -> Result<String, fmt::Error> {
|
pub fn extended_info(&self) -> Result<String, fmt::Error> {
|
||||||
let mut s = String::with_capacity(1000);
|
let mut s = String::with_capacity(1000);
|
||||||
s.push_str("<h3>Printings:</h3>");
|
s.push_str("<h3>Printings:</h3>");
|
||||||
for printing in &self.card_sets {
|
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)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,53 @@
|
|||||||
|
use time::Date;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::Card,
|
data::Card,
|
||||||
parser::{Field, Operator, RawCardFilter, Value, OPERATOR_CHARS},
|
parser::{Field, Operator, RawCardFilter, Value, OPERATOR_CHARS},
|
||||||
|
SETS_BY_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 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
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct SearchCard {
|
pub struct SearchCard {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
card_type: String,
|
card_type: String,
|
||||||
name: String,
|
name: String,
|
||||||
text: String,
|
text: String,
|
||||||
atk: Option<i32>,
|
atk: Option<i32>,
|
||||||
def: Option<i32>,
|
def: Option<i32>,
|
||||||
attribute: Option<String>,
|
attribute: Option<String>,
|
||||||
r#type: String,
|
r#type: String,
|
||||||
// also includes rank
|
// also includes rank
|
||||||
level: Option<i32>,
|
level: Option<i32>,
|
||||||
link_rating: Option<i32>,
|
link_rating: Option<i32>,
|
||||||
link_arrows: Option<Vec<String>>,
|
link_arrows: Option<Vec<String>>,
|
||||||
sets: Vec<String>,
|
sets: Vec<String>,
|
||||||
|
original_year: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Card> for SearchCard {
|
impl From<&Card> for SearchCard {
|
||||||
fn from(card: &Card) -> Self {
|
fn from(card: &Card) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: card.id,
|
id: card.id,
|
||||||
card_type: card.card_type.to_lowercase(),
|
card_type: card.card_type.to_lowercase(),
|
||||||
name: card.name.to_lowercase(),
|
name: card.name.to_lowercase(),
|
||||||
text: card.text.to_lowercase(),
|
text: card.text.to_lowercase(),
|
||||||
atk: card.atk,
|
atk: card.atk,
|
||||||
def: card.def,
|
def: card.def,
|
||||||
attribute: card.attribute.as_ref().map(|s| s.to_lowercase()),
|
attribute: card.attribute.as_ref().map(|s| s.to_lowercase()),
|
||||||
r#type: card.r#type.to_lowercase(),
|
r#type: card.r#type.to_lowercase(),
|
||||||
level: card.level,
|
level: card.level,
|
||||||
link_rating: card.link_rating,
|
link_rating: card.link_rating,
|
||||||
link_arrows: card.link_arrows.as_ref().map(|arrows| arrows.iter().map(|a| a.to_lowercase()).collect()),
|
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(),
|
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::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::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::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::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)),
|
RawCardFilter(Field::Attribute, Operator::Equal, Value::String(s)) => Box::new(move |card| card.attribute.contains(&s)),
|
||||||
|
18
src/main.rs
18
src/main.rs
@ -1,10 +1,11 @@
|
|||||||
#![feature(option_result_contains, once_cell)]
|
#![feature(option_result_contains, once_cell)]
|
||||||
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, Set};
|
||||||
use filter::SearchCard;
|
use filter::SearchCard;
|
||||||
use itertools::Itertools;
|
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};
|
||||||
|
use time::Date;
|
||||||
|
|
||||||
mod data;
|
mod data;
|
||||||
mod filter;
|
mod filter;
|
||||||
@ -13,13 +14,24 @@ mod parser;
|
|||||||
const RESULT_LIMIT: usize = 100;
|
const RESULT_LIMIT: usize = 100;
|
||||||
|
|
||||||
static CARDS: LazyLock<Vec<Card>> = LazyLock::new(|| {
|
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")
|
.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>> =
|
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());
|
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 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()));
|
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 {
|
pub enum Field {
|
||||||
Atk = 1,
|
Atk = 1,
|
||||||
Def = 2,
|
Def = 2,
|
||||||
Level = 3,
|
Level = 4,
|
||||||
LinkRating = 4,
|
LinkRating = 6,
|
||||||
Set = 5,
|
Year = 8,
|
||||||
Type = 6,
|
Set = 10,
|
||||||
Attribute = 7,
|
Type = 12,
|
||||||
Class = 8,
|
Attribute = 14,
|
||||||
Name = 9,
|
Class = 16,
|
||||||
Text = 10,
|
Name = 18,
|
||||||
|
Text = 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Field {
|
impl Display for Field {
|
||||||
@ -87,6 +88,7 @@ impl Display for Field {
|
|||||||
Self::Def => "DEF",
|
Self::Def => "DEF",
|
||||||
Self::LinkRating => "link rating",
|
Self::LinkRating => "link rating",
|
||||||
Self::Set => "set",
|
Self::Set => "set",
|
||||||
|
Self::Year => "year",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,6 +107,7 @@ impl FromStr for Field {
|
|||||||
"lr" | "linkrating" => Self::LinkRating,
|
"lr" | "linkrating" => Self::LinkRating,
|
||||||
"name" => Self::Name,
|
"name" => Self::Name,
|
||||||
"set" | "s" => Self::Set,
|
"set" | "s" => Self::Set,
|
||||||
|
"year" | "y" => Self::Year,
|
||||||
_ => Err(s.to_string())?,
|
_ => Err(s.to_string())?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user