use axum::response::Html; use axum::extract::State; use std::sync::Arc; use askama_axum::Template; use axum::{routing::get, Router}; use std::borrow::Cow; use std::ffi::OsString; use std::fmt; use std::fs; use std::io::*; use std::path::Path; use std::path::PathBuf; trait Post<'a>: fmt::Debug { fn dump(&self); fn display_name(&'a self) -> &'a str; fn get_content(&self) -> Cow; } #[derive(Debug)] struct MockPost; impl<'a> Post<'a> for MockPost { fn dump(&self) { println!("Post content goes here."); } fn display_name(&'a self) -> &'a str { "" } fn get_content(&self) -> Cow { Cow::Borrowed("") } } struct MdFilePost { path: PathBuf, name: OsString, } impl MdFilePost { fn new(path: &Path) -> Self { Self { path: path.to_path_buf(), name: path.file_stem().unwrap_or_default().to_owned(), } } } impl<'a> Post<'a> for MdFilePost { fn dump(&self) { println!("{}: {}", &self.display_name(), &self.get_content()); } fn display_name(&'a self) -> &'a str { self.name.to_str().unwrap() } fn get_content(&self) -> Cow { let mut file = std::fs::File::open(&self.path).unwrap(); let mut markdown = String::new(); file.read_to_string(&mut markdown).unwrap(); Cow::Owned(markdown) } } impl fmt::Debug for MdFilePost { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "MdFilePost '{}'", self.display_name()) } } trait Posts { fn get_posts(&self) -> impl Iterator; } struct FsDirPosts { path: PathBuf, } impl FsDirPosts { fn new(path: &str) -> Self { Self { path: PathBuf::from(path), } } } impl Posts for FsDirPosts { fn get_posts(&self) -> impl Iterator { let dirs = fs::read_dir(&self.path).unwrap(); dirs.flatten().map(|d| MdFilePost::new(&d.path())) } } trait Page<'a>: Template { fn from_post(post: &impl Post<'a>) -> Self; } #[derive(Template)] #[template(path = "post.html")] struct MdPage { article: String, } impl<'a> Page<'a> for MdPage { fn from_post(post: &impl Post<'a>) -> Self { Self { article: post.get_content().into_owned(), } } } fn test_get_posts(posts: &impl Posts) { for post in posts.get_posts() { let page = MdPage::from_post(&post); println!("{}", page.render().unwrap()); } } async fn handler(State(posts): State>) -> Html { let post = posts.get_posts().next().unwrap(); let page = MdPage::from_post(&post); Html(page.render().unwrap()) } #[tokio::main] async fn main() { let repo = Arc::new(FsDirPosts::new(&format!("/data/ct/{}", "blog"))); let app = Router::new().route("/posts", get(handler)).with_state(repo); let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); axum::serve(listener, app).await.unwrap(); }