pub mod atom; pub mod helper; use self::helper::{article_metadata, get_articles}; use crate::error::MyResult; use crate::layout::{DynScaffold, Scaffold}; 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"; pub const ASSET_ROOT: &'static str = "./blog/assets"; #[get("/blog")] pub fn r_blog() -> Redirect { Redirect::to(rocket::uri!(r_blog_index())) } #[get("/blog/index")] pub async fn r_blog_index() -> MyResult> { // TODO this is a major performance issue here. requires O(n) syscalls to complete let articles = get_articles(&PathBuf::from_str(ARTICLE_ROOT).unwrap()).await?; Ok(Scaffold { title: "blog index".to_string(), content: markup::new! { h2 { "The Weblog" } p { i { "Articles in reverse-chronological order." } } p { a[href=uri!(r_blog_atom())]{ "Atom feed" } } ul { @for a in &articles { li { @a.date.to_string() ": " a[href=uri!(r_blog_article(&a.canonical_name))] { @a.title } } } } }, }) } #[get("/blog/")] pub async fn r_blog_article(name: &str) -> MyResult> { let apath = PathBuf::from_str(ARTICLE_ROOT) .unwrap() .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 ast = markdown::to_mdast( &text, &markdown::ParseOptions { constructs: markdown::Constructs { math_flow: true, math_text: true, ..Default::default() }, ..Default::default() }, ) .map_err(|e| anyhow!("the server had trouble parsing markdown: {e}"))?; Ok(Scaffold { title: a.title, content: markup::new! { @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)) } } }