From 4ea388dc7dab5dd7f784c180abafe59c39bb884d Mon Sep 17 00:00:00 2001 From: metamuffin Date: Sat, 4 May 2024 01:23:33 +0200 Subject: syntax highlight code blocks and inline math --- Cargo.lock | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/blog/mod.rs | 96 +++++++++++++++++++++++---- 3 files changed, 284 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c702c0c..ea34aff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,12 +263,27 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "binascii" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -368,6 +383,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.14" @@ -504,6 +528,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -899,6 +933,12 @@ dependencies = [ "log", ] +[[package]] +name = "latex2mathml" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678cf5bdb3ba63a264e6e0c9eee36538ca1d2da0afa4dd801c1f96309e710765" + [[package]] name = "lazy_static" version = "1.4.0" @@ -911,6 +951,21 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1012,10 +1067,12 @@ dependencies = [ "futures", "include_dir", "iso8601", + "latex2mathml", "log", "markdown", "markup", "rocket", + "syntect", "tokio", ] @@ -1125,6 +1182,28 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +dependencies = [ + "bitflags 1.3.2", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "overload" version = "0.1.1" @@ -1201,6 +1280,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plist" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa" +dependencies = [ + "base64", + "indexmap 1.9.3", + "line-wrap", + "quick-xml", + "serde", + "time", +] + [[package]] name = "polling" version = "2.5.2" @@ -1243,6 +1342,15 @@ dependencies = [ "yansi", ] +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.33" @@ -1355,6 +1463,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + [[package]] name = "rocket" version = "0.5.0" @@ -1482,6 +1596,21 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1622,6 +1751,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax 0.8.3", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + [[package]] name = "tempfile" version = "3.6.0" @@ -1636,6 +1787,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "thiserror" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.7" @@ -1903,6 +2074,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2019,6 +2200,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2239,6 +2429,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "1.0.0-rc" diff --git a/Cargo.toml b/Cargo.toml index 4d267db..7d59ae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,5 @@ anyhow = "1.0.82" markup = "0.15.0" markdown = "1.0.0-alpha.17" chrono = "0.4.38" +syntect = "5.2.0" +latex2mathml = "0.2.3" diff --git a/src/blog/mod.rs b/src/blog/mod.rs index b301171..49fbd4b 100644 --- a/src/blog/mod.rs +++ b/src/blog/mod.rs @@ -8,8 +8,14 @@ use crate::uri; use anyhow::anyhow; pub use atom::r_blog_atom; use atom::rocket_uri_macro_r_blog_atom; +use latex2mathml::DisplayStyle; +use markdown::mdast::Node; +use markup::{raw, DynRender}; use rocket::{get, response::Redirect}; use std::{path::PathBuf, str::FromStr}; +use syntect::highlighting::ThemeSet; +use syntect::html::highlighted_html_for_string; +use syntect::parsing::SyntaxSet; use tokio::fs::read_to_string; pub const ARTICLE_ROOT: &'static str = "./blog/articles"; @@ -49,28 +55,92 @@ pub async fn r_blog_article(name: &str) -> MyResult> { .join(PathBuf::new().with_file_name(name).with_extension("md")); let a = article_metadata(apath.clone()).await?; let text = read_to_string(apath).await?; - let html = markdown::to_html_with_options( + let ast = markdown::to_mdast( &text, - &markdown::Options { - parse: markdown::ParseOptions { - constructs: markdown::Constructs { - math_flow: true, - math_text: true, - ..Default::default() - }, - ..Default::default() - }, - compile: markdown::CompileOptions { + &markdown::ParseOptions { + constructs: markdown::Constructs { + math_flow: true, + math_text: true, ..Default::default() }, + ..Default::default() }, ) - .map_err(|e| anyhow!("the server had trouble compiling markdown: {e}"))?; + .map_err(|e| anyhow!("the server had trouble parsing markdown: {e}"))?; + Ok(Scaffold { title: a.title, content: markup::new! { - @markup::raw(&html) + @node_to_render(&ast) p{i{ "Article written by metamuffin, text licenced under CC BY-ND 4.0, non-trivial code blocks under GPL-3.0-only except where indicated otherwise" }} }, }) } + +fn node_to_render<'a>(node: &'a Node) -> DynRender<'a> { + match node { + Node::Text(s) => markup::new!(@s.value), + Node::Paragraph(el) => markup::new!(p { @for e in &el.children { @node_to_render(e) } }), + Node::List(list) => markup::new!(ul { @for e in &list.children { @node_to_render(e) } }), + Node::Root(el) => markup::new!(article { @for e in &el.children { @node_to_render(e) } }), + Node::ListItem(el) => markup::new!(li { @for e in &el.children { @node_to_render(e) } }), + Node::Emphasis(el) => markup::new!(i { @for e in &el.children { @node_to_render(e) } }), + Node::Strong(el) => markup::new!(strong { @for e in &el.children { @node_to_render(e) } }), + Node::Html(html) => markup::new!(@raw(&html.value)), + Node::Link(l) => { + markup::new!(a[href=&l.url, alt=&l.title] { @for e in &l.children { @node_to_render(e) } }) + } + Node::Break(_) => markup::new!(br;), + Node::InlineCode(s) => markup::new!(code { @s.value }), + Node::Delete(_) => markup::new!("todo1"), + Node::FootnoteReference(_) => markup::new!("todo3"), + Node::FootnoteDefinition(_) => markup::new!("todo4"), + Node::Image(_) => markup::new!("todo5"), + Node::ImageReference(_) => markup::new!("todo6"), + Node::LinkReference(_) => markup::new!("todo8"), + Node::BlockQuote(_) => markup::new!("todo10"), + Node::Table(_) => markup::new!("todo12"), + Node::ThematicBreak(_) => markup::new!("todo13"), + Node::TableRow(_) => markup::new!("todo14"), + Node::TableCell(_) => markup::new!("todo15"), + Node::Definition(_) => markup::new!("todo16"), + Node::Toml(_) + | Node::Yaml(_) + | Node::MdxjsEsm(_) + | Node::MdxJsxFlowElement(_) + | Node::MdxJsxTextElement(_) + | Node::MdxTextExpression(_) + | Node::MdxFlowExpression(_) => markup::new!("unsupported"), + Node::Heading(h) => { + let inner = markup::new!(@for e in &h.children { @node_to_render(e) }); + match h.depth { + 1 => markup::new!(h2{@inner}), + 2 => markup::new!(h3{@inner}), + 3 => markup::new!(h4{@inner}), + 4 => markup::new!(h5{@inner}), + 5 => markup::new!(h6{@inner}), + 6 => markup::new!(h6{@inner}), + _ => unreachable!(), + } + } + Node::Code(code) => { + let theme = &ThemeSet::load_defaults().themes["base16-ocean.dark"]; + let syntax = &SyntaxSet::load_defaults_newlines(); + let lang = syntax + .find_syntax_by_extension(&code.lang.to_owned().unwrap_or("txt".to_owned())) + .unwrap_or_else(|| syntax.find_syntax_by_extension("txt").unwrap()); + let html = highlighted_html_for_string(&code.value, syntax, lang, theme).unwrap(); + markup::new!(@raw(&html)) + } + Node::Math(s) => { + let mathml = latex2mathml::latex_to_mathml(&s.value, DisplayStyle::Block) + .expect("invalid block math"); + markup::new!(@raw(&mathml)) + } + Node::InlineMath(s) => { + let mathml = latex2mathml::latex_to_mathml(&s.value, DisplayStyle::Inline) + .expect("invalid inline math"); + markup::new!(@raw(&mathml)) + } + } +} -- cgit v1.2.3-70-g09d2