Files
site/src/main.rs
2026-01-31 14:50:36 -05:00

145 lines
4.4 KiB
Rust

use axum::{
Router, extract::Path, response::Html, routing::get
};
use tower_http::services::ServeDir;
use askama::Template;
// use typst::model;
// use typst_html;
use std::{collections::HashMap, fs, io::{Error, Write}, process::{Command, Stdio}};
use regex::Regex;
fn load_posts() -> Result<HashMap<String, Post>, Error> {
let dirs = fs::read_dir("static/posts")?;
let re_slug = Regex::new(r#"#let\s+post_slug\s*=\s*"([^"]*)""#).unwrap();
let re_summary = Regex::new(r#"#let\s+post_summary\s*=\s*"([^"]*)""#).unwrap();
let re_img = Regex::new(r#"#let\s+post_preview_image\s*=\s*"([^"]*)""#).unwrap();
let re_title = Regex::new("(?m)^=+ (.*)").unwrap();
let mut posts: HashMap<String, Post> = HashMap::new();
for dir in dirs {
let dir_path = dir.as_ref().unwrap().path();
let typst_path = dir_path.join("post.typ");
let typst = fs::read_to_string(typst_path).expect("Failed reading post");
let title = re_title.captures(&typst)
.expect(format!("Post title not found in {}", dir_path.to_str().unwrap()).as_str())
.get(1).unwrap().as_str().trim().to_string() ;
println!("{}", title);
let slug = re_slug.captures(&typst)
.expect("Post slug not found")
.get(1).unwrap().as_str().trim().to_string();
let summary = re_summary.captures(&typst)
.expect("Post summary not found")
.get(1).unwrap().as_str().trim().to_string();
let img_path = dir?.path().join(
re_img.captures(&typst)
.expect("Post preview image not found")
.get(1).unwrap().as_str().trim().to_string()
).to_str().expect("Failed converting path to string").to_string();
posts.insert(
slug.clone(),
Post {
slug: slug,
title: title,
preview_image: img_path,
summary: summary,
render: Html(PostTemplate {
content: typst_to_html(typst),
}.render().expect("Failed rendering post")),
}
);
}
Ok(posts)
}
#[derive(Template)]
#[template(path = "post.html")]
struct PostTemplate {
content: String,
}
#[derive(Clone)]
#[derive(Debug)]
struct Post {
title: String,
preview_image: String,
summary: String,
slug: String,
render: Html<String>
}
#[derive(Debug)]
#[derive(Template)]
#[template(path = "posts.html")]
struct PostsTemplate {
posts: Vec<Post>
}
// Template for home
#[derive(Template)]
#[template(path = "home.html", ext = "html")]
struct HomeTemplate {
content: String
}
fn typst_to_html(typst: String) -> String {
let mut child = Command::new("typst")
.args(["compile", "-", "-", "--format", "html", "--features", "html" ])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed running typst");
let mut stdin = child.stdin.take().expect("Failed opening STDIN");
stdin.write_all(typst.as_bytes()).expect("Failed writing to STDIN");
drop(stdin);
let output = child.wait_with_output().expect("Failed reading output");
String::from_utf8_lossy(&output.stdout).to_string()
}
// , posts: &Vec<Post>
async fn get_post(
Path(slug): Path<String>,
posts: HashMap<String, Post>
) -> Html<String> {
if posts.contains_key(&slug) {
return posts[&slug].render.clone()
}
else {
return Html("404".to_string());
}
}
#[tokio::main]
async fn main() {
let home_html = Html(
HomeTemplate {
content: typst_to_html(fs::read_to_string(
"static/home.typ"
).expect("Couldnt read file"))
}.render().expect("Failed rendering home template")
);
let posts: HashMap<String, Post> = load_posts().expect("Failed loading posts");
let posts_html = Html(
PostsTemplate{
posts: posts.values().cloned().collect::<Vec<Post>>()
}.render().expect("Failed rendering posts")
);
let site: Router = Router::new()
.route("/", get(home_html))
.route("/posts", get(posts_html))
.route("/posts/{slug}", get(|slug| get_post(slug, posts)))
.nest_service("/static", ServeDir::new("static"));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
println!("Running on http://127.0.0.1:3000");
axum::serve(listener, site).await.unwrap();
}