From a6507a7a77cf8d6f002d58fbf1362d4c0fba1276 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Sun, 10 Mar 2024 18:55:14 +0100 Subject: start web server --- src/example.rs | 8 +++- src/format.rs | 133 ++++++++++++++++++++++++++++++++------------------------- src/main.rs | 31 ++++++++------ src/server.rs | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 72 deletions(-) create mode 100644 src/server.rs (limited to 'src') 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) { + pub fn gen_furigana<'a>( + &mut self, + dict_idx: &DictIndex<'a>, + overrides: &HashMap, + ) { 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#" @@ -34,35 +38,35 @@ fn format_batch_aux<'a>( i )?; - writeln!(f, r#"

index"#)?; + writeln!(buf, r#"

index"#)?; for j in 0..count { if j != i { - writeln!(f, r#" {:03}"#, j, j)?; + writeln!(buf, r#" {:03}"#, j, j)?; } else { - writeln!(f, " {:03}", j)?; + writeln!(buf, " {:03}", j)?; } } - writeln!(f, r#"

"#)?; - writeln!(f, "

Level: {}

", batch.level)?; + writeln!(buf, r#"

"#)?; + writeln!(buf, "

Level: {}

", batch.level)?; - write!(f, r#"

"#)?; + write!(buf, r#"

"#)?; 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#"{}"#, c, c )?; } ex_prev = ex_prev.union(&ex_chars); } - writeln!(f, r#"

"#)?; + writeln!(buf, r#"

"#)?; for ex in batch.examples.iter() { - writeln!(f, "
")?; - write!(f, r#"

"#)?; + writeln!(buf, "


")?; + write!(buf, r#"

"#)?; 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#"{}"#, c, cls, c )?; } else { - write!(f, "{}", c)?; + write!(buf, "{}", c)?; } } - writeln!(f, "

")?; - writeln!(f, r#"

{}

"#, ex.en)?; + writeln!(buf, "

")?; + writeln!(buf, r#"

{}

"#, ex.en)?; - writeln!(f, r#"
Explanation"#)?; + writeln!(buf, r#"
Explanation"#)?; 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#"

{}

"#, be)?; + writeln!(buf, r#"

{}

"#, be)?; } - writeln!(f, r#"

"#)?; + writeln!(buf, r#"

"#)?; for c in ex.chars.inter(&batch.chars).chars().iter() { writeln!( - f, + buf, r#"{}"#, c, c )?; } - writeln!(f, r#"

"#)?; + writeln!(buf, r#"

"#)?; for be in expl_all { - writeln!(f, r#"

{}

"#, be)?; + writeln!(buf, r#"

{}

"#, be)?; } - writeln!(f, r#"
"#)?; + writeln!(buf, r#"
"#)?; } - writeln!(f, "
")?; + writeln!(buf, "
")?; 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#"

Extra examples (reading practice)

"# )?; for ex in batch.extra_examples.iter() { writeln!( - f, + buf, r#""#, ex.furigana_markup(), ex.en )?; } - writeln!(f, r#"
{}
{}
"#)?; + writeln!(buf, r#""#)?; - writeln!(f, "
")?; - writeln!(f, "

\(≧▽≦)/

")?; + writeln!(buf, "
")?; + writeln!(buf, "

\(≧▽≦)/

")?; - write!(f, "
")?; - f.flush()?; + write!(buf, "
")?; + 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#"

{}

"#, t )?; for v in vocab { writeln!( - f, + buf, r#""#, v.level, v.kanji, v.en, v.kana )?; } - writeln!(f, "
{}  {}  {}{}
")?; + writeln!(buf, "")?; } 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#" @@ -237,13 +249,16 @@ pub fn format_index(batches: &[Batch], kanji_levels: &[(String, String)]) -> Res
"# )?; - writeln!(f, r#"

About / How-to


"#)?; + writeln!( + buf, + r#"

About / How-to


"# + )?; - writeln!(f, "")?; - writeln!(f, "")?; + writeln!(buf, "
NumLevelKanjiExamplesLesson-1Lesson-2Ignore
")?; + writeln!(buf, "")?; for (i, batch) in batches.iter().enumerate() { writeln!( - f, + buf, r#""#, 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#"
NumLevelKanjiExamplesLesson-1Lesson-2Ignore
{:03}{}{}  {}{}{}{}
"#)?; + writeln!(buf, r#""#)?; - writeln!(f, "
")?; + writeln!(buf, "
")?; 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, "")?; + writeln!(buf, "
")?; writeln!( - f, + buf, r#""# )?; 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#""#, lvl, chars.len(), @@ -286,17 +301,21 @@ pub fn format_index(batches: &[Batch], kanji_levels: &[(String, String)]) -> Res missing.len() )?; } - writeln!(f, "
LevelCountKanjiMissing kanji
{}{}{}{} ({})
")?; + writeln!(buf, "")?; - write!(f, "
")?; - f.flush()?; + write!(buf, "
")?; + 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#" @@ -307,19 +326,19 @@ pub fn format_about() -> Result<()> { "# )?; - writeln!(f, r#"
"#)?; + writeln!(buf, r#"
"#)?; writeln!( - f, + buf, r#"

Back to lessons


"# )?; writeln!( - f, + buf, "{}", markdown::to_html(&fs::read_to_string("README.md")?) )?; - writeln!(f, r#"
"#)?; + writeln!(buf, r#"
"#)?; 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> { +pub fn read_batches() -> anyhow::Result> { let json = fs::read("data/batches.json")?; Ok(serde_json::from_slice::>(&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, +pub struct Batch { + pub level: String, + pub chars: Charset, + pub chars_p1: Charset, + pub chars_p2: Charset, + pub chars_bad: Charset, + pub examples: Vec, #[serde(default)] - extra_vocab: Vec, + pub extra_vocab: Vec, #[serde(default)] - extra_examples: Vec, + pub extra_examples: Vec, } 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; +#[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) -> tide::Result { + Ok(tide::Response::builder(200) + .body(req.state().index) + .content_type(mime::HTML) + .build()) +} + +async fn about_page(_req: Request) -> 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) -> 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()) +} -- cgit v1.2.3