things
This commit is contained in:
parent
20eea2b6dd
commit
5b6dd2330d
10 changed files with 265 additions and 96 deletions
3
.markdownlint.json
Normal file
3
.markdownlint.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"MD025": false
|
||||||
|
}
|
89
Cargo.lock
generated
89
Cargo.lock
generated
|
@ -68,13 +68,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.67"
|
version = "0.1.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4"
|
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.6",
|
"syn 2.0.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -355,9 +355,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -405,7 +405,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"scratch",
|
"scratch",
|
||||||
"syn 2.0.6",
|
"syn 2.0.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -422,7 +422,7 @@ checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.6",
|
"syn 2.0.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -817,9 +817,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.2"
|
version = "1.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
|
@ -1265,9 +1265,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.7.2"
|
version = "1.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c"
|
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -1282,9 +1282,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.21"
|
version = "0.1.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
|
@ -1336,7 +1336,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.6",
|
"syn 2.0.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1359,6 +1359,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -1459,9 +1468,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.6"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ece519cfaf36269ea69d16c363fa1d59ceba8296bbfbfc003c3176d01f2816ee"
|
checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1523,7 +1532,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.6",
|
"syn 2.0.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1580,6 +1589,40 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.19.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
|
@ -1920,12 +1963,15 @@ dependencies = [
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"glob",
|
"glob",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"lazy_static",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tera",
|
"tera",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -2038,6 +2084,15 @@ version = "0.42.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.11.2+zstd.1.5.2"
|
version = "0.11.2+zstd.1.5.2"
|
||||||
|
|
|
@ -10,13 +10,17 @@ axum = { version = "0.6.12", features = ["http2"] }
|
||||||
cached = "0.42.0"
|
cached = "0.42.0"
|
||||||
color-eyre = "0.6.1"
|
color-eyre = "0.6.1"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
|
#grass = { version = "0.12.3", features = ["random"] } # not really needed yet
|
||||||
hyper = { version = "0.14.19", features = ["full"] }
|
hyper = { version = "0.14.19", features = ["full"] }
|
||||||
|
lazy_static = "1.4.0"
|
||||||
pulldown-cmark = "0.9.2"
|
pulldown-cmark = "0.9.2"
|
||||||
|
regex = "1.7.2"
|
||||||
serde = "1.0.144"
|
serde = "1.0.144"
|
||||||
serde_derive = "1.0.144"
|
serde_derive = "1.0.144"
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.85"
|
||||||
tera = "1.17.0"
|
tera = "1.17.0"
|
||||||
tokio = { version = "1.19.2", features = ["full"] }
|
tokio = { version = "1.19.2", features = ["full"] }
|
||||||
|
toml = "0.7.3"
|
||||||
tower = { version = "0.4.12", features = ["full"] }
|
tower = { version = "0.4.12", features = ["full"] }
|
||||||
tower-http = { version = "0.4.0", features = ["full"] }
|
tower-http = { version = "0.4.0", features = ["full"] }
|
||||||
tracing = "0.1.35"
|
tracing = "0.1.35"
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
+++
|
||||||
|
title="TOML metadata test"
|
||||||
|
+++
|
||||||
|
|
||||||
# Testing post as index within folder
|
# Testing post as index within folder
|
||||||
|
|
||||||
hope it works yay
|
hope it works yay
|
||||||
|
|
6
posts/foldertest/nestedpost.md
Normal file
6
posts/foldertest/nestedpost.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title='nested post test does not work'
|
||||||
|
+++
|
||||||
|
# yet again a nested post test
|
||||||
|
|
||||||
|
will it work this time, at least with the slug??
|
|
@ -1,4 +1,3 @@
|
||||||
use serde_derive::Serialize;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::log::*;
|
use tracing::log::*;
|
||||||
|
|
||||||
|
@ -17,11 +16,6 @@ pub enum Error {
|
||||||
|
|
||||||
pub type Result<T = Html<Vec<u8>>> = std::result::Result<T, Error>;
|
pub type Result<T = Html<Vec<u8>>> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct PageContext {
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
let ctx = tera::Context::new();
|
let ctx = tera::Context::new();
|
||||||
let res = state.tera.render("index.html", &ctx).map_err(|e| {
|
let res = state.tera.render("index.html", &ctx).map_err(|e| {
|
||||||
|
|
|
@ -1,43 +1,23 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{extract::Path, response::Html, Extension};
|
use axum::{extract::Path, response::Html, Extension};
|
||||||
use cached::proc_macro::cached;
|
|
||||||
use pulldown_cmark::{html, Options, Parser};
|
|
||||||
use tracing::log::*;
|
use tracing::log::*;
|
||||||
|
|
||||||
use super::{Error, Result};
|
use super::{Error, Result};
|
||||||
use crate::{handlers::PageContext, State};
|
use crate::{post::render_post, State};
|
||||||
|
|
||||||
pub async fn view(Path(path): Path<String>, Extension(state): Extension<Arc<State>>) -> Result {
|
pub async fn view(Path(path): Path<String>, Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
let post = path.trim_end_matches('/');
|
info!("Requested post: {}", path);
|
||||||
info!("Requested post: {}", post);
|
let post = state
|
||||||
|
.posts
|
||||||
|
.iter()
|
||||||
|
.find(|p| p.slug.eq_ignore_ascii_case(&path))
|
||||||
|
.ok_or(Error::NotFound)?;
|
||||||
|
//let post = load_post(&path).await.ok_or(Error::NotFound)?;
|
||||||
let res = render_post(&state, post).await.ok_or(Error::NotFound)?;
|
let res = render_post(&state, post).await.ok_or(Error::NotFound)?;
|
||||||
Ok(Html(res.into()))
|
Ok(Html(res.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cached(time = 60, key = "String", convert = r"{ path.to_owned() }")]
|
|
||||||
async fn render_post(state: &State, path: &str) -> Option<String> {
|
|
||||||
info!("Rendering post...");
|
|
||||||
let post = state.posts.iter().find(|p| p.slug == path)?;
|
|
||||||
|
|
||||||
let options = Options::all();
|
|
||||||
let parser = Parser::new_ext(&post.content, options);
|
|
||||||
|
|
||||||
let mut out = String::new();
|
|
||||||
html::push_html(&mut out, parser);
|
|
||||||
|
|
||||||
let ctx = tera::Context::from_serialize(PageContext { content: out }).ok()?;
|
|
||||||
|
|
||||||
let res = match state.tera.render("post.html", &ctx) {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed rendering post: {}", e);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Some(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
let mut ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
ctx.insert("posts", &state.posts);
|
ctx.insert("posts", &state.posts);
|
||||||
|
|
61
src/main.rs
61
src/main.rs
|
@ -1,63 +1,30 @@
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time};
|
||||||
|
|
||||||
use axum::{
|
use axum::{routing::get, Extension, Router};
|
||||||
routing::get,
|
|
||||||
Extension, Router,
|
|
||||||
};
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glob::glob;
|
use post::Post;
|
||||||
use serde_derive::Serialize;
|
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::log::*;
|
use tracing::log::*;
|
||||||
|
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
mod post;
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
posts: Vec<Post>,
|
posts: Vec<Post>,
|
||||||
tera: Tera,
|
tera: Tera,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct Post {
|
|
||||||
pub name: String,
|
|
||||||
pub slug: String,
|
|
||||||
pub content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
info!("Starting server...");
|
info!("Starting server...");
|
||||||
|
|
||||||
|
let ts = time::Instant::now();
|
||||||
|
|
||||||
let tera = Tera::new("templates/**/*")?;
|
let tera = Tera::new("templates/**/*")?;
|
||||||
let posts = glob("posts/**/*.md")?
|
let posts = post::load_all()?;
|
||||||
.map(|p| {
|
|
||||||
let path = p.unwrap();
|
|
||||||
info!("found page: {}", path.display());
|
|
||||||
|
|
||||||
let filename = path.file_name().unwrap().to_string_lossy();
|
|
||||||
|
|
||||||
let (filename, _) = filename.rsplit_once('.').unwrap();
|
|
||||||
|
|
||||||
let slug = if filename.eq_ignore_ascii_case("index") {
|
|
||||||
path.parent()
|
|
||||||
.unwrap()
|
|
||||||
.file_name()
|
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned()
|
|
||||||
} else {
|
|
||||||
filename.to_owned()
|
|
||||||
};
|
|
||||||
Post {
|
|
||||||
name: slug.clone(),
|
|
||||||
slug,
|
|
||||||
content: std::fs::read_to_string(&path).unwrap(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let state = Arc::new(State { tera, posts });
|
let state = Arc::new(State { tera, posts });
|
||||||
|
|
||||||
|
@ -67,13 +34,19 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(handlers::index))
|
.route("/", get(handlers::index))
|
||||||
.nest("/posts", Router::new()
|
.nest(
|
||||||
.route("/", get(handlers::posts::index))
|
"/posts",
|
||||||
.route("/:route/", get(handlers::posts::view))
|
Router::new()
|
||||||
.fallback_service(tower_http::services::ServeDir::new("./posts")))
|
.route("/", get(handlers::posts::index))
|
||||||
|
.route("/:route/", get(handlers::posts::view))
|
||||||
|
.fallback_service(tower_http::services::ServeDir::new("./posts")),
|
||||||
|
)
|
||||||
.nest_service("/static", tower_http::services::ServeDir::new("./static"))
|
.nest_service("/static", tower_http::services::ServeDir::new("./static"))
|
||||||
.layer(middleware);
|
.layer(middleware);
|
||||||
|
|
||||||
|
let duration = time::Instant::now() - ts;
|
||||||
|
|
||||||
|
info!("loaded server in {}ms", duration.as_secs_f64() * 1000.0);
|
||||||
info!("Now listening at http://localhost:8180");
|
info!("Now listening at http://localhost:8180");
|
||||||
|
|
||||||
axum::Server::bind(&"0.0.0.0:8180".parse().unwrap())
|
axum::Server::bind(&"0.0.0.0:8180".parse().unwrap())
|
||||||
|
|
150
src/post.rs
Normal file
150
src/post.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use cached::proc_macro::cached;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use glob::glob;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use pulldown_cmark::{html, Options, Parser};
|
||||||
|
use regex::Regex;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use tokio::fs;
|
||||||
|
use tracing::log::*;
|
||||||
|
|
||||||
|
use crate::State;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct FrontMatter {
|
||||||
|
pub title: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, Debug)]
|
||||||
|
pub struct Post {
|
||||||
|
pub content: String,
|
||||||
|
pub slug: String,
|
||||||
|
pub absolute_path: String,
|
||||||
|
pub frontmatter: Option<FrontMatter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_all() -> Result<Vec<Post>> {
|
||||||
|
Ok(glob("posts/**/*.md")?
|
||||||
|
.map(|p| {
|
||||||
|
let path = p.unwrap();
|
||||||
|
info!("found page: {}", path.display());
|
||||||
|
|
||||||
|
let filename = path.file_name().unwrap();
|
||||||
|
let slug = if filename.eq_ignore_ascii_case("index.md") {
|
||||||
|
path.parent().unwrap().to_string_lossy()
|
||||||
|
} else {
|
||||||
|
path.to_string_lossy()
|
||||||
|
}
|
||||||
|
.trim_start_matches("posts")
|
||||||
|
.trim_start_matches(std::path::MAIN_SEPARATOR)
|
||||||
|
.trim_end_matches(".md")
|
||||||
|
.trim_end_matches(std::path::MAIN_SEPARATOR)
|
||||||
|
.replace('\\', "/");
|
||||||
|
|
||||||
|
info!("slug: {slug}");
|
||||||
|
|
||||||
|
let raw = std::fs::read_to_string(&path).unwrap();
|
||||||
|
let (frontmatter, content) = parse_frontmatter(raw);
|
||||||
|
|
||||||
|
let content = content.map(|c| {
|
||||||
|
let options = Options::all();
|
||||||
|
let mut content_html = String::new();
|
||||||
|
let parser = Parser::new_ext(&c, options);
|
||||||
|
html::push_html(&mut content_html, parser);
|
||||||
|
content_html
|
||||||
|
});
|
||||||
|
|
||||||
|
Post {
|
||||||
|
absolute_path: format!("/posts/{slug}/"),
|
||||||
|
slug,
|
||||||
|
content: content.unwrap_or_default(),
|
||||||
|
frontmatter,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cached(time = 60, key = "String", convert = r#"{ String::from(path) }"#)]
|
||||||
|
pub async fn load_post(path: &str) -> Option<Post> {
|
||||||
|
let path = path
|
||||||
|
.trim_end_matches('/')
|
||||||
|
.trim_end_matches(".md")
|
||||||
|
.trim_end_matches(".html");
|
||||||
|
|
||||||
|
info!("loading post: {path}");
|
||||||
|
|
||||||
|
let path = if path.starts_with("posts/") {
|
||||||
|
Path::new(path).to_owned()
|
||||||
|
} else {
|
||||||
|
Path::new("posts").join(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = if let Ok(content) = fs::read_to_string(path.with_extension("md")).await {
|
||||||
|
content
|
||||||
|
} else if let Ok(content) = fs::read_to_string(path.join("index.md")).await {
|
||||||
|
content
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (frontmatter, content) = parse_frontmatter(content);
|
||||||
|
|
||||||
|
let content = content.map(|c| {
|
||||||
|
let options = Options::all();
|
||||||
|
let mut content_html = String::new();
|
||||||
|
let parser = Parser::new_ext(&c, options);
|
||||||
|
html::push_html(&mut content_html, parser);
|
||||||
|
content_html
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(Post {
|
||||||
|
absolute_path: format!("/{}/", path.to_string_lossy()),
|
||||||
|
slug: path.to_string_lossy().into(),
|
||||||
|
content: content.unwrap_or_default(),
|
||||||
|
frontmatter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_frontmatter(src: String) -> (Option<FrontMatter>, Option<String>) {
|
||||||
|
lazy_static! {
|
||||||
|
static ref FRONTMATTER_REGEX: Regex = regex::Regex::new(
|
||||||
|
r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(fm) = FRONTMATTER_REGEX.captures(&src) {
|
||||||
|
(
|
||||||
|
fm.get(1)
|
||||||
|
.and_then(|m| toml::from_str(m.as_str()).expect("invalid toml")),
|
||||||
|
fm.get(2).map(|m| m.as_str().to_owned()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(None, Some(src))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Postcontext {
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cached(time = 60, key = "String", convert = r"{ post.absolute_path.clone() }")]
|
||||||
|
pub async fn render_post(state: &State, post: &Post) -> Option<String> {
|
||||||
|
info!("rendering post: {}", post.absolute_path);
|
||||||
|
|
||||||
|
let ctx = tera::Context::from_serialize(Postcontext {
|
||||||
|
content: post.content.clone(),
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
let res = match state.tera.render("post.html", &ctx) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed rendering post: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(res)
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
<p>i occasionally write some stuff i guess</p>
|
<p>i occasionally write some stuff i guess</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
<li><a href="/posts/{{post.name}}/">{{post.name}}</a></li>
|
<li><a href="{{post.absolute_path}}">{% if post.frontmatter %}{{post.frontmatter.title}}{% else %}{{post.slug}}{% endif %}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock main %}
|
{% endblock main %}
|
Loading…
Reference in a new issue