diff --git a/src/data.rs b/src/data.rs index 7bbc210..9e1a3c5 100644 --- a/src/data.rs +++ b/src/data.rs @@ -30,7 +30,7 @@ pub struct Card { impl Display for Card { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, r#"

{}


"#, &self.id, &self.name)?; + write!(f, r#"

{}


"#, &self.id, &self.name)?; if let Some(level) = self.level { if self.card_type.contains("XYZ") { f.write_str("Rank ")?; diff --git a/src/main.rs b/src/main.rs index c2819d2..71e976e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,10 @@ async fn main() -> std::io::Result<()> { // tap these so they’re initialized let num_cards = (CARDS_BY_ID.len() + SEARCH_CARDS.len()) / 2; println!("Read {num_cards} cards in {:?}", now.elapsed()); - HttpServer::new(|| App::new().service(search).service(card_info)).bind((Ipv4Addr::from([127, 0, 0, 1]), 8080))?.run().await + HttpServer::new(|| App::new().service(search).service(card_info).service(help)) + .bind((Ipv4Addr::from([127, 0, 0, 1]), 8080))? + .run() + .await } #[derive(Debug, Deserialize)] @@ -37,6 +40,13 @@ struct Query { } const HEADER: &str = include_str!("../static/header.html"); +const HELP_CONTENT: &str = include_str!("../static/help.html"); +const FOOTER: &str = r#"
+Home +      +Query Syntax +
+"#; #[get("/")] async fn search(q: Option, web::Form>>) -> Result> { @@ -57,7 +67,7 @@ async fn search(q: Option, web::Form>>) -> Resul Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res)) } -#[get("/{id}")] +#[get("/card/{id}")] async fn card_info(card_id: web::Path) -> Result> { let mut res = String::with_capacity(2_000); res.push_str(HEADER); @@ -80,6 +90,16 @@ async fn card_info(card_id: web::Path) -> Result Result> { + 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); + Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res)) +} + fn render_searchbox(res: &mut String, query: &Option) -> std::fmt::Result { write!( res, @@ -123,7 +143,7 @@ fn render_results(res: &mut String, query: &str) -> Result<(), Box{card}"#, + r#"{card}"#, card.id, card.id )?; } @@ -131,5 +151,5 @@ fn render_results(res: &mut String, query: &str) -> Result<(), Box") + res.push_str(FOOTER) } diff --git a/src/parser.rs b/src/parser.rs index aca4cc6..4fedbc3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -101,6 +101,7 @@ impl FromStr for Field { "c" | "class" => Self::Class, "o" | "eff" | "text" | "effect" | "e" => Self::Text, "lr" | "linkrating" => Self::LinkRating, + "name" => Self::Name, _ => Err(s.to_string())?, }) } diff --git a/static/header.html b/static/header.html index 6e2ee02..c09991a 100644 --- a/static/header.html +++ b/static/header.html @@ -27,6 +27,11 @@ body { text-align: justify; } +code { + font-family: "Hack", "Fira Code", "Courier New", monospace; + background-color: #2a2a2a; +} + .meta { font-size: 75%; color: var(--fg-dim); @@ -76,7 +81,7 @@ td:nth-child(2) { width: 20%; } -h1, h2 { +h2 { margin-block-end: 0; font-weight: normal; } @@ -109,6 +114,17 @@ h2 { display: table; clear: both; } + +/* little floaty thing at the bottom with links to home and help */ +#bottom { + position: fixed; + bottom: 3em; + right: 10%; + background-color: var(--bg2); + z-index: 2; + padding: 1em; + border-radius: 1em; +} diff --git a/static/help.html b/static/help.html new file mode 100644 index 0000000..915c700 --- /dev/null +++ b/static/help.html @@ -0,0 +1,35 @@ +

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. + +

Search fields

+Currently supported search fields are: +
    +
  • atk and def.
  • +
  • The level (or l) of a monster. Note that the search does not distinguish between level and rank, so l:4 will return all monsters that are either level 4 or rank 4.
  • +
  • The linkrating (or lr) of a monster.
  • +
  • The class (or c) which you might call card type. Since “type” already means something else, the search uses class for “Spell”, “Trap”, “Effect”, “XYZ”, etc., so c:link will return all link monsters.
  • +
  • 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.
  • +
+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: +
    +
  • Equality (:, =, or ==) checks if the value is equal to your search. For text fields, this checks if your search is contained in the field, so effect:banish will show all cards that have the word “banish” anywhere in their text.
  • +
  • Inequality (!=) checks if the value is not equal to your search. For text fields, this return cards that do not contain the word you searched.
  • +
  • Comparisons (<, >, <=, >=) check if the value is less than, greater than, less than or equal, and greater than or equal to your search. atk>=4000 will show all cards with an ATK of at least 4000. These operators do not work for text fields.
  • +
+ +

Examples

+