checkpoint
This commit is contained in:
+2
-2
@@ -42,7 +42,7 @@ impl FleenUi {
|
|||||||
match FleenApp::open(path.clone()) {
|
match FleenApp::open(path.clone()) {
|
||||||
Ok(app) => {
|
Ok(app) => {
|
||||||
self.app = Some(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) }
|
Err(err) => { self.error = Some(err) }
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ impl FleenUi {
|
|||||||
match FleenApp::create(path.clone()) {
|
match FleenApp::create(path.clone()) {
|
||||||
Ok(app) => {
|
Ok(app) => {
|
||||||
self.app = Some(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) }
|
Err(err) => { self.error = Some(err) }
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-19
@@ -1,6 +1,6 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use markdown::message::Message;
|
use markdown::message::Message;
|
||||||
use markdown::{Constructs, Options, ParseOptions};
|
use markdown::{Constructs, Options, ParseOptions};
|
||||||
use markdown::mdast::Node;
|
use markdown::mdast::Node;
|
||||||
@@ -30,11 +30,11 @@ pub struct Frontmatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Frontmatter {
|
impl Frontmatter {
|
||||||
fn apply_layout(self, content: String, filename: PathBuf, root: &PathBuf) -> Result<RenderOutput, RenderError> {
|
fn apply_layout(self, content: String, filename: PathBuf, root: &Path) -> Result<RenderOutput, RenderError> {
|
||||||
let title = self.title.unwrap_or(String::new());
|
let title = self.title.unwrap_or_default();
|
||||||
let wrapped = if let Some(layout) = self.layout {
|
let wrapped = if let Some(layout) = self.layout {
|
||||||
let absolute_layout = root.join(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())
|
layout.replace("$title", title.as_str()).replace("$content", content.as_str())
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
@@ -50,19 +50,19 @@ impl Frontmatter {
|
|||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum RenderError {
|
pub enum RenderError {
|
||||||
#[error("Error reading {1}: {0}")]
|
#[error("Error reading {1}: {0}")]
|
||||||
FileReadError(Error, PathBuf),
|
FileRead(Error, PathBuf),
|
||||||
#[error("Error parsing Markdown {1}: {0}")]
|
#[error("Error parsing Markdown {1}: {0}")]
|
||||||
MarkdownParseError(Message, PathBuf),
|
MarkdownParse(Message, PathBuf),
|
||||||
#[error("Error parsing frontmatter in {1}: {0}")]
|
#[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
|
/// 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
|
/// (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
|
/// 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.
|
/// index.html is what a reasonable webserver will pick as the default file.
|
||||||
pub fn default_path(root: &PathBuf) -> PathBuf {
|
pub fn default_path(root: &Path) -> PathBuf {
|
||||||
if let Ok(true) = fs::exists(root.join("/index.html")) {
|
if let Ok(true) = fs::exists(root.join("index.html")) {
|
||||||
"index.html".into()
|
"index.html".into()
|
||||||
} else {
|
} else {
|
||||||
"index.md".into()
|
"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
|
/// 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<RenderOutput, RenderError> {
|
pub fn render(source: PathBuf, root: &Path) -> Result<RenderOutput, RenderError> {
|
||||||
let extension = source.extension().map(|o| o.to_str().unwrap());
|
let extension = source.extension().map(|o| o.to_str().unwrap());
|
||||||
if skipped_path(source.clone()) {
|
if skipped_path(source.clone()) {
|
||||||
// Skipped path, nothing
|
// Skipped path, nothing
|
||||||
@@ -99,7 +99,7 @@ pub fn render(source: PathBuf, root: &PathBuf) -> Result<RenderOutput, RenderErr
|
|||||||
// If any element of the path starts with an underscore, we want to skip rendering it.
|
// If any element of the path starts with an underscore, we want to skip rendering it.
|
||||||
// In addition, if a cheeky person has put .. in the path, just skip it (which will trigger a 404 from the dev server)
|
// In addition, if a cheeky person has put .. in the path, just skip it (which will trigger a 404 from the dev server)
|
||||||
fn skipped_path(source: PathBuf) -> bool {
|
fn skipped_path(source: PathBuf) -> bool {
|
||||||
source.into_iter().any(|el| {
|
source.iter().any(|el| {
|
||||||
match el.to_str() {
|
match el.to_str() {
|
||||||
Some("..") => true,
|
Some("..") => true,
|
||||||
Some(s) if s.starts_with("_") => 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
|
// This gets called by `render` if the source path extension is md
|
||||||
fn render_as_markdown(source: PathBuf, root: &PathBuf) -> Result<RenderOutput, RenderError> {
|
fn render_as_markdown(source: PathBuf, root: &Path) -> Result<RenderOutput, RenderError> {
|
||||||
let absolute_source = root.join(source.clone());
|
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 options = markdown_options();
|
||||||
let html = markdown::to_html_with_options(contents.as_str(), &options).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::MarkdownParseError(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())? {
|
if let Some(frontmatter) = find_frontmatter(ast, source.clone())? {
|
||||||
frontmatter.apply_layout(html, source, root)
|
frontmatter.apply_layout(html, source, root)
|
||||||
@@ -141,9 +141,9 @@ fn markdown_options() -> Options {
|
|||||||
// Look for and try to parse toml frontmatter
|
// Look for and try to parse toml frontmatter
|
||||||
fn find_frontmatter(node: Node, source: PathBuf) -> Result<Option<Frontmatter>, RenderError> {
|
fn find_frontmatter(node: Node, source: PathBuf) -> Result<Option<Frontmatter>, RenderError> {
|
||||||
if let Some(children) = node.children() {
|
if let Some(children) = node.children() {
|
||||||
for child in children.into_iter() {
|
for child in children.iter() {
|
||||||
if let Node::Toml(toml_str) = child {
|
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))
|
return Ok(Some(fmatter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn render_file(path: impl Into<PathBuf>) -> RenderOutput {
|
fn render_file(path: impl Into<PathBuf>) -> RenderOutput {
|
||||||
match render(path.into(), &("./testdata".into())) {
|
match render(path.into(), Path::new("./testdata")) {
|
||||||
Ok(ro) => ro,
|
Ok(ro) => ro,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{}", e);
|
println!("{}", e);
|
||||||
@@ -209,7 +209,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_raw() {
|
fn test_raw() {
|
||||||
let contents = render_file("raw.txt");
|
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]
|
#[test]
|
||||||
|
|||||||
+10
-11
@@ -1,6 +1,5 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::http::Uri;
|
use axum::http::Uri;
|
||||||
@@ -9,24 +8,23 @@ use axum::Router;
|
|||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use crate::renderer::{default_path, render, RenderOutput};
|
use crate::renderer::{default_path, render, RenderOutput};
|
||||||
|
|
||||||
pub async fn start_server(root: PathBuf) {
|
pub async fn start_server(root: PathBuf, port: u32) {
|
||||||
let state = Arc::new(root);
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(|State(root): State<Arc<PathBuf>>| async {
|
.route("/", get(|State(root): State<PathBuf>| async move {
|
||||||
|
// We need a separate route for the default path because {*p} must match at least one thing
|
||||||
let path = default_path(&root);
|
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<Arc<PathBuf>>, uri: Uri| async move {
|
.route("/{*path}", get(|State(root): State<PathBuf>, uri: Uri| async move {
|
||||||
serve_path(String::from(uri.path()), root)
|
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(format!("0.0.0.0:{}", port)).await.unwrap();
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_path(path: String, root: impl AsRef<PathBuf>) -> impl IntoResponse {
|
fn serve_path(path: String, root: PathBuf) -> impl IntoResponse {
|
||||||
let path = path.strip_prefix("/").unwrap_or(path.as_str());
|
let path = path.strip_prefix("/").unwrap_or(path.as_str());
|
||||||
let render = render(path.into(), root.as_ref());
|
let render = render(path.into(), root.as_ref());
|
||||||
|
|
||||||
@@ -44,6 +42,7 @@ fn serve_path(path: String, root: impl AsRef<PathBuf>) -> impl IntoResponse {
|
|||||||
}
|
}
|
||||||
Ok(RenderOutput::NoOutput) |
|
Ok(RenderOutput::NoOutput) |
|
||||||
Ok(RenderOutput::Dir(_)) => {
|
Ok(RenderOutput::Dir(_)) => {
|
||||||
|
// TODO a nicer 404 page?
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(404)
|
.status(404)
|
||||||
.body(Body::from(include_str!("../templates/404.html"))).unwrap()
|
.body(Body::from(include_str!("../templates/404.html"))).unwrap()
|
||||||
|
|||||||
Reference in New Issue
Block a user