More modularization
This commit is contained in:
parent
2052092796
commit
96575cf40b
14 changed files with 267 additions and 133 deletions
101
Cargo.lock
generated
101
Cargo.lock
generated
|
@ -26,6 +26,55 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.80"
|
version = "0.1.80"
|
||||||
|
@ -180,6 +229,33 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color-eyre"
|
name = "color-eyre"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
|
@ -207,6 +283,12 @@ dependencies = [
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "config"
|
name = "config"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
@ -676,6 +758,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
@ -752,6 +840,7 @@ name = "lyrs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"config",
|
"config",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -1365,6 +1454,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.60"
|
version = "2.0.60"
|
||||||
|
@ -1676,6 +1771,12 @@ version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.7.5", features = ["macros", "tokio"] }
|
axum = { version = "0.7.5", features = ["macros", "tokio"] }
|
||||||
|
clap = "4.5.4"
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
config = "0.14.0"
|
config = "0.14.0"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
|
1
src/cli.rs
Normal file
1
src/cli.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
28
src/error.rs
Normal file
28
src/error.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use axum::{http::StatusCode, response::IntoResponse};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error(pub Box<dyn std::error::Error>);
|
||||||
|
|
||||||
|
impl IntoResponse for Error {
|
||||||
|
fn into_response(self) -> axum::http::Response<axum::body::Body> {
|
||||||
|
error!("webserver error: {:?}", self.0);
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("internal server error: {}", self.0),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This enables using `?` on functions that return `Result<_, anyhow::Error>`
|
||||||
|
// to turn them into `Result<_, AppError>`. That way you don't need to do that
|
||||||
|
// manually.
|
||||||
|
impl<E> From<E> for Error
|
||||||
|
where
|
||||||
|
E: Into<Box<dyn std::error::Error>>,
|
||||||
|
{
|
||||||
|
fn from(err: E) -> Self {
|
||||||
|
Self(err.into())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,24 @@
|
||||||
use std::path::Path;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use tokio::sync::mpsc::channel;
|
use std::path::Path;
|
||||||
use tracing::{error, info, instrument};
|
use tokio::{sync::mpsc::channel, task::JoinHandle};
|
||||||
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::Besult;
|
pub type WatcherType = RecommendedWatcher;
|
||||||
|
pub type FileWatcher = (WatcherType, JoinHandle<()>);
|
||||||
pub type FileWatcher = RecommendedWatcher;
|
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{file_watcher, FileWatcher};
|
#![allow(unused_imports)]
|
||||||
|
pub use super::{file_monitor, file_watcher, FileWatcher};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_watcher<P, F>(dir: P, cb: F) -> Besult<FileWatcher>
|
/// Notifies your callback for each individual event
|
||||||
|
pub fn file_watcher<P, F>(dir: P, cb: F) -> Result<FileWatcher>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
F: Fn(Event) -> () + std::marker::Send + 'static,
|
F: Fn(Event) -> () + std::marker::Send + 'static,
|
||||||
{
|
{
|
||||||
|
// TODO: debounce?
|
||||||
let (tx, mut rx) = channel(1);
|
let (tx, mut rx) = channel(1);
|
||||||
let mut watcher = RecommendedWatcher::new(
|
let mut watcher = RecommendedWatcher::new(
|
||||||
move |res| match res {
|
move |res| match res {
|
||||||
|
@ -30,7 +32,7 @@ where
|
||||||
|
|
||||||
info!("Watching directory '{}'", dir.as_ref().display());
|
info!("Watching directory '{}'", dir.as_ref().display());
|
||||||
|
|
||||||
tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
while let Some(ev) = rx.recv().await {
|
while let Some(ev) = rx.recv().await {
|
||||||
cb(ev)
|
cb(ev)
|
||||||
}
|
}
|
||||||
|
@ -38,5 +40,37 @@ where
|
||||||
|
|
||||||
watcher.watch(dir.as_ref(), RecursiveMode::Recursive)?;
|
watcher.watch(dir.as_ref(), RecursiveMode::Recursive)?;
|
||||||
|
|
||||||
Ok(watcher)
|
Ok((watcher, handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only know when something changes
|
||||||
|
pub fn file_monitor<P, F>(dir: P, cb: F) -> Result<FileWatcher>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
F: Fn() -> () + std::marker::Send + 'static,
|
||||||
|
{
|
||||||
|
let (tx, mut rx) = tokio::sync::watch::channel(());
|
||||||
|
let mut watcher = RecommendedWatcher::new(
|
||||||
|
move |res| match res {
|
||||||
|
Ok(_e) => futures::executor::block_on(async {
|
||||||
|
tx.send_replace(());
|
||||||
|
}),
|
||||||
|
Err(e) => error!("Error from file_watcher: {e}"),
|
||||||
|
},
|
||||||
|
Config::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
info!("Watching directory '{}'", dir.as_ref().display());
|
||||||
|
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
while let Ok(_) = rx.changed().await {
|
||||||
|
cb();
|
||||||
|
// "good enough" debouncing
|
||||||
|
rx.mark_unchanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.watch(dir.as_ref(), RecursiveMode::Recursive)?;
|
||||||
|
|
||||||
|
Ok((watcher, handle))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
use tower_livereload::LiveReloadLayer;
|
|
||||||
|
|
||||||
pub fn live_reload_layer() -> LiveReloadLayer {
|
|
||||||
LiveReloadLayer::new()
|
|
||||||
}
|
|
82
src/main.rs
82
src/main.rs
|
@ -1,85 +1,21 @@
|
||||||
use axum::extract::State;
|
use crate::prelude::*;
|
||||||
use axum::response::IntoResponse;
|
|
||||||
use axum::routing::get;
|
|
||||||
use axum::{http::StatusCode, response::Html, serve, Router};
|
|
||||||
use minijinja::context;
|
|
||||||
use state::State as AppState;
|
|
||||||
pub use tracing::{debug, error, event, info, span, warn, Level};
|
|
||||||
|
|
||||||
use crate::live_reload::live_reload_layer;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Berr(Box<dyn std::error::Error>);
|
|
||||||
type Besult<T> = std::result::Result<T, Berr>;
|
|
||||||
|
|
||||||
|
mod cli;
|
||||||
|
mod error;
|
||||||
mod file_watcher;
|
mod file_watcher;
|
||||||
mod live_reload;
|
|
||||||
mod observe;
|
mod observe;
|
||||||
|
mod prelude;
|
||||||
|
mod result;
|
||||||
|
mod router;
|
||||||
mod state;
|
mod state;
|
||||||
mod static_files;
|
mod static_files;
|
||||||
mod tailwind;
|
mod tailwind;
|
||||||
mod templates;
|
mod templates;
|
||||||
|
mod webserver;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Besult<()> {
|
async fn main() -> Result<()> {
|
||||||
// load configuration?
|
// load configuration?
|
||||||
let _setup_logging = observe::setup_logging();
|
let _setup_logging = observe::setup_logging();
|
||||||
|
webserver::webserver(router::router().await?).await
|
||||||
let state = AppState::try_new().await?;
|
|
||||||
|
|
||||||
// TODO: only start tailwind if in dev mode?
|
|
||||||
tokio::spawn(async move { tailwind::start_watcher() });
|
|
||||||
|
|
||||||
let lr = live_reload_layer();
|
|
||||||
|
|
||||||
{
|
|
||||||
// TODO: only start watcher for dev mode?
|
|
||||||
let state = state.clone();
|
|
||||||
let lr = lr.reloader();
|
|
||||||
tokio::spawn(async move { state.templates.start_watcher(Some(lr)) });
|
|
||||||
}
|
|
||||||
|
|
||||||
let (static_file_service, _static_file_watcher) = static_files::router(Some(lr.reloader()))?;
|
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
|
|
||||||
info!("Listening on {listener:?}");
|
|
||||||
let router = Router::new()
|
|
||||||
.route("/", get(index))
|
|
||||||
.nest_service("/static", static_file_service)
|
|
||||||
.with_state(state);
|
|
||||||
|
|
||||||
Ok(serve(listener, router).await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn index(State(state): State<AppState>) -> Besult<Html<String>> {
|
|
||||||
Ok(Html(
|
|
||||||
state
|
|
||||||
.templates
|
|
||||||
.render("pages/index.html.jinja", context!())
|
|
||||||
.await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoResponse for Berr {
|
|
||||||
fn into_response(self) -> axum::http::Response<axum::body::Body> {
|
|
||||||
error!("webserver error: {:?}", self.0);
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("internal server error: {}", self.0),
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This enables using `?` on functions that return `Result<_, anyhow::Error>`
|
|
||||||
// to turn them into `Result<_, AppError>`. That way you don't need to do that
|
|
||||||
// manually.
|
|
||||||
impl<E> From<E> for Berr
|
|
||||||
where
|
|
||||||
E: Into<Box<dyn std::error::Error>>,
|
|
||||||
{
|
|
||||||
fn from(err: E) -> Self {
|
|
||||||
Self(err.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
5
src/prelude.rs
Normal file
5
src/prelude.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
pub use crate::error::Error;
|
||||||
|
pub use crate::result::Result;
|
||||||
|
pub use tracing::{debug, error, event, info, span, warn, Level};
|
3
src/result.rs
Normal file
3
src/result.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
31
src/router.rs
Normal file
31
src/router.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use crate::{prelude::*, state::State as AppState, static_files};
|
||||||
|
use axum::{extract::State, response::Html, routing::get, Router};
|
||||||
|
use minijinja::context;
|
||||||
|
use tower_livereload::LiveReloadLayer;
|
||||||
|
|
||||||
|
pub async fn router() -> Result<Router> {
|
||||||
|
let state = AppState::try_new().await?;
|
||||||
|
|
||||||
|
let lr = LiveReloadLayer::new();
|
||||||
|
let _template_file_watcher = state
|
||||||
|
.clone()
|
||||||
|
.templates
|
||||||
|
.start_watcher(Some(lr.reloader()))
|
||||||
|
.await?;
|
||||||
|
let (static_file_service, _static_file_watcher) = static_files::router(Some(lr.reloader()))?;
|
||||||
|
|
||||||
|
Ok(Router::new()
|
||||||
|
.route("/", get(index))
|
||||||
|
.nest_service("/static", static_file_service)
|
||||||
|
.layer(lr)
|
||||||
|
.with_state(state))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn index(State(state): State<AppState>) -> Result<Html<String>> {
|
||||||
|
Ok(Html(
|
||||||
|
state
|
||||||
|
.templates
|
||||||
|
.render("pages/index.html.jinja", context!())
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::templates::Templates;
|
use crate::{prelude::*, templates::Templates};
|
||||||
use crate::Besult;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -8,7 +7,7 @@ pub struct State {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub async fn try_new() -> Besult<Self> {
|
pub async fn try_new() -> Result<Self> {
|
||||||
let templates = Arc::new(Templates::try_load("src/templates").await?);
|
let templates = Arc::new(Templates::try_load("src/templates").await?);
|
||||||
|
|
||||||
Ok(Self { templates })
|
Ok(Self { templates })
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
use crate::file_watcher::prelude::*;
|
use crate::{
|
||||||
|
file_watcher::{file_monitor, prelude::*},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::Besult;
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use tower_livereload::Reloader;
|
use tower_livereload::Reloader;
|
||||||
use tracing::{info, instrument};
|
|
||||||
|
|
||||||
fn static_file_dir() -> &'static PathBuf {
|
fn static_file_dir() -> &'static PathBuf {
|
||||||
static STATIC_FILE_DIR: OnceLock<PathBuf> = OnceLock::new();
|
static STATIC_FILE_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||||
STATIC_FILE_DIR.get_or_init(|| PathBuf::from_str("static").unwrap())
|
STATIC_FILE_DIR.get_or_init(|| PathBuf::from_str("static").unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
pub fn router(reloader: Option<Reloader>) -> Result<(Router, Option<FileWatcher>)> {
|
||||||
pub fn router(reloader: Option<Reloader>) -> Besult<(Router, Option<FileWatcher>)> {
|
|
||||||
let watcher = if let Some(rl) = reloader {
|
let watcher = if let Some(rl) = reloader {
|
||||||
Some(file_watcher(static_file_dir(), move |ev| {
|
// TODO: debounce?
|
||||||
info!("Static File Watcher Event: {ev:#?}");
|
Some(file_monitor(static_file_dir(), move || {
|
||||||
|
info!("Static File Watcher Event");
|
||||||
rl.reload()
|
rl.reload()
|
||||||
})?)
|
})?)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use crate::{file_watcher::prelude::*, prelude::*};
|
||||||
|
|
||||||
use minijinja::Environment;
|
use minijinja::Environment;
|
||||||
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
|
|
||||||
use pathdiff::diff_paths;
|
use pathdiff::diff_paths;
|
||||||
use tokio::sync::{mpsc::channel, Mutex};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use tower_livereload::Reloader;
|
use tower_livereload::Reloader;
|
||||||
use tracing::{error, info};
|
use tracing::{info, instrument};
|
||||||
|
|
||||||
use crate::{file_watcher::file_watcher, Besult};
|
#[derive(Clone, Debug)]
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Templates {
|
pub struct Templates {
|
||||||
env: Arc<Mutex<Environment<'static>>>,
|
env: Arc<Mutex<Environment<'static>>>,
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Error = Box<dyn std::error::Error>;
|
|
||||||
|
|
||||||
impl Templates {
|
impl Templates {
|
||||||
pub fn for_dir<P: Into<PathBuf>>(dir: P) -> Self {
|
pub fn for_dir<P: Into<PathBuf>>(dir: P) -> Self {
|
||||||
let env = Arc::new(Mutex::new(Environment::new()));
|
let env = Arc::new(Mutex::new(Environment::new()));
|
||||||
|
@ -26,7 +21,7 @@ impl Templates {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn try_load<P: Into<PathBuf>>(dir: P) -> Result<Self, Error> {
|
pub async fn try_load<P: Into<PathBuf>>(dir: P) -> Result<Self> {
|
||||||
let result = Self::for_dir(dir);
|
let result = Self::for_dir(dir);
|
||||||
result.load_env().await?;
|
result.load_env().await?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
@ -35,7 +30,7 @@ impl Templates {
|
||||||
pub async fn start_watcher(
|
pub async fn start_watcher(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
reloader: Option<Reloader>,
|
reloader: Option<Reloader>,
|
||||||
) -> Besult<Option<RecommendedWatcher>> {
|
) -> Result<Option<FileWatcher>> {
|
||||||
if let Some(rl) = reloader {
|
if let Some(rl) = reloader {
|
||||||
Ok(Some(self.watch(rl).await?))
|
Ok(Some(self.watch(rl).await?))
|
||||||
} else {
|
} else {
|
||||||
|
@ -43,28 +38,21 @@ impl Templates {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn watch(self: Arc<Self>, reloader: Reloader) -> Besult<RecommendedWatcher> {
|
async fn watch(self: Arc<Self>, reloader: Reloader) -> Result<FileWatcher> {
|
||||||
let (tx, mut rx) = channel(1);
|
// TODO: only reload template that changed?
|
||||||
let watcher = file_watcher(self.dir.clone(), move |ev| {
|
let watcher = file_monitor(self.dir.clone(), move || {
|
||||||
info!("Template File Watcher Event: {ev:#?}");
|
futures::executor::block_on(async {
|
||||||
match tx.blocking_send(ev) {
|
self.load_env()
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) => error!("Error sending template file watcher event: {err}"),
|
|
||||||
};
|
|
||||||
})?;
|
|
||||||
let us = self.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
while let Some(_) = rx.recv().await {
|
|
||||||
us.load_env()
|
|
||||||
.await
|
.await
|
||||||
.expect("Failed to reload templates after template changed during runtime");
|
.expect("Failed to reload templates after template changed during runtime");
|
||||||
reloader.reload()
|
reloader.reload();
|
||||||
}
|
});
|
||||||
});
|
})?;
|
||||||
Ok(watcher)
|
Ok(watcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_env(&self) -> Result<(), Error> {
|
#[instrument]
|
||||||
|
pub async fn load_env(&self) -> Result<()> {
|
||||||
info!("Loading templates...");
|
info!("Loading templates...");
|
||||||
for d in walkdir::WalkDir::new(&self.dir) {
|
for d in walkdir::WalkDir::new(&self.dir) {
|
||||||
match d {
|
match d {
|
||||||
|
@ -76,7 +64,7 @@ impl Templates {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
info!("Loading template {filename:#?} ({d:#?})");
|
info!("Loading template {filename:?} ({d:?})");
|
||||||
self.env
|
self.env
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
|
@ -85,7 +73,6 @@ impl Templates {
|
||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Done loading templates!");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,11 +80,12 @@ impl Templates {
|
||||||
&self,
|
&self,
|
||||||
template_name: &str,
|
template_name: &str,
|
||||||
context: S,
|
context: S,
|
||||||
) -> Result<String, minijinja::Error> {
|
) -> Result<String> {
|
||||||
self.env
|
Ok(self
|
||||||
|
.env
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.get_template(template_name)?
|
.get_template(template_name)?
|
||||||
.render(context)
|
.render(context)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/webserver.rs
Normal file
11
src/webserver.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::{prelude::*, tailwind};
|
||||||
|
use axum::{serve, Router};
|
||||||
|
|
||||||
|
pub async fn webserver(router: Router) -> Result<()> {
|
||||||
|
// TODO: only start tailwind if in dev mode?
|
||||||
|
tokio::spawn(async move { tailwind::start_watcher() });
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
|
info!("Listening on {listener:?}");
|
||||||
|
Ok(serve(listener, router).await?)
|
||||||
|
}
|
Loading…
Reference in a new issue