refactoring some stuff
This commit is contained in:
parent
9121a3add4
commit
33416e8963
17 changed files with 266 additions and 109 deletions
|
@ -23,7 +23,8 @@ serde = { version = "1.0.183", features = ["derive"] }
|
|||
serde_json = "1.0.105"
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
toml_edit = { version = "0.19.14", features = ["serde"] }
|
||||
tower-http = { version = "0.5.1", features = ["fs"] }
|
||||
tower-livereload = "0.9.2"
|
||||
walkdir = "2.3.3"
|
||||
ulid = "1.0.0"
|
||||
url = "2.5.0"
|
||||
base64 = "0.21.7"
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#[cfg(debug_assertions)]
|
||||
mod live_reload;
|
||||
|
||||
use std::{net::Ipv4Addr, path::PathBuf, sync::Arc};
|
||||
use std::{borrow::Cow, net::Ipv4Addr, path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
extract::{RawQuery, State},
|
||||
extract::{Path, RawQuery, State},
|
||||
http::{header::CONTENT_TYPE, HeaderValue, StatusCode},
|
||||
response::{Html, IntoResponse, Response},
|
||||
routing::get,
|
||||
Router,
|
||||
|
@ -13,16 +14,18 @@ use axum::{
|
|||
use log::{error, info};
|
||||
use pulldown_cmark::escape::escape_html;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
use crate::state::{Source, Treehouse};
|
||||
|
||||
use super::Paths;
|
||||
|
||||
struct SystemPages {
|
||||
index: String,
|
||||
four_oh_four: String,
|
||||
b_docs: String,
|
||||
sandbox: String,
|
||||
|
||||
navmap: String,
|
||||
}
|
||||
|
||||
struct Server {
|
||||
|
@ -33,19 +36,27 @@ struct Server {
|
|||
|
||||
pub async fn serve(treehouse: Treehouse, paths: &Paths<'_>, port: u16) -> anyhow::Result<()> {
|
||||
let app = Router::new()
|
||||
.nest_service("/", ServeDir::new(paths.target_dir))
|
||||
.route("/", get(index))
|
||||
.route("/*page", get(page))
|
||||
.route("/b", get(branch))
|
||||
.route("/navmap.js", get(navmap))
|
||||
.route("/sandbox", get(sandbox))
|
||||
.route("/static/*file", get(static_file))
|
||||
.fallback(get(four_oh_four))
|
||||
.with_state(Arc::new(Server {
|
||||
treehouse,
|
||||
target_dir: paths.target_dir.to_owned(),
|
||||
system_pages: SystemPages {
|
||||
index: std::fs::read_to_string(paths.target_dir.join("index.html"))
|
||||
.context("cannot read index page")?,
|
||||
four_oh_four: std::fs::read_to_string(paths.target_dir.join("_treehouse/404.html"))
|
||||
.context("cannot read 404 page")?,
|
||||
b_docs: std::fs::read_to_string(paths.target_dir.join("_treehouse/b.html"))
|
||||
.context("cannot read /b documentation page")?,
|
||||
sandbox: std::fs::read_to_string(paths.target_dir.join("static/html/sandbox.html"))
|
||||
.context("cannot read sandbox page")?,
|
||||
navmap: std::fs::read_to_string(paths.target_dir.join("navmap.js"))
|
||||
.context("cannot read navigation map")?,
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -57,6 +68,65 @@ pub async fn serve(treehouse: Treehouse, paths: &Paths<'_>, port: u16) -> anyhow
|
|||
Ok(axum::serve(listener, app).await?)
|
||||
}
|
||||
|
||||
fn get_content_type(path: &str) -> Option<&'static str> {
|
||||
match () {
|
||||
_ if path.ends_with(".html") => Some("text/html"),
|
||||
_ if path.ends_with(".js") => Some("text/javascript"),
|
||||
_ if path.ends_with(".woff2") => Some("font/woff2"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn index(State(state): State<Arc<Server>>) -> Response {
|
||||
Html(state.system_pages.index.clone()).into_response()
|
||||
}
|
||||
|
||||
async fn navmap(State(state): State<Arc<Server>>) -> Response {
|
||||
let mut response = state.system_pages.navmap.clone().into_response();
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("text/javascript"));
|
||||
response
|
||||
}
|
||||
|
||||
async fn four_oh_four(State(state): State<Arc<Server>>) -> Response {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
Html(state.system_pages.four_oh_four.clone()),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
async fn static_file(Path(path): Path<String>, State(state): State<Arc<Server>>) -> Response {
|
||||
if let Ok(file) = tokio::fs::read(state.target_dir.join("static").join(&path)).await {
|
||||
let mut response = file.into_response();
|
||||
if let Some(content_type) = get_content_type(&path) {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static(content_type));
|
||||
} else {
|
||||
response.headers_mut().remove(CONTENT_TYPE);
|
||||
}
|
||||
response
|
||||
} else {
|
||||
four_oh_four(State(state)).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn page(Path(path): Path<String>, State(state): State<Arc<Server>>) -> Response {
|
||||
let path = if !path.ends_with(".html") {
|
||||
Cow::Owned(path + ".html")
|
||||
} else {
|
||||
Cow::Borrowed(&path)
|
||||
};
|
||||
|
||||
if let Ok(file) = tokio::fs::read(state.target_dir.join(&*path)).await {
|
||||
([(CONTENT_TYPE, "text/html")], file).into_response()
|
||||
} else {
|
||||
four_oh_four(State(state)).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn sandbox(State(state): State<Arc<Server>>) -> Response {
|
||||
// Small hack to prevent the LiveReloadLayer from injecting itself into the sandbox.
|
||||
// The sandbox is always nested under a different page, so there's no need to do that.
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use axum::{http::Response, Router};
|
||||
use axum::{
|
||||
http::{header::CONTENT_TYPE, Response},
|
||||
Router,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DisableLiveReload;
|
||||
|
@ -8,8 +11,9 @@ pub fn live_reload(router: Router) -> Router {
|
|||
|response: &Response<_>| {
|
||||
let is_html = response
|
||||
.headers()
|
||||
.get("Content-Type")
|
||||
.is_some_and(|v| v == "text/html");
|
||||
.get(CONTENT_TYPE)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.is_some_and(|v| v.starts_with("text/html"));
|
||||
let is_disabled = response.extensions().get::<DisableLiveReload>().is_some();
|
||||
is_html && !is_disabled
|
||||
},
|
||||
|
|
|
@ -100,6 +100,10 @@ impl Config {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn page_url(&self, page: &str) -> String {
|
||||
format!("{}/{}", self.site, page)
|
||||
}
|
||||
|
||||
pub fn pic_url(&self, id: &str) -> String {
|
||||
format!(
|
||||
"{}/static/pic/{}",
|
||||
|
|
|
@ -122,9 +122,7 @@ pub fn branch_to_html(
|
|||
"".into(),
|
||||
)
|
||||
}),
|
||||
"page" => {
|
||||
Some((format!("{}/{}.html", config.site, linked).into(), "".into()))
|
||||
}
|
||||
"page" => Some((config.page_url(linked).into(), "".into())),
|
||||
"pic" => config.pics.get(linked).map(|filename| {
|
||||
(
|
||||
format!("{}/static/pic/{}", config.site, &filename).into(),
|
||||
|
@ -160,7 +158,7 @@ pub fn branch_to_html(
|
|||
if let Content::Link(link) = &branch.attributes.content {
|
||||
write!(
|
||||
s,
|
||||
"<noscript><a class=\"navigate icon-go\" href=\"{}/{}.html\">Go to linked tree: <code>{}</code></a></noscript>",
|
||||
"<noscript><a class=\"navigate icon-go\" href=\"{}/{}\">Go to linked tree: <code>{}</code></a></noscript>",
|
||||
EscapeAttribute(&config.site),
|
||||
EscapeAttribute(link),
|
||||
EscapeHtml(link),
|
||||
|
@ -174,7 +172,7 @@ pub fn branch_to_html(
|
|||
if let Content::Link(link) = &branch.attributes.content {
|
||||
write!(
|
||||
s,
|
||||
"<a class=\"icon icon-go\" href=\"{}/{}.html\" title=\"linked tree\"></a>",
|
||||
"<a class=\"icon icon-go\" href=\"{}/{}\" title=\"linked tree\"></a>",
|
||||
EscapeAttribute(&config.site),
|
||||
EscapeAttribute(link),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue