summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-02-13 21:02:09 +0100
committermetamuffin <metamuffin@disroot.org>2023-02-13 21:02:09 +0100
commit9cefa70c3594445c3af6428be982b8b5b5883a42 (patch)
treecebfeb0026348d83666674aaece3ae22b13ba5dc
parentad483c540b24b4adc1cdab5bdf62772ba19b615a (diff)
downloadmetamuffin-website-9cefa70c3594445c3af6428be982b8b5b5883a42.tar
metamuffin-website-9cefa70c3594445c3af6428be982b8b5b5883a42.tar.bz2
metamuffin-website-9cefa70c3594445c3af6428be982b8b5b5883a42.tar.zst
include blog (2)
-rw-r--r--Cargo.lock58
-rw-r--r--Cargo.toml3
m---------blog0
-rw-r--r--src/blog/helper.rs71
-rw-r--r--src/blog/mod.rs61
-rw-r--r--src/error.rs59
-rw-r--r--src/main.rs3
-rw-r--r--src/pages.rs13
8 files changed, 252 insertions, 16 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f2f1207..ecc9d14 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 4059389..fbe0897 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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}") }
- },
- }
-}