diff options
author | metamuffin <metamuffin@disroot.org> | 2025-10-02 19:14:17 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-10-02 19:14:17 +0200 |
commit | 23871d5aadcaa4d01b7c46cb951854572940414d (patch) | |
tree | 9a3f0490675642a5b76bdda8f44f2e75b469046c /src/blog | |
parent | fbc308f96dca2854bc462e6fee412b5dc35b6c3c (diff) | |
download | metamuffin-website-23871d5aadcaa4d01b7c46cb951854572940414d.tar metamuffin-website-23871d5aadcaa4d01b7c46cb951854572940414d.tar.bz2 metamuffin-website-23871d5aadcaa4d01b7c46cb951854572940414d.tar.zst |
Rewrite
Diffstat (limited to 'src/blog')
-rw-r--r-- | src/blog/atom.rs | 75 | ||||
-rw-r--r-- | src/blog/helper.rs | 72 | ||||
-rw-r--r-- | src/blog/mod.rs | 146 |
3 files changed, 0 insertions, 293 deletions
diff --git a/src/blog/atom.rs b/src/blog/atom.rs deleted file mode 100644 index a29ffb1..0000000 --- a/src/blog/atom.rs +++ /dev/null @@ -1,75 +0,0 @@ -use super::{ - helper::{get_articles, ArticleMeta}, - rocket_uri_macro_r_blog_article, rocket_uri_macro_r_blog_index, ARTICLE_ROOT, -}; -use crate::{error::MyResult, uri}; -use rocket::get; -use std::{path::PathBuf, str::FromStr}; - -#[get("/blog/feed.atom")] -pub async fn r_blog_atom() -> MyResult<String> { - let entries = get_articles(&PathBuf::from_str(ARTICLE_ROOT).unwrap()) - .await? - .iter() - .map( - |ArticleMeta { - title, - date, - canonical_name, - .. - }| { - let title = horrible_escape_function(title); - let datetime = iso8601::DateTime { - date: date.clone(), - time: iso8601::Time::default(), - }; - let href = uri!(r_blog_article(canonical_name)); - format!( - r#" - <entry> - <title>{title}</title> - <link href="{href}" /> - <id>tag:metamuffin.org,{date},{canonical_name}</id> - <published>{datetime}</published> - <summary>N/A</summary> - <author> - <name>metamuffin</name> - <email>metamuffin@disroot.org</email> - </author> - </entry>"# - ) - }, - ) - .collect::<Vec<_>>(); - - let feed_url = uri!(r_blog_atom()); - let index_url = uri!(r_blog_index()); - let now = chrono::Utc::now().to_rfc3339(); - - Ok(format!( - r#"<?xml version="1.0" encoding="utf-8"?> -<feed xmlns="http://www.w3.org/2005/Atom"> - <title>metamuffin's blog</title> - <subtitle>where they post pointless stuff</subtitle> - <link href="{feed_url}" rel="self" /> - <link href="{index_url}" /> - <id>urn:uuid:3cf2b704-3d94-4f1f-b194-42798ab5b47c</id> - <updated>{now}</updated> - <author> - <name>metamuffin</name> - <email>metamuffin@disroot.org</email> - </author> - {} -</feed> - "#, - entries.join("\n") - )) -} - -pub fn horrible_escape_function(text: &str) -> String { - text.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("'", "’") - .replace("\"", """) -} diff --git a/src/blog/helper.rs b/src/blog/helper.rs deleted file mode 100644 index 35396c0..0000000 --- a/src/blog/helper.rs +++ /dev/null @@ -1,72 +0,0 @@ -use anyhow::{anyhow, Context}; -use futures::future::join_all; -use std::{ - path::{Path, PathBuf}, - process::Stdio, -}; -use tokio::{ - fs::File, - io::{AsyncBufReadExt, BufReader}, - process::Command, -}; - -#[derive(Clone)] -pub struct ArticleMeta { - pub title: String, - pub canonical_name: String, - pub date: iso8601::Date, -} - -pub async fn article_metadata(path: PathBuf) -> anyhow::Result<ArticleMeta> { - let f = File::open(&path).await.context("article not found")?; - let mut f = BufReader::new(f); - let mut buf = String::new(); - f.read_line(&mut buf).await.context("cant read the file")?; // assume the 1st line has the title - Ok(ArticleMeta { - title: String::from(buf[2..].trim()), - canonical_name: path - .file_stem() - .ok_or(anyhow!("no file stem"))? - .to_str() - .ok_or(anyhow!("this file's name is broken"))? - .to_string(), - date: iso8601::date( - &path - .file_name() - .ok_or(anyhow!("file has no name"))? - .to_str() - .ok_or(anyhow!("this file's name is broken"))?[0..10], - ) - .map_err(|e| anyhow!("file date wrong: {e}"))?, - }) -} - -pub async fn get_articles(blog_root: &Path) -> anyhow::Result<Vec<ArticleMeta>> { - let mut a = join_all( - std::fs::read_dir(blog_root)? - .map(|e| e.unwrap()) - .map(|e| article_metadata(e.path())), - ) - .await - .into_iter() - .collect::<anyhow::Result<Vec<_>>>()?; - a.sort_by_cached_key(|e| -match e.date { - iso8601::Date::YMD { year, month, day } => day as i32 + month as i32 * 100 + year * 10000, - _ => unreachable!(), - }); - Ok(a) -} - -// TODO use this somewhere -pub async fn file_history(filename: &str) -> String { - String::from_utf8( - Command::new("/usr/bin/git") - .args(&["log", "--follow", "--pretty=tformat:%as %h %s", filename]) - .stdout(Stdio::piped()) - .output() - .await - .unwrap() - .stdout, - ) - .unwrap() -} diff --git a/src/blog/mod.rs b/src/blog/mod.rs deleted file mode 100644 index b3ab5a4..0000000 --- a/src/blog/mod.rs +++ /dev/null @@ -1,146 +0,0 @@ -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<DynScaffold<'static>> { - // 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/<name>")] -pub async fn r_blog_article(name: &str) -> MyResult<DynScaffold<'static>> { - 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)) - } - } -} |