diff options
author | metamuffin <metamuffin@disroot.org> | 2023-02-13 21:02:09 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-02-13 21:02:09 +0100 |
commit | 9cefa70c3594445c3af6428be982b8b5b5883a42 (patch) | |
tree | cebfeb0026348d83666674aaece3ae22b13ba5dc | |
parent | ad483c540b24b4adc1cdab5bdf62772ba19b615a (diff) | |
download | metamuffin-website-9cefa70c3594445c3af6428be982b8b5b5883a42.tar metamuffin-website-9cefa70c3594445c3af6428be982b8b5b5883a42.tar.bz2 metamuffin-website-9cefa70c3594445c3af6428be982b8b5b5883a42.tar.zst |
include blog (2)
-rw-r--r-- | Cargo.lock | 58 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
m--------- | blog | 0 | ||||
-rw-r--r-- | src/blog/helper.rs | 71 | ||||
-rw-r--r-- | src/blog/mod.rs | 61 | ||||
-rw-r--r-- | src/error.rs | 59 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/pages.rs | 13 |
8 files changed, 252 insertions, 16 deletions
@@ -47,6 +47,12 @@ dependencies = [ ] [[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + +[[package]] name = "async-channel" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -498,6 +504,7 @@ checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -521,6 +528,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] name = "futures-io" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -542,6 +560,17 @@ dependencies = [ ] [[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "futures-sink" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -562,6 +591,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -841,6 +871,15 @@ dependencies = [ ] [[package]] +name = "iso8601" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" +dependencies = [ + "nom", +] + +[[package]] name = "itoa" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -969,6 +1008,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] name = "mio" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1001,6 +1046,16 @@ dependencies = [ ] [[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1886,9 +1941,12 @@ dependencies = [ name = "website" version = "0.1.0" dependencies = [ + "anyhow", "async-std", "env_logger", + "futures", "include_dir", + "iso8601", "log", "markdown", "markup", @@ -12,3 +12,6 @@ async-std = "1.12.0" markup = "0.13.1" include_dir = { version = "0.7.3", features = ["glob"] } markdown = "1.0.0-alpha.7" +iso8601 = "0.6.1" +futures = "0.3.26" +anyhow = "1.0.69" diff --git a/blog b/blog -Subproject 77eef59404acaed6faa636239bd18010e34a91d +Subproject c19adca147d38562b3f4a06cb2205e043bc2485 diff --git a/src/blog/helper.rs b/src/blog/helper.rs new file mode 100644 index 0000000..6dea529 --- /dev/null +++ b/src/blog/helper.rs @@ -0,0 +1,71 @@ +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, +}; + +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 index 5cc5f0b..3ac38eb 100644 --- a/src/blog/mod.rs +++ b/src/blog/mod.rs @@ -1,5 +1,13 @@ +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; use rocket::{get, response::Redirect}; +use std::{path::PathBuf, str::FromStr}; +use tokio::fs::read_to_string; #[get("/blog")] pub fn r_blog() -> Redirect { @@ -7,9 +15,54 @@ pub fn r_blog() -> Redirect { } #[get("/blog/index")] -pub fn r_blog_index() -> DynScaffold<'static> { - Scaffold { +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("./blog/articles").unwrap()).await?; + Ok(Scaffold { title: "blog index".to_string(), - content: markup::new! {}, - } + content: markup::new! { + h2 { "The Weblog" } + i { "Articles in reverse-chronological order." } + 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("./blog/articles") + .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 html = markdown::to_html_with_options( + &text, + &markdown::Options { + parse: markdown::ParseOptions { + constructs: markdown::Constructs { + math_flow: true, + math_text: true, + ..Default::default() + }, + ..Default::default() + }, + compile: markdown::CompileOptions { + ..Default::default() + }, + }, + ) + .map_err(|e| anyhow!("the server had trouble compiling markdown: {e}"))?; + Ok(Scaffold { + title: a.title, + content: markup::new! { + @markup::raw(&html) + }, + }) } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..1fe89da --- /dev/null +++ b/src/error.rs @@ -0,0 +1,59 @@ +use std::fmt::Display; + +use crate::layout::{DynScaffold, Scaffold}; +use rocket::{ + catch, + http::Status, + response::{self, Responder}, + Request, +}; + +#[catch(default)] +pub fn r_catch<'a>(status: Status, _request: &Request) -> DynScaffold<'a> { + Scaffold { + title: "Error".to_string(), + content: markup::new! { + h2 { "Error" } + p { @format!("{status}") } + }, + } +} + +pub type MyResult<T> = Result<T, MyError>; + +#[derive(Debug)] +pub struct MyError(pub anyhow::Error); + +impl<'r> Responder<'r, 'static> for MyError { + fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { + Scaffold { + title: "Error".to_string(), + content: markup::new! { + h2 { "An error occured. Nobody is sorry"} + pre.error { @format!("{:?}", self.0) } + }, + } + .respond_to(req) + } +} + +impl Display for MyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} +// impl<T: std::error::Error> From<T> for MyError { +// fn from(err: T) -> MyError { +// MyError(anyhow::anyhow!("{err}")) +// } +// } +impl From<std::io::Error> for MyError { + fn from(err: std::io::Error) -> MyError { + MyError(anyhow::anyhow!("{err}")) + } +} +impl From<anyhow::Error> for MyError { + fn from(err: anyhow::Error) -> MyError { + MyError(err) + } +} diff --git a/src/main.rs b/src/main.rs index b1b5147..1857cfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,12 +8,14 @@ pub mod layout; pub mod pages; pub mod source; pub mod wellknown; +pub mod error; use blog::*; use pages::*; use rocket::{catchers, fairing::AdHoc, http::Header, routes}; use source::*; use wellknown::*; +use error::*; #[tokio::main] async fn main() { @@ -35,6 +37,7 @@ async fn main() { r_source, r_blog, r_blog_index, + r_blog_article, r_wellknown_security, r_wellknown_matrix_server, r_wellknown_matrix_client, diff --git a/src/pages.rs b/src/pages.rs index 00626e3..482e254 100644 --- a/src/pages.rs +++ b/src/pages.rs @@ -1,5 +1,5 @@ use crate::layout::{DynScaffold, Scaffold}; -use rocket::{catch, get, http::Status, response::Redirect, uri, Request}; +use rocket::{get, response::Redirect, uri}; #[get("/")] pub fn r_root() -> Redirect { @@ -95,14 +95,3 @@ pub fn r_contact() -> DynScaffold<'static> { pub fn r_pgp_key() -> &'static str { include_str!("../assets/key.asc") } - -#[catch(default)] -pub fn r_catch<'a>(status: Status, _request: &Request) -> DynScaffold<'a> { - Scaffold { - title: "Error".to_string(), - content: markup::new! { - h2 { "Error" } - p { @format!("{status}") } - }, - } -} |