From db367d2914a51123eac6122a79a3b91a304da075 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Mon, 1 Sep 2025 19:28:21 -0500 Subject: [PATCH] checkpoint --- src/main.rs | 4 ++-- src/renderer.rs | 38 +++++++++++++++++++------------------- src/server.rs | 21 ++++++++++----------- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/main.rs b/src/main.rs index b05ed3f..8cbd8bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ impl FleenUi { match FleenApp::open(path.clone()) { Ok(app) => { self.app = Some(app); - self.server_handle = Some(tokio::spawn(start_server(path))) + self.server_handle = Some(tokio::spawn(start_server(path, 3000))) } Err(err) => { self.error = Some(err) } } @@ -52,7 +52,7 @@ impl FleenUi { match FleenApp::create(path.clone()) { Ok(app) => { self.app = Some(app); - self.server_handle = Some(tokio::spawn(start_server(path))) + self.server_handle = Some(tokio::spawn(start_server(path, 3000))) } Err(err) => { self.error = Some(err) } } diff --git a/src/renderer.rs b/src/renderer.rs index 0aaa43f..6da7076 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,6 +1,6 @@ use std::fs; use std::io::Error; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use markdown::message::Message; use markdown::{Constructs, Options, ParseOptions}; use markdown::mdast::Node; @@ -30,11 +30,11 @@ pub struct Frontmatter { } impl Frontmatter { - fn apply_layout(self, content: String, filename: PathBuf, root: &PathBuf) -> Result { - let title = self.title.unwrap_or(String::new()); + fn apply_layout(self, content: String, filename: PathBuf, root: &Path) -> Result { + let title = self.title.unwrap_or_default(); let wrapped = if let Some(layout) = self.layout { let absolute_layout = root.join(layout); - let layout = fs::read_to_string(absolute_layout.clone()).map_err(|e| RenderError::FileReadError(e, absolute_layout))?; + let layout = fs::read_to_string(absolute_layout.clone()).map_err(|e| RenderError::FileRead(e, absolute_layout))?; layout.replace("$title", title.as_str()).replace("$content", content.as_str()) } else { content @@ -50,19 +50,19 @@ impl Frontmatter { #[derive(Error, Debug)] pub enum RenderError { #[error("Error reading {1}: {0}")] - FileReadError(Error, PathBuf), + FileRead(Error, PathBuf), #[error("Error parsing Markdown {1}: {0}")] - MarkdownParseError(Message, PathBuf), + MarkdownParse(Message, PathBuf), #[error("Error parsing frontmatter in {1}: {0}")] - FrontmatterParseError(toml::de::Error, PathBuf) + FrontmatterParse(toml::de::Error, PathBuf) } /// Return the path for the default thing to render for the test server, which is either index.html /// (if it exists) or index.md. Note that this does not guarantee that the file actually exists, it /// just chooses that because those are the files which can become index.html in the output and /// index.html is what a reasonable webserver will pick as the default file. -pub fn default_path(root: &PathBuf) -> PathBuf { - if let Ok(true) = fs::exists(root.join("/index.html")) { +pub fn default_path(root: &Path) -> PathBuf { + if let Ok(true) = fs::exists(root.join("index.html")) { "index.html".into() } else { "index.md".into() @@ -70,7 +70,7 @@ pub fn default_path(root: &PathBuf) -> PathBuf { } /// Take a source file path (relative to the root) and the root path, and return a RenderOutput for it -pub fn render(source: PathBuf, root: &PathBuf) -> Result { +pub fn render(source: PathBuf, root: &Path) -> Result { let extension = source.extension().map(|o| o.to_str().unwrap()); if skipped_path(source.clone()) { // Skipped path, nothing @@ -99,7 +99,7 @@ pub fn render(source: PathBuf, root: &PathBuf) -> Result bool { - source.into_iter().any(|el| { + source.iter().any(|el| { match el.to_str() { Some("..") => true, Some(s) if s.starts_with("_") => true, @@ -109,12 +109,12 @@ fn skipped_path(source: PathBuf) -> bool { } // This gets called by `render` if the source path extension is md -fn render_as_markdown(source: PathBuf, root: &PathBuf) -> Result { +fn render_as_markdown(source: PathBuf, root: &Path) -> Result { let absolute_source = root.join(source.clone()); - let contents = fs::read_to_string(absolute_source.clone()).map_err(|e| RenderError::FileReadError(e, source.clone()))?; + let contents = fs::read_to_string(absolute_source.clone()).map_err(|e| RenderError::FileRead(e, source.clone()))?; let options = markdown_options(); - let html = markdown::to_html_with_options(contents.as_str(), &options).map_err(|e| RenderError::MarkdownParseError(e, source.clone()))?; - let ast = markdown::to_mdast(contents.as_str(), &options.parse).map_err(|e| RenderError::MarkdownParseError(e, source.clone()))?; + let html = markdown::to_html_with_options(contents.as_str(), &options).map_err(|e| RenderError::MarkdownParse(e, source.clone()))?; + let ast = markdown::to_mdast(contents.as_str(), &options.parse).map_err(|e| RenderError::MarkdownParse(e, source.clone()))?; if let Some(frontmatter) = find_frontmatter(ast, source.clone())? { frontmatter.apply_layout(html, source, root) @@ -141,9 +141,9 @@ fn markdown_options() -> Options { // Look for and try to parse toml frontmatter fn find_frontmatter(node: Node, source: PathBuf) -> Result, RenderError> { if let Some(children) = node.children() { - for child in children.into_iter() { + for child in children.iter() { if let Node::Toml(toml_str) = child { - let fmatter: Frontmatter = toml::from_str(toml_str.value.as_str()).map_err(|e| RenderError::FrontmatterParseError(e, source))?; + let fmatter: Frontmatter = toml::from_str(toml_str.value.as_str()).map_err(|e| RenderError::FrontmatterParse(e, source))?; return Ok(Some(fmatter)) } } @@ -156,7 +156,7 @@ mod tests { use super::*; fn render_file(path: impl Into) -> RenderOutput { - match render(path.into(), &("./testdata".into())) { + match render(path.into(), Path::new("./testdata")) { Ok(ro) => ro, Err(e) => { println!("{}", e); @@ -209,7 +209,7 @@ mod tests { #[test] fn test_raw() { let contents = render_file("raw.txt"); - assert_eq!(contents, RenderOutput::RawFile(PathBuf::from("raw.txt"))); + assert_eq!(contents, RenderOutput::RawFile(PathBuf::from("./testdata/raw.txt"))); } #[test] diff --git a/src/server.rs b/src/server.rs index 7e37671..0c2cf19 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,5 @@ use std::fs; use std::path::PathBuf; -use std::sync::Arc; use axum::body::Body; use axum::extract::State; use axum::http::Uri; @@ -9,24 +8,23 @@ use axum::Router; use axum::routing::get; use crate::renderer::{default_path, render, RenderOutput}; -pub async fn start_server(root: PathBuf) { - let state = Arc::new(root); +pub async fn start_server(root: PathBuf, port: u32) { let app = Router::new() - .route("/", get(|State(root): State>| async { + .route("/", get(|State(root): State| async move { + // We need a separate route for the default path because {*p} must match at least one thing let path = default_path(&root); - serve_path(String::from(path.to_str().unwrap()), root) + serve_path(String::from(path.to_str().unwrap()), root.clone()) })) - .route("/{*path}", get(|State(root): State>, uri: Uri| async move { - serve_path(String::from(uri.path()), root) + .route("/{*path}", get(|State(root): State, uri: Uri| async move { + serve_path(String::from(uri.path()), root.clone()) })) - .with_state(state); + .with_state(root); - // run our app with hyper, listening globally on port 3000 - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); + let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await.unwrap(); axum::serve(listener, app).await.unwrap(); } -fn serve_path(path: String, root: impl AsRef) -> impl IntoResponse { +fn serve_path(path: String, root: PathBuf) -> impl IntoResponse { let path = path.strip_prefix("/").unwrap_or(path.as_str()); let render = render(path.into(), root.as_ref()); @@ -44,6 +42,7 @@ fn serve_path(path: String, root: impl AsRef) -> impl IntoResponse { } Ok(RenderOutput::NoOutput) | Ok(RenderOutput::Dir(_)) => { + // TODO a nicer 404 page? Response::builder() .status(404) .body(Body::from(include_str!("../templates/404.html"))).unwrap()