aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2024-03-10 18:55:14 +0100
committerAlex Auvolat <alex@adnab.me>2024-03-10 18:55:14 +0100
commita6507a7a77cf8d6f002d58fbf1362d4c0fba1276 (patch)
tree49bc2a7fe0c0616b225304f4bb22753f965d6da1 /src
parent1c6372d1fcb63e906ab38a86ec952ac343288c96 (diff)
downloaddatagengo-a6507a7a77cf8d6f002d58fbf1362d4c0fba1276.tar.gz
datagengo-a6507a7a77cf8d6f002d58fbf1362d4c0fba1276.zip
start web server
Diffstat (limited to 'src')
-rw-r--r--src/example.rs8
-rw-r--r--src/format.rs133
-rw-r--r--src/main.rs31
-rw-r--r--src/server.rs124
4 files changed, 224 insertions, 72 deletions
diff --git a/src/example.rs b/src/example.rs
index 770ac8a..7d20a28 100644
--- a/src/example.rs
+++ b/src/example.rs
@@ -1,10 +1,14 @@
-use std::collections::{HashSet, HashMap};
+use std::collections::{HashMap, HashSet};
use crate::charset::Charset;
use crate::*;
impl Example {
- pub fn gen_furigana<'a>(&mut self, dict_idx: &DictIndex<'a>, overrides: &HashMap<String, String>) {
+ pub fn gen_furigana<'a>(
+ &mut self,
+ dict_idx: &DictIndex<'a>,
+ overrides: &HashMap<String, String>,
+ ) {
use std::fmt::Write;
if let Some(v) = overrides.get(&self.ja) {
diff --git a/src/format.rs b/src/format.rs
index caed70a..3519b13 100644
--- a/src/format.rs
+++ b/src/format.rs
@@ -1,4 +1,5 @@
use std::fs;
+use std::io::Write;
use anyhow::Result;
@@ -11,18 +12,21 @@ use crate::*;
// =====================================================================
pub fn format_batch<'a>(dict_idx: &DictIndex<'a>, count: usize, (i, batch): (usize, &Batch)) {
- format_batch_aux(dict_idx, count, i, batch).expect("format batch");
+ let mut f = io::BufWriter::new(
+ fs::File::create(format!("public/{:03}.html", i)).expect("create batch file"),
+ );
+ format_batch_to(&mut f, dict_idx, count, i, batch).expect("format batch");
}
-fn format_batch_aux<'a>(
+pub fn format_batch_to<'a>(
+ buf: &mut impl Write,
dict_idx: &DictIndex<'a>,
count: usize,
i: usize,
batch: &Batch,
) -> Result<()> {
- let mut f = io::BufWriter::new(fs::File::create(format!("public/{:03}.html", i))?);
write!(
- f,
+ buf,
r#"<!DOCTYPE html>
<html>
<head>
@@ -34,35 +38,35 @@ fn format_batch_aux<'a>(
i
)?;
- writeln!(f, r#"<p><a href="index.html">index</a>"#)?;
+ writeln!(buf, r#"<p><a href="index.html">index</a>"#)?;
for j in 0..count {
if j != i {
- writeln!(f, r#" <a href="{:03}.html">{:03}</a>"#, j, j)?;
+ writeln!(buf, r#" <a href="{:03}.html">{:03}</a>"#, j, j)?;
} else {
- writeln!(f, " {:03}", j)?;
+ writeln!(buf, " {:03}", j)?;
}
}
- writeln!(f, r#"</p>"#)?;
- writeln!(f, "<p>Level: {}</p>", batch.level)?;
+ writeln!(buf, r#"</p>"#)?;
+ writeln!(buf, "<p>Level: {}</p>", batch.level)?;
- write!(f, r#"<p class="ja">"#)?;
+ write!(buf, r#"<p class="ja">"#)?;
let mut ex_prev = Charset::default();
for ex in batch.examples.iter() {
let ex_chars = ex.chars.inter(&batch.chars);
for c in ex_chars.diff(&ex_prev).chars().iter() {
write!(
- f,
+ buf,
r#"<a href="https://jisho.org/search/{}%20%23kanji">{}</a>"#,
c, c
)?;
}
ex_prev = ex_prev.union(&ex_chars);
}
- writeln!(f, r#"</p>"#)?;
+ writeln!(buf, r#"</p>"#)?;
for ex in batch.examples.iter() {
- writeln!(f, "<hr />")?;
- write!(f, r#"<p class="ja ja_main">"#)?;
+ writeln!(buf, "<hr />")?;
+ write!(buf, r#"<p class="ja ja_main">"#)?;
let furi = ex.furigana_markup();
for c in furi.chars() {
let class = if batch.chars.contains(c) {
@@ -78,18 +82,18 @@ fn format_batch_aux<'a>(
};
if let Some(cls) = class {
write!(
- f,
+ buf,
r#"<a href="https://jisho.org/search/{}%20%23kanji" class="{}">{}</a>"#,
c, cls, c
)?;
} else {
- write!(f, "{}", c)?;
+ write!(buf, "{}", c)?;
}
}
- writeln!(f, "</p>")?;
- writeln!(f, r#"<p class="en">{}</p>"#, ex.en)?;
+ writeln!(buf, "</p>")?;
+ writeln!(buf, r#"<p class="en">{}</p>"#, ex.en)?;
- writeln!(f, r#"<details><summary>Explanation</summary>"#)?;
+ writeln!(buf, r#"<details><summary>Explanation</summary>"#)?;
let mut expl_batch = Vec::new();
let mut expl_all = Vec::new();
for word in ex.expl.split(|c| c == ' ' || c == '~') {
@@ -111,26 +115,26 @@ fn format_batch_aux<'a>(
}
}
for be in expl_batch {
- writeln!(f, r#"<p>{}</p>"#, be)?;
+ writeln!(buf, r#"<p>{}</p>"#, be)?;
}
- writeln!(f, r#"<p class="chars">"#)?;
+ writeln!(buf, r#"<p class="chars">"#)?;
for c in ex.chars.inter(&batch.chars).chars().iter() {
writeln!(
- f,
+ buf,
r#"<a href="https://jisho.org/search/{}%20%23kanji">{}</a>"#,
c, c
)?;
}
- writeln!(f, r#"</p>"#)?;
+ writeln!(buf, r#"</p>"#)?;
for be in expl_all {
- writeln!(f, r#"<p>{}</p>"#, be)?;
+ writeln!(buf, r#"<p>{}</p>"#, be)?;
}
- writeln!(f, r#"</details>"#)?;
+ writeln!(buf, r#"</details>"#)?;
}
- writeln!(f, "<hr />")?;
+ writeln!(buf, "<hr />")?;
format_vocab(
- &mut f,
+ buf,
&batch
.extra_vocab
.iter()
@@ -139,7 +143,7 @@ fn format_batch_aux<'a>(
"Extra vocabulary (this level)",
)?;
format_vocab(
- &mut f,
+ buf,
&batch
.extra_vocab
.iter()
@@ -149,42 +153,42 @@ fn format_batch_aux<'a>(
)?;
writeln!(
- f,
+ buf,
r#"<p><strong>Extra examples (reading practice)</strong></p><table class="extratable">"#
)?;
for ex in batch.extra_examples.iter() {
writeln!(
- f,
+ buf,
r#"<tr><td><div class="extra_example"><div class="extra_ja font_ja">{}</div><div class="extra_en">{}</div></div></td></tr>"#,
ex.furigana_markup(),
ex.en
)?;
}
- writeln!(f, r#"</table>"#)?;
+ writeln!(buf, r#"</table>"#)?;
- writeln!(f, "<hr />")?;
- writeln!(f, "<p>\(≧▽≦)/</p>")?;
+ writeln!(buf, "<hr />")?;
+ writeln!(buf, "<p>\(≧▽≦)/</p>")?;
- write!(f, "<div></body></html>")?;
- f.flush()?;
+ write!(buf, "<div></body></html>")?;
+ buf.flush()?;
Ok(())
}
-fn format_vocab(f: &mut impl Write, vocab: &[&JlptVocab], t: &str) -> Result<()> {
+fn format_vocab(buf: &mut impl Write, vocab: &[&JlptVocab], t: &str) -> Result<()> {
if !vocab.is_empty() {
writeln!(
- f,
+ buf,
r#"<p><strong>{}</strong></p><table class="vocabtable">"#,
t
)?;
for v in vocab {
writeln!(
- f,
+ buf,
r#"<tr><td>{}</td><td style="word-break: keep-all">&nbsp;&nbsp;<span class="tab_large font_ja">{}</span>&nbsp;&nbsp;</td><td>{}</td><td class="font_ja" style="word-break: keep-all">{}</td></tr>"#,
v.level, v.kanji, v.en, v.kana
)?;
}
- writeln!(f, "</table>")?;
+ writeln!(buf, "</table>")?;
}
Ok(())
}
@@ -225,8 +229,16 @@ fn dict_str<'a>(qkeb: &str, qreb: Option<&str>, ent: &roxmltree::Node<'a, 'a>) -
pub fn format_index(batches: &[Batch], kanji_levels: &[(String, String)]) -> Result<()> {
let mut f = io::BufWriter::new(fs::File::create("public/index.html")?);
+ format_index_to(&mut f, batches, kanji_levels)
+}
+
+pub fn format_index_to(
+ buf: &mut impl Write,
+ batches: &[Batch],
+ kanji_levels: &[(String, String)],
+) -> Result<()> {
write!(
- f,
+ buf,
r#"<!DOCTYPE html>
<html>
<head>
@@ -237,13 +249,16 @@ pub fn format_index(batches: &[Batch], kanji_levels: &[(String, String)]) -> Res
<body><div class="index_page">"#
)?;
- writeln!(f, r#"<p><a href="about.html">About / How-to</a></p><hr />"#)?;
+ writeln!(
+ buf,
+ r#"<p><a href="about.html">About / How-to</a></p><hr />"#
+ )?;
- writeln!(f, "<table>")?;
- writeln!(f, "<tr><th>Num</th><th>Level</th><th>Kanji</th><th>Examples</th><th>Lesson-1</th><th>Lesson-2</th><th>Ignore</th></tr>")?;
+ writeln!(buf, "<table>")?;
+ writeln!(buf, "<tr><th>Num</th><th>Level</th><th>Kanji</th><th>Examples</th><th>Lesson-1</th><th>Lesson-2</th><th>Ignore</th></tr>")?;
for (i, batch) in batches.iter().enumerate() {
writeln!(
- f,
+ buf,
r#"<tr><td><a href="{:03}.html">{:03}</a></td><td>{}</td><td class="font_ja">{}</td><td>&nbsp;&nbsp;{}</td><td class="font_ja">{}</td><td class="font_ja">{}</td><td class="font_ja">{}</td></tr>"#,
i,
i,
@@ -255,9 +270,9 @@ pub fn format_index(batches: &[Batch], kanji_levels: &[(String, String)]) -> Res
batch.chars_bad.to_string()
)?;
}
- writeln!(f, r#"</table>"#)?;
+ writeln!(buf, r#"</table>"#)?;
- writeln!(f, "<hr />")?;
+ writeln!(buf, "<hr />")?;
let all_chars = Charset::from_iter(
batches
@@ -265,9 +280,9 @@ pub fn format_index(batches: &[Batch], kanji_levels: &[(String, String)]) -> Res
.map(|x| x.chars.chars().iter().copied())
.flatten(),
);
- writeln!(f, "<table>")?;
+ writeln!(buf, "<table>")?;
writeln!(
- f,
+ buf,
r#"<tr><th>Level</th><th>Count</th><th width="60%">Kanji</th><th>Missing kanji</th></tr>"#
)?;
for (lvl, chars) in kanji_levels.iter() {
@@ -277,7 +292,7 @@ pub fn format_index(batches: &[Batch], kanji_levels: &[(String, String)]) -> Res
let chars = Charset::new(chars);
let missing = chars.diff(&all_chars);
writeln!(
- f,
+ buf,
r#"<tr><td>{}</td><td>{}</td><td class="font_ja">{}</td><td><span class="font_ja">{}</span> ({})</td></tr>"#,
lvl,
chars.len(),
@@ -286,17 +301,21 @@ pub fn format_index(batches: &[Batch], kanji_levels: &[(String, String)]) -> Res
missing.len()
)?;
}
- writeln!(f, "</table>")?;
+ writeln!(buf, "</table>")?;
- write!(f, "</div></body></html>")?;
- f.flush()?;
+ write!(buf, "</div></body></html>")?;
+ buf.flush()?;
Ok(())
}
pub fn format_about() -> Result<()> {
let mut f = io::BufWriter::new(fs::File::create("public/about.html")?);
+ format_about_to(&mut f)
+}
+
+pub fn format_about_to(buf: &mut impl Write) -> Result<()> {
write!(
- f,
+ buf,
r#"<!DOCTYPE html>
<html>
<head>
@@ -307,19 +326,19 @@ pub fn format_about() -> Result<()> {
<body>"#
)?;
- writeln!(f, r#"<div class="about_page">"#)?;
+ writeln!(buf, r#"<div class="about_page">"#)?;
writeln!(
- f,
+ buf,
r#"<p><a href="index.html">Back to lessons</a></p><hr />"#
)?;
writeln!(
- f,
+ buf,
"{}",
markdown::to_html(&fs::read_to_string("README.md")?)
)?;
- writeln!(f, r#"</div></body></html>"#)?;
+ writeln!(buf, r#"</div></body></html>"#)?;
Ok(())
}
diff --git a/src/main.rs b/src/main.rs
index b0c46c0..252740b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,6 @@
use std::collections::HashMap;
use std::fs;
-use std::io::{self, Write};
+use std::io;
//use anyhow::Result;
use rand::prelude::*;
@@ -12,6 +12,7 @@ mod charset;
mod datafiles;
mod example;
mod format;
+mod server;
use charset::Charset;
use datafiles::*;
use format::*;
@@ -39,9 +40,11 @@ enum Cmd {
AddExamples,
AddFurigana,
Format,
+ Server,
}
-fn main() {
+#[async_std::main]
+async fn main() {
let opt = Opt::from_args();
match opt.cmd {
@@ -158,7 +161,6 @@ fn main() {
}
}
-
save_batches(batches).expect("save_batches");
}
Cmd::Format => {
@@ -189,12 +191,15 @@ fn main() {
format_index(&batches, &kanji_levels).expect("format_index");
format_about().expect("format_about");
}
+ Cmd::Server => {
+ server::server_main().await.expect("error in server");
+ }
}
}
// ----
-fn read_batches() -> anyhow::Result<Vec<Batch>> {
+pub fn read_batches() -> anyhow::Result<Vec<Batch>> {
let json = fs::read("data/batches.json")?;
Ok(serde_json::from_slice::<Vec<Batch>>(&json)?)
}
@@ -215,17 +220,17 @@ const CHARS_PER_BATCH: usize = 20;
const MAX_NEW_CHARS_PER_EX: usize = 5;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
-struct Batch {
- level: String,
- chars: Charset,
- chars_p1: Charset,
- chars_p2: Charset,
- chars_bad: Charset,
- examples: Vec<Example>,
+pub struct Batch {
+ pub level: String,
+ pub chars: Charset,
+ pub chars_p1: Charset,
+ pub chars_p2: Charset,
+ pub chars_bad: Charset,
+ pub examples: Vec<Example>,
#[serde(default)]
- extra_vocab: Vec<JlptVocab>,
+ pub extra_vocab: Vec<JlptVocab>,
#[serde(default)]
- extra_examples: Vec<Example>,
+ pub extra_examples: Vec<Example>,
}
fn gen_batches(
diff --git a/src/server.rs b/src/server.rs
new file mode 100644
index 0000000..83b9151
--- /dev/null
+++ b/src/server.rs
@@ -0,0 +1,124 @@
+use std::fs;
+use std::sync::Arc;
+
+use anyhow::anyhow;
+
+use http_types::mime;
+use tide::prelude::*;
+use tide::Request;
+
+use crate::datafiles::*;
+use crate::format::*;
+use crate::*;
+
+pub async fn server_main() -> tide::Result<()> {
+ // ---- load data files ----
+
+ eprintln!("Loading JMdict_e.xml...");
+ let jmdict_raw = fs::read_to_string("data/JMdict_e.xml").expect("read_jmdict");
+ let jmdict_raw: &'static str = String::leak(jmdict_raw);
+
+ eprintln!("Parsing JMdict_e.xml...");
+ let jmdict = roxmltree::Document::parse_with_options(
+ &jmdict_raw,
+ roxmltree::ParsingOptions {
+ allow_dtd: true,
+ ..Default::default()
+ },
+ )
+ .expect("parse_jmdict");
+ let jmdict_xml = Box::leak(Box::new(jmdict));
+
+ eprintln!("Indexing JMdict_e.xml...");
+ let jmdict_idx = index_jmdict(jmdict_xml);
+
+ eprintln!("Loading batches.json...");
+ let batches = read_batches().expect("read/parse");
+ let batches = Box::leak(batches.into_boxed_slice());
+
+ eprintln!("Loading kanji levels...");
+ let kanji_levels = read_kanji_levels().expect("read_kanji_levels");
+
+ let mut index_bytes = Vec::new();
+ format_index_to(&mut index_bytes, &batches, &kanji_levels).unwrap();
+ let index = String::leak(String::from_utf8(index_bytes).unwrap());
+
+ // ---- setup http server ----
+
+ let state = Arc::new(StateStruct {
+ jmdict_raw,
+ jmdict_xml,
+ jmdict_idx,
+ batches,
+ index,
+ });
+
+ let mut app = tide::with_state(state);
+ app.with(tide::log::LogMiddleware::new());
+
+ app.at("/").get(home_page);
+ app.at("/index.html").get(home_page);
+ app.at("/style.css").serve_file("static/style.css")?;
+ app.at("/about.html").get(about_page);
+ app.at("/:batch").get(batch_page);
+
+ // ---- serve actual http ----
+
+ eprintln!("Server listening on 127.0.0.1:8080");
+ app.listen("127.0.0.1:8080").await?;
+
+ Ok(())
+}
+
+type State = Arc<StateStruct>;
+#[allow(dead_code)]
+struct StateStruct {
+ jmdict_raw: &'static str,
+ jmdict_xml: &'static roxmltree::Document<'static>,
+ jmdict_idx: DictIndex<'static>,
+ batches: &'static [Batch],
+ index: &'static str,
+}
+
+async fn home_page(req: Request<State>) -> tide::Result {
+ Ok(tide::Response::builder(200)
+ .body(req.state().index)
+ .content_type(mime::HTML)
+ .build())
+}
+
+async fn about_page(_req: Request<State>) -> tide::Result {
+ let mut about = Vec::new();
+ format_about_to(&mut about)?;
+ Ok(tide::Response::builder(200)
+ .body(about)
+ .content_type(mime::HTML)
+ .build())
+}
+
+async fn batch_page(req: Request<State>) -> tide::Result {
+ let batch_idx = req.param("batch")?;
+ let batch_idx: usize = batch_idx
+ .strip_suffix(".html")
+ .unwrap_or(batch_idx)
+ .parse()?;
+ let batch = req
+ .state()
+ .batches
+ .get(batch_idx)
+ .ok_or(anyhow!("this batch number does not exist"))?;
+
+ let mut buf = vec![];
+ format_batch_to(
+ &mut buf,
+ &req.state().jmdict_idx,
+ req.state().batches.len(),
+ batch_idx,
+ batch,
+ )?;
+
+ Ok(tide::Response::builder(200)
+ .body(buf)
+ .content_type(mime::HTML)
+ .build())
+}