From 556bbb3a08f8038a04642b8a95a9425b79526ab2 Mon Sep 17 00:00:00 2001 From: kageru Date: Mon, 13 Feb 2023 11:13:07 +0100 Subject: [PATCH] refactor page rendering to use an intermediate object --- src/main.rs | 100 ++++++++++++++++++++++----------------------- static/header.html | 4 +- static/help.html | 7 +++- static/style.css | 2 +- 4 files changed, 58 insertions(+), 55 deletions(-) diff --git a/src/main.rs b/src/main.rs index e2d5291..8dd4991 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,8 @@ mod data; mod filter; mod parser; +type AnyResult = Result>; + // The yearly tins have ~250 cards in them. // I want to be higher than that so the page is usable as a set list. const RESULT_LIMIT: usize = 300; @@ -55,6 +57,13 @@ struct Query { q: String, } +#[derive(Debug)] +struct PageData { + title: String, + query: Option, + body: String, +} + const HEADER: &str = include_str!("../static/header.html"); const HELP_CONTENT: &str = include_str!("../static/help.html"); const FOOTER: &str = r#"
@@ -65,7 +74,7 @@ const FOOTER: &str = r#"
"#; #[get("/")] -async fn search(q: Option, web::Form>>) -> Result> { +async fn search(q: Option, web::Form>>) -> AnyResult { let q = match q { Some(Either::Left(web::Query(Query { q }))) => Some(q), Some(Either::Right(web::Form(Query { q }))) => Some(q), @@ -73,54 +82,43 @@ async fn search(q: Option, web::Form>>) -> Resul } .filter(|s| !s.is_empty()); let mut res = String::with_capacity(10_000); - res.push_str(HEADER); - render_searchbox(&mut res, &q)?; - match q { - Some(q) => render_results(&mut res, &q)?, - None => res.push_str("Enter a query above to search"), - } - finish_document(&mut res); + let data = match q { + Some(q) => compute_results(q)?, + None => PageData { title: "YGO card search".to_owned(), query: None, body: "Enter a query above to search".to_owned() }, + }; + add_data(&mut res, &data)?; Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res)) } #[get("/card/{id}")] -async fn card_info(card_id: web::Path) -> Result> { +async fn card_info(card_id: web::Path) -> AnyResult { let mut res = String::with_capacity(2_000); - res.push_str(HEADER); - render_searchbox(&mut res, &None)?; - match CARDS_BY_ID.get(&card_id) { - Some(card) => { - res.push_str(r#""#); - write!( - res, - r#" -
- - {card} - {} -
"#, + let data = match CARDS_BY_ID.get(&card_id) { + Some(card) => PageData { + title: format!("{} - YGO Card Database", card.name), + query: None, + body: format!( + r#"
{card}{}
"#, IMG_HOST.as_str(), card.id, card.extended_info().unwrap_or_else(|_| String::new()), - )?; - } - None => res.push_str("Card not found"), - } - finish_document(&mut res); + ), + }, + None => PageData { title: "Card not found - YGO Card Database".to_owned(), query: None, body: "Card not found".to_owned() }, + }; + add_data(&mut res, &data)?; Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res)) } #[get("/help")] -async fn help() -> Result> { +async fn help() -> AnyResult { let mut res = String::with_capacity(HEADER.len() + HELP_CONTENT.len() + FOOTER.len() + 250); - res.push_str(HEADER); - render_searchbox(&mut res, &None)?; - res.push_str(HELP_CONTENT); - res.push_str(FOOTER); + let data = PageData { query: None, title: "Query Syntax - YGO Card Database".to_owned(), body: HELP_CONTENT.to_owned() }; + add_data(&mut res, &data)?; Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res)) } -fn render_searchbox(res: &mut String, query: &Option) -> std::fmt::Result { +fn add_searchbox(res: &mut String, query: &Option) -> std::fmt::Result { write!( res, r#" @@ -134,12 +132,13 @@ fn render_searchbox(res: &mut String, query: &Option) -> std::fmt::Resul ) } -fn render_results(res: &mut String, query: &str) -> Result<(), Box> { - let (raw_filters, query) = match parser::parse_filters(query) { +fn compute_results(raw_query: String) -> AnyResult { + let mut body = String::with_capacity(10_000); + let (raw_filters, query) = match parser::parse_filters(&raw_query) { Ok(q) => q, Err(e) => { - write!(res, "Could not parse query: {e:?}")?; - return Ok(()); + let s = format!("Could not parse query: {e:?}"); + return Ok(PageData { title: s.clone(), query: Some(raw_query), body: s }); } }; let now = Instant::now(); @@ -149,30 +148,29 @@ fn render_results(res: &mut String, query: &str) -> Result<(), BoxShowing {} results where {} (took {:?})", - matches.len(), - raw_filters.iter().map(|f| f.to_string()).join(" and "), - now.elapsed() - )?; + let readable_query = format!("Showing {} results where {}", matches.len(), raw_filters.iter().map(|f| f.to_string()).join(" and "),); + write!(body, "{readable_query} (took {:?})", now.elapsed())?; if matches.is_empty() { - return Ok(()); + return Ok(PageData { title: readable_query.clone(), query: Some(raw_query), body }); } - res.push_str("
"); + body.push_str("
"); for card in matches { write!( - res, + body, r#"{card}"#, card.id, IMG_HOST.as_str(), card.id )?; } - res.push_str("
"); - Ok(()) + body.push_str("
"); + Ok(PageData { title: readable_query.clone(), query: Some(raw_query), body }) } -fn finish_document(res: &mut String) { - res.push_str(FOOTER) +fn add_data(res: &mut String, pd: &PageData) -> AnyResult<()> { + res.push_str(&HEADER.replacen("{TITLE}", &pd.title, 1).replacen("{IMG_HOST}", &IMG_HOST, 1)); + add_searchbox(res, &pd.query)?; + res.push_str(&pd.body); + res.push_str(FOOTER); + Ok(()) } diff --git a/static/header.html b/static/header.html index 5a247e1..920a68d 100644 --- a/static/header.html +++ b/static/header.html @@ -1,6 +1,8 @@ - + + +{TITLE} diff --git a/static/help.html b/static/help.html index b0f7944..ac952f4 100644 --- a/static/help.html +++ b/static/help.html @@ -1,6 +1,7 @@

Query Syntax

The syntax is heavily inspired by Scryfall with some changes and a lot fewer features.
-You can filter different characteristics of a card and combine multiple filters into one search. See below for examples. +You can filter different characteristics of a card and combine multiple filters into one search. See below for examples.
+

Search fields

Currently supported search fields are: @@ -12,13 +13,15 @@ Currently supported search fields are:
  • The type (or t) of a card (this is “Warrior”, “Pyro”, “Insect”, etc. for monsters, but also “quick-play”, “counter”, or “normal” for Spells/Traps).
  • The attribute (or attr or a) of a card. This is “Light”, “Dark”, “Earth”, etc.
  • The text (or effect, eff, e, or o) of a card. This is either the effect or flavor text (for normal monsters). For pendulum cards, this searches in both pendulum and monster effects. The o alias is to help my muscle memory coming from Scryfall.
  • -
  • The set (or s) a card was printed in. This considers all printings, not just the original.
  • +
  • The set (or s) a card was printed in. This considers all printings, not just the original, and uses the set code (e.g. ioc for Invasion of Chaos or pote for Power of the Elements).
  • The copies (or legal) you’re allowed to play according to the current banlist.
  • Anything not associated with a search field is interpreted as a search in the card name, so l:4 utopia will show all level/rank 4 monsters with “Utopia” in their name.
    If your search contains spaces (e.g. searching for an effect that says “destroy that target”), the text must be quoted like effect:"destroy that target".

    Note that all fields are case-insensitive, so class:NORMAL is the same as class:Normal or class:normal. +
    +

    Search operators

    The following search operators are supported: diff --git a/static/style.css b/static/style.css index d8caef1..ae8294a 100644 --- a/static/style.css +++ b/static/style.css @@ -69,7 +69,7 @@ h2 { margin-block-end: 0; margin-block-start: 0; font-weight: normal; - font-size: 120%; + font-size: 130%; } .thumb {