Skip to content

Commit

Permalink
add front matter
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsdqs committed Feb 8, 2024
1 parent 53984ec commit fc20d62
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 13 deletions.
60 changes: 57 additions & 3 deletions ddk/Dictionary.css
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,62 @@ small {
gap: 0.5em;
}

@media (prefers-color-scheme: dark) .thumbinner {
border-color: #fff5;
background: #fff1;
.list-of-pokemon-gen > summary {
font-size: 1.3em;
font-weight: bold;
}
.list-of-pokemon {
list-style: none;
padding: 0;
display: grid;
grid-template-columns: 1fr;
gap: 0.5em;
}
.list-of-pokemon li {
margin: 0;
background: #0001;
border-radius: 0.5em;
padding: 2px 1em;
display: grid;
align-items: center;
grid-template-columns: 4em 48px 1fr;
gap: 1em;
}
.list-of-pokemon li .dex-id {
text-align: center;
font-feature-settings: 'tnum' 1;
}
.list-of-pokemon li img {
height: 48px;
}
.list-of-pokemon li a {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}

@media (min-width: 600px) {
.list-of-pokemon {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 900px) {
.list-of-pokemon {
grid-template-columns: 1fr 1fr 1fr;
}
}
@media (min-width: 1200px) {
.list-of-pokemon {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
}

@media (prefers-color-scheme: dark) {
.thumbinner {
border-color: #fff5;
background: #fff1;
}
.list-of-pokemon li {
background: #fff1;
}
}
2 changes: 1 addition & 1 deletion ddk/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
<key>DCSDictionaryManufacturerName</key>
<string>cloudwithlightning.net</string>
<key>DCSDictionaryFrontMatterReferenceID</key>
<string>front_back_matter</string>
<string>front-matter</string>
</dict>
</plist>
132 changes: 127 additions & 5 deletions src/gen.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
use crate::index::DexId;
use crate::index::{DexId, Index};
use crate::mon::{MonEntry, MonImage};
use crate::xhtml::XhtmlEscaped;
use anyhow::Context;
use anyhow::{anyhow, bail, Context};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write;

pub fn generate_dictionary(pokemon: &BTreeMap<DexId, MonEntry>) -> anyhow::Result<String> {
pub fn generate_dictionary(
index: &Index,
pokemon: &BTreeMap<DexId, MonEntry>,
) -> anyhow::Result<String> {
let mut out = String::from(
r#"<?xml version="1.0" encoding="UTF-8"?>
<!-- generated file -->
<d:dictionary xmlns="http://www.w3.org/1999/xhtml" xmlns:d="http://www.apple.com/DTDs/DictionaryService-1.0.rng">
"#,
);

generate_front_matter(&mut out, index, pokemon).context("error generating front matter")?;

for (id, mon) in pokemon {
generate_mon(&mut out, mon).with_context(|| format!("error generating entry {id}"))?;
}
Expand All @@ -39,6 +44,116 @@ fn raw(s: &str) -> String {
.replace("</a> <a", "</a>\u{00a0}<wbr/><a")
}

fn roman_numerals(mut i: usize) -> String {
#[rustfmt::skip]
let numerals = &[
(1000, &["M", "MM", "MMM", "?", "?", "?", "?", "?", "?"]),
(100, &["C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"]),
(10, &["X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"]),
(1, &["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]),
];
let mut out = String::new();
for &(ord, num) in numerals {
let x = i / ord;
if x > 0 && x < 10 {
out += num[x - 1];
}
i -= ord * x;
}
out
}

fn generate_front_matter(
out: &mut String,
index: &Index,
pokemon: &BTreeMap<DexId, MonEntry>,
) -> anyhow::Result<()> {
writeln!(
out,
r#"<d:entry id="front-matter" d:title="Pokédex">
<div class="outer-container">
<h1>Pokédex</h1>
<p>An index of {} Pokémon across {} generations.</p>
<ul>"#,
index.pokemon_pages.len(),
index.pokemon_gens.len(),
)?;
for gen in 0..index.pokemon_gens.len() {
let gen = gen + 1;
writeln!(
out,
r#"<li><a href="x-dictionary:r:list-of-pokemon-gen-{gen}">Generation {}</a></li>"#,
text(&roman_numerals(gen)),
)?;
}
writeln!(
out,
r#"</ul>
<hr />
<p style="font-size:smaller">Data from Bulbapedia — CC BY-NC-SA 2.5</p>
</div>
</d:entry>"#
)?;

let mut prev_gen = 0;
for (id, entry) in pokemon {
let gen = index
.pokemon_gens
.iter()
.enumerate()
.find(|(_, i)| i.contains(id))
.map(|(i, _)| i + 1)
.ok_or(anyhow!("could not find generation for {id}"))?;

if gen != prev_gen {
if prev_gen != 0 {
writeln!(out, r#"</ul></div></d:entry>"#)?;
}
prev_gen = gen;

let numerals = roman_numerals(gen);
writeln!(
out,
r#"<d:entry id="list-of-pokemon-gen-{gen}" d:title="Generation {} Pokémon">"#,
text(&numerals)
)?;
writeln!(
out,
r#"<div class="outer-container"><h1>Generation {} Pokémon</h1>"#,
text(&numerals)
)?;
writeln!(out, r#"<ul class="list-of-pokemon">"#)?;
}

let (menu_id, menu_image_id) = pokemon
.get(&id.next())
.and_then(|entry| entry.prev_entry.as_ref())
.or(id
.prev()
.and_then(|id| pokemon.get(&id).and_then(|entry| entry.next_entry.as_ref())))
.ok_or(anyhow!("could not find menu image for {id}"))?;
if menu_id != id {
bail!("missing entry before or after {id}??");
}
let image_url = format!("images/{}", urlencoding::encode(menu_image_id));

writeln!(out, r#"<li data-id="{id}">"#)?;
writeln!(out, r#"<div class="dex-id">{id}</div>"#)?;
writeln!(out, r#"<img src="{}" alt="" />"#, attr(&image_url))?;
writeln!(
out,
r#"<a href="x-dictionary:r:pokemon-{}" class="entry-name">{}</a>"#,
id.0,
text(&entry.name)
)?;
writeln!(out, r#"</li>"#)?;
}

writeln!(out, r#"</ul></div></d:entry>"#)?;

Ok(())
}

fn generate_mon(out: &mut String, mon: &MonEntry) -> anyhow::Result<()> {
writeln!(
out,
Expand All @@ -47,7 +162,9 @@ fn generate_mon(out: &mut String, mon: &MonEntry) -> anyhow::Result<()> {
attr(&mon.name),
)?;

let mut names_seen: BTreeSet<_> = [mon.name.clone(), mon.name_jp_text.clone()].into_iter().collect();
let mut names_seen: BTreeSet<_> = [mon.name.clone(), mon.name_jp_text.clone()]
.into_iter()
.collect();
writeln!(out, r#"<d:index d:value="{}" />"#, attr(&mon.name))?;
writeln!(out, r#"<d:index d:value="{}" />"#, attr(&mon.name_jp_text))?;

Expand All @@ -63,7 +180,12 @@ fn generate_mon(out: &mut String, mon: &MonEntry) -> anyhow::Result<()> {
if names_seen.contains(&name) {
continue;
}
writeln!(out, r#"<d:index d:value="{}" d:anchor="xpointer(//*[@id='pokemon-image-{}'])" />"#, attr(&name), i)?;
writeln!(
out,
r#"<d:index d:value="{}" d:anchor="xpointer(//*[@id='pokemon-image-{}'])" />"#,
attr(&name),
i
)?;
names_seen.insert(name);
}
}
Expand Down
75 changes: 74 additions & 1 deletion src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,23 @@ impl fmt::Display for DexId {
}
}

impl DexId {
pub fn prev(&self) -> Option<DexId> {
if self.0 > 1 {
Some(Self(self.0 - 1))
} else {
None
}
}
pub fn next(&self) -> DexId {
Self(self.0 + 1)
}
}

#[derive(Debug)]
pub struct Index {
pub pokemon_pages: BTreeMap<DexId, String>,
pub pokemon_gens: Vec<Vec<DexId>>,
}

pub fn read_index(fetcher: &Fetcher) -> anyhow::Result<Index> {
Expand All @@ -47,6 +61,7 @@ pub fn read_index(fetcher: &Fetcher) -> anyhow::Result<Index> {

let base_url = Url::parse(POKEMON_INDEX_URL).unwrap();
let mut pokemon_pages = BTreeMap::new();
let mut pokemon_gens: Vec<Vec<_>> = Vec::new();

for tr in doc
.select("tr")
Expand All @@ -61,6 +76,61 @@ pub fn read_index(fetcher: &Fetcher) -> anyhow::Result<Index> {
continue;
};

let generation = {
let parent_table = td
.as_node()
.parent()
.and_then(|node| node.parent())
.and_then(|node| node.parent())
.ok_or(anyhow!("entry is not in a table somehow"))?;

let mut prev_sibling = parent_table.previous_sibling();
while prev_sibling
.as_ref()
.map_or(false, |cursor| cursor.as_element().is_none())
{
prev_sibling = prev_sibling.unwrap().previous_sibling();
}
let gen_header = prev_sibling
.as_ref()
.and_then(|c| c.as_element())
.ok_or(anyhow!("entry table is missing prev sibling"))?;
if &*gen_header.name.local != "h3" {
bail!("entry table does not come after a gen header");
}
let gen_title = prev_sibling.unwrap().text_contents();
let gen_title = gen_title.trim();
if !gen_title.starts_with("Generation ") {
bail!("generation title does not start with “Generation”: {gen_title}");
}
let mut roman_numerals: Vec<_> = gen_title[11..]
.chars()
.map(|c| match c {
'I' => 1,
'V' => 5,
'X' => 10,
'L' => 50,
'C' => 100,
'D' => 500,
'M' => 1000,
_ => 0,
})
.filter(|i| *i != 0)
.collect();

for i in 0..roman_numerals.len() - 1 {
if roman_numerals[i + 1] > roman_numerals[i] {
roman_numerals[i + 1] -= roman_numerals[i];
roman_numerals[i] = 0;
}
}
roman_numerals.into_iter().sum()
};
if pokemon_gens.len() < generation {
pokemon_gens.resize_with(generation, Default::default);
}
pokemon_gens[generation - 1].push(dex_id);

let link = tr
.select_first("a[href$='mon)']")
.map_err(|()| anyhow!("missing link for entry {dex_id}"))?;
Expand All @@ -72,5 +142,8 @@ pub fn read_index(fetcher: &Fetcher) -> anyhow::Result<Index> {
pokemon_pages.insert(dex_id, base_url.join(href).unwrap().to_string());
}

Ok(Index { pokemon_pages })
Ok(Index {
pokemon_pages,
pokemon_gens,
})
}
8 changes: 6 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ fn main() {
eprintln!("{e:#}");
std::process::exit(1);
});
eprintln!("got {} entries", index.pokemon_pages.len());
eprintln!(
"got {} entries and {} generations",
index.pokemon_pages.len(),
index.pokemon_gens.len()
);
eprintln!("loading data");

let pokemon: BTreeMap<_, _> = index
Expand All @@ -76,7 +80,7 @@ fn main() {

eprintln!("generating entries");

let out = generate_dictionary(&pokemon).unwrap_or_else(|e| {
let out = generate_dictionary(&index, &pokemon).unwrap_or_else(|e| {
eprintln!("error generating dictionary: {e:#}");
std::process::exit(1);
});
Expand Down
Loading

0 comments on commit fc20d62

Please sign in to comment.