Error handling I think
This commit is contained in:
parent
f408a87195
commit
d5a3d1582d
15 changed files with 196 additions and 110 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -816,6 +816,7 @@ dependencies = [
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
"redact",
|
"redact",
|
||||||
"serde",
|
"serde",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
@ -1401,18 +1402,18 @@ checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.59"
|
version = "1.0.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
|
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.59"
|
version = "1.0.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
|
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -29,6 +29,7 @@ notify = "6.1.1"
|
||||||
pathdiff = "0.2.1"
|
pathdiff = "0.2.1"
|
||||||
redact = { version = "0.1.9", features = ["serde"] }
|
redact = { version = "0.1.9", features = ["serde"] }
|
||||||
serde = "1.0.201"
|
serde = "1.0.201"
|
||||||
|
thiserror = "1.0.60"
|
||||||
tokio = { version = "1.37.0", features = ["full"] }
|
tokio = { version = "1.37.0", features = ["full"] }
|
||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
tower-http = { version = "0.5.2", features = ["fs"] }
|
tower-http = { version = "0.5.2", features = ["fs"] }
|
||||||
|
|
43
src/cli.rs
43
src/cli.rs
|
@ -1,5 +1,6 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, router::NewRouterError};
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
mod prelude {
|
mod prelude {
|
||||||
pub use clap::{Args, Parser, Subcommand};
|
pub use clap::{Args, Parser, Subcommand};
|
||||||
|
@ -23,26 +24,52 @@ enum Commands {
|
||||||
/// Doc comment
|
/// Doc comment
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
struct Run {
|
struct Run {
|
||||||
/// Doc comment
|
/// Whether or not to watch certain resource files for changes and reload accordingly
|
||||||
#[arg(short, long, default_value = None)]
|
#[arg(short, long, default_value = None)]
|
||||||
pub watch: bool,
|
pub watch: bool,
|
||||||
|
|
||||||
|
/// The address to bind to - you almost certainly want to use :: or 0.0.0.0 instead of the default
|
||||||
|
#[arg(short = 'H', long, default_value = "::1")]
|
||||||
|
pub host: String,
|
||||||
|
|
||||||
|
/// The port to bind to
|
||||||
|
#[arg(short, long, default_value = "3000")]
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum RunError {
|
||||||
|
#[error("router error: {0}")]
|
||||||
|
Router(#[from] NewRouterError),
|
||||||
|
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Run {
|
impl Run {
|
||||||
pub async fn run(&self) -> Result<()> {
|
pub async fn run(&self) -> Result<(), RunError> {
|
||||||
let (router, _watchers) = crate::router::router(self.watch).await?;
|
let (router, _watchers) = crate::router::router(self.watch).await?;
|
||||||
crate::webserver::webserver(router, self.watch).await
|
Ok(
|
||||||
|
crate::webserver::webserver(router, self.watch, Some(&self.host), Some(self.port))
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cli() -> Result<Cli> {
|
pub fn cli() -> Cli {
|
||||||
Ok(Cli::parse())
|
Cli::parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ExecError {
|
||||||
|
#[error("run error: {0}")]
|
||||||
|
Run(#[from] RunError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
pub async fn exec(self) -> Result<()> {
|
pub async fn exec(self) -> Result<(), ExecError> {
|
||||||
match self.command {
|
match self.command {
|
||||||
Commands::Run(args) => args.run().await,
|
Commands::Run(args) => Ok(args.run().await?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
28
src/error.rs
28
src/error.rs
|
@ -1,28 +0,0 @@
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ use crate::prelude::*;
|
||||||
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tokio::{sync::mpsc::channel, task::JoinHandle};
|
use tokio::{sync::mpsc::channel, task::JoinHandle};
|
||||||
use tracing::{error, info};
|
|
||||||
|
|
||||||
pub type WatcherType = RecommendedWatcher;
|
pub type WatcherType = RecommendedWatcher;
|
||||||
pub type FileWatcher = (WatcherType, JoinHandle<()>);
|
pub type FileWatcher = (WatcherType, JoinHandle<()>);
|
||||||
|
@ -13,9 +12,10 @@ pub mod prelude {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Notifies your callback for each individual event
|
/// Notifies your callback for each individual event
|
||||||
pub fn file_watcher<P, F>(dir: P, cb: F) -> Result<FileWatcher>
|
#[instrument(skip(callback))]
|
||||||
|
pub fn file_watcher<P, F>(dir: P, callback: F) -> Result<FileWatcher, notify::Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path> + std::fmt::Debug,
|
||||||
F: Fn(Event) -> () + std::marker::Send + 'static,
|
F: Fn(Event) -> () + std::marker::Send + 'static,
|
||||||
{
|
{
|
||||||
// TODO: debounce?
|
// TODO: debounce?
|
||||||
|
@ -23,6 +23,7 @@ where
|
||||||
let mut watcher = RecommendedWatcher::new(
|
let mut watcher = RecommendedWatcher::new(
|
||||||
move |res| match res {
|
move |res| match res {
|
||||||
Ok(e) => futures::executor::block_on(async {
|
Ok(e) => futures::executor::block_on(async {
|
||||||
|
trace!("Sending event: {e:?}");
|
||||||
tx.send(e).await.unwrap();
|
tx.send(e).await.unwrap();
|
||||||
}),
|
}),
|
||||||
Err(e) => error!("Error from file_watcher: {e}"),
|
Err(e) => error!("Error from file_watcher: {e}"),
|
||||||
|
@ -30,11 +31,11 @@ where
|
||||||
Config::default(),
|
Config::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
info!("Watching directory '{}'", dir.as_ref().display());
|
info!("watching");
|
||||||
|
|
||||||
let handle = 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)
|
callback(ev)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,9 +45,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only know when something changes
|
/// Only know when something changes
|
||||||
pub fn file_monitor<P, F>(dir: P, cb: F) -> Result<FileWatcher>
|
#[instrument(skip(callback))]
|
||||||
|
pub fn file_monitor<P, F>(dir: P, callback: F) -> Result<FileWatcher, notify::Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path> + std::fmt::Debug,
|
||||||
F: Fn() -> () + std::marker::Send + 'static,
|
F: Fn() -> () + std::marker::Send + 'static,
|
||||||
{
|
{
|
||||||
let (tx, mut rx) = tokio::sync::watch::channel(());
|
let (tx, mut rx) = tokio::sync::watch::channel(());
|
||||||
|
@ -60,11 +62,11 @@ where
|
||||||
Config::default(),
|
Config::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
info!("Watching directory '{}'", dir.as_ref().display());
|
info!("watching");
|
||||||
|
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
while let Ok(_) = rx.changed().await {
|
while let Ok(_) = rx.changed().await {
|
||||||
cb();
|
callback();
|
||||||
// "good enough" debouncing
|
// "good enough" debouncing
|
||||||
rx.mark_unchanged();
|
rx.mark_unchanged();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod error;
|
|
||||||
mod file_watcher;
|
mod file_watcher;
|
||||||
mod observe;
|
mod observe;
|
||||||
mod prelude;
|
mod prelude;
|
||||||
mod result;
|
|
||||||
mod router;
|
mod router;
|
||||||
mod state;
|
mod state;
|
||||||
mod static_files;
|
mod static_files;
|
||||||
|
@ -14,7 +12,6 @@ mod templates;
|
||||||
mod webserver;
|
mod webserver;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> AnonResult<()> {
|
||||||
let _setup_logging = observe::setup_logging();
|
Ok(cli::cli().exec().await?)
|
||||||
cli::cli()?.exec().await
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
pub use crate::error::Error;
|
pub use color_eyre::eyre::Result as AnonResult;
|
||||||
pub use crate::result::Result;
|
pub use std::result::Result;
|
||||||
pub use tracing::{debug, error, event, info, span, warn, Level};
|
pub use tracing::{debug, error, event, info, instrument, span, trace, warn, Level};
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
use crate::error::Error;
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
|
@ -1,9 +1,53 @@
|
||||||
use crate::{file_watcher::FileWatcher, prelude::*, state::State as AppState, static_files};
|
use crate::{
|
||||||
use axum::{extract::State, response::Html, routing::get, Router};
|
file_watcher::FileWatcher,
|
||||||
|
prelude::*,
|
||||||
|
state::{NewStateError, State as AppState},
|
||||||
|
static_files,
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
http::StatusCode,
|
||||||
|
response::{Html, IntoResponse},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
use minijinja::context;
|
use minijinja::context;
|
||||||
use tower_livereload::LiveReloadLayer;
|
use tower_livereload::LiveReloadLayer;
|
||||||
|
|
||||||
pub async fn router(with_watchers: bool) -> Result<(Router, Vec<Option<FileWatcher>>)> {
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum NewRouterError {
|
||||||
|
#[error("new state error: {0}")]
|
||||||
|
State(#[from] NewStateError),
|
||||||
|
|
||||||
|
#[error("watcher error: {0}")]
|
||||||
|
Watcher(#[from] notify::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ReqError {
|
||||||
|
#[error("template error: {0}")]
|
||||||
|
Template(#[from] minijinja::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for ReqError {
|
||||||
|
fn into_response(self) -> axum::http::Response<axum::body::Body> {
|
||||||
|
error!("webserver error: {:?}", self);
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
// TODO: don't expose raw errors over the internet?
|
||||||
|
format!("internal server error: {}", self),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ReqResult<T> = Result<T, ReqError>;
|
||||||
|
|
||||||
|
pub async fn router(
|
||||||
|
with_watchers: bool,
|
||||||
|
) -> Result<(Router, Vec<Option<FileWatcher>>), NewRouterError> {
|
||||||
let state = AppState::try_new().await?;
|
let state = AppState::try_new().await?;
|
||||||
|
|
||||||
let live_reload_layer: Option<LiveReloadLayer> = if with_watchers {
|
let live_reload_layer: Option<LiveReloadLayer> = if with_watchers {
|
||||||
|
@ -38,7 +82,7 @@ pub async fn router(with_watchers: bool) -> Result<(Router, Vec<Option<FileWatch
|
||||||
Ok((result, watchers))
|
Ok((result, watchers))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn index(State(state): State<AppState>) -> Result<Html<String>> {
|
async fn index(State(state): State<AppState>) -> ReqResult<Html<String>> {
|
||||||
Ok(Html(
|
Ok(Html(
|
||||||
state
|
state
|
||||||
.templates
|
.templates
|
||||||
|
@ -46,7 +90,7 @@ async fn index(State(state): State<AppState>) -> Result<Html<String>> {
|
||||||
.await?,
|
.await?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
async fn about(State(state): State<AppState>) -> Result<Html<String>> {
|
async fn about(State(state): State<AppState>) -> ReqResult<Html<String>> {
|
||||||
Ok(Html(
|
Ok(Html(
|
||||||
state
|
state
|
||||||
.templates
|
.templates
|
||||||
|
|
15
src/state.rs
15
src/state.rs
|
@ -1,4 +1,7 @@
|
||||||
use crate::{prelude::*, templates::Templates};
|
use crate::{
|
||||||
|
prelude::*,
|
||||||
|
templates::{TemplateLoadError, Templates},
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -7,9 +10,17 @@ pub struct State {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub async fn try_new() -> Result<Self> {
|
pub async fn try_new() -> Result<Self, NewStateError> {
|
||||||
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum NewStateError {
|
||||||
|
#[error("template load error: {0}")]
|
||||||
|
TemplateLoad(#[from] TemplateLoadError),
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ fn static_file_dir() -> &'static PathBuf {
|
||||||
STATIC_FILE_DIR.get_or_init(|| PathBuf::from_str("static").unwrap())
|
STATIC_FILE_DIR.get_or_init(|| PathBuf::from_str("static").unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn router(reloader: Option<Reloader>) -> Result<(Router, Option<FileWatcher>)> {
|
pub fn router(reloader: Option<Reloader>) -> Result<(Router, Option<FileWatcher>), notify::Error> {
|
||||||
let watcher = if let Some(rl) = reloader {
|
let watcher = if let Some(rl) = reloader {
|
||||||
// TODO: debounce?
|
// TODO: debounce?
|
||||||
Some(file_monitor(static_file_dir(), move || {
|
Some(file_monitor(static_file_dir(), move || {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
|
use crate::prelude::*;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncBufReadExt, BufReader},
|
io::{AsyncBufReadExt, BufReader},
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
use tracing::{error, event, info, Level};
|
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
pub fn start_watcher() {
|
pub fn start_watcher() {
|
||||||
info!("Starting tailwind...");
|
info!("starting");
|
||||||
match Command::new("tailwindcss")
|
match Command::new("tailwindcss")
|
||||||
.args(["-i", "src/style.css", "-o", "static/style.css", "--watch"])
|
.args(["-i", "src/style.css", "-o", "static/style.css", "--watch"])
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
|
@ -14,7 +15,7 @@ pub fn start_watcher() {
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
Ok(mut tw) => {
|
Ok(mut tw) => {
|
||||||
info!("Tailwind spawned!");
|
info!("spawned");
|
||||||
let mut stdout_reader = BufReader::new(tw.stdout.take().unwrap()).lines();
|
let mut stdout_reader = BufReader::new(tw.stdout.take().unwrap()).lines();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Ok(Some(l)) = stdout_reader.next_line().await {
|
while let Ok(Some(l)) = stdout_reader.next_line().await {
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
use crate::{file_watcher::prelude::*, prelude::*};
|
use crate::{file_watcher::prelude::*, prelude::*};
|
||||||
use minijinja::Environment;
|
use minijinja::Environment;
|
||||||
use pathdiff::diff_paths;
|
use pathdiff::diff_paths;
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tower_livereload::Reloader;
|
use tower_livereload::Reloader;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum TemplateLoadError {
|
||||||
|
#[error("template error: {0}")]
|
||||||
|
Minijinja(#[from] minijinja::Error),
|
||||||
|
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Templates {
|
pub struct Templates {
|
||||||
env: Arc<Mutex<Environment<'static>>>,
|
env: Arc<Mutex<Environment<'static>>>,
|
||||||
|
@ -20,7 +33,7 @@ impl Templates {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn try_load<P: Into<PathBuf>>(dir: P) -> Result<Self> {
|
pub async fn try_load<P: Into<PathBuf>>(dir: P) -> Result<Self, TemplateLoadError> {
|
||||||
let result = Self::for_dir(dir);
|
let result = Self::for_dir(dir);
|
||||||
result.load_env().await?;
|
result.load_env().await?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
@ -29,7 +42,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>,
|
||||||
) -> Result<Option<FileWatcher>> {
|
) -> Result<Option<FileWatcher>, notify::Error> {
|
||||||
if let Some(rl) = reloader {
|
if let Some(rl) = reloader {
|
||||||
Ok(Some(self.watch(rl).await?))
|
Ok(Some(self.watch(rl).await?))
|
||||||
} else {
|
} else {
|
||||||
|
@ -37,41 +50,50 @@ impl Templates {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn watch(self: Arc<Self>, reloader: Reloader) -> Result<FileWatcher> {
|
async fn watch(self: Arc<Self>, reloader: Reloader) -> Result<FileWatcher, notify::Error> {
|
||||||
// TODO: only reload template that changed?
|
let watcher = file_watcher(self.dir.clone(), move |ev| {
|
||||||
let watcher = file_monitor(self.dir.clone(), move || {
|
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
self.load_env()
|
if ev.kind.is_create() || ev.kind.is_modify() {
|
||||||
.await
|
for p in ev.paths {
|
||||||
.expect("Failed to reload templates after template changed during runtime");
|
let _ = self.load_template(&p).await;
|
||||||
|
}
|
||||||
reloader.reload();
|
reloader.reload();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})?;
|
})?;
|
||||||
Ok(watcher)
|
Ok(watcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_env(&self) -> Result<()> {
|
#[instrument(skip(self, p))]
|
||||||
info!("Loading templates...");
|
pub async fn load_template<P>(&self, p: P) -> Result<(), TemplateLoadError>
|
||||||
for d in walkdir::WalkDir::new(&self.dir) {
|
where
|
||||||
match d {
|
P: AsRef<Path> + Copy,
|
||||||
Ok(d) => {
|
{
|
||||||
// ignore editor temporary files
|
let p = p.as_ref();
|
||||||
if [".bck", ".tmp"].iter().any(|s| d.path().ends_with(s)) {
|
if p.is_dir() {
|
||||||
continue;
|
return Ok(());
|
||||||
}
|
}
|
||||||
if d.file_type().is_dir() {
|
let filename: String = diff_paths(p, "src/templates")
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let filename: String = diff_paths(d.path(), "src/templates")
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
info!("Loading template {filename:?} ({d:?})");
|
if [".bck", ".tmp"].iter().any(|s| filename.ends_with(s)) {
|
||||||
self.env
|
debug!("skipping temporary file");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
info!(filename);
|
||||||
|
Ok(self
|
||||||
|
.env
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.add_template_owned(filename, std::fs::read_to_string(d.path())?)?;
|
.add_template_owned(filename, std::fs::read_to_string(p)?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn load_env(&self) -> Result<(), TemplateLoadError> {
|
||||||
|
for d in walkdir::WalkDir::new(&self.dir) {
|
||||||
|
match d {
|
||||||
|
Ok(d) => self.load_template(d.path()).await?,
|
||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +104,7 @@ impl Templates {
|
||||||
&self,
|
&self,
|
||||||
template_name: &str,
|
template_name: &str,
|
||||||
context: S,
|
context: S,
|
||||||
) -> Result<String> {
|
) -> Result<String, minijinja::Error> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.env
|
.env
|
||||||
.lock()
|
.lock()
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head hx-preserve="true">
|
||||||
<title>Sup</title>
|
<title>Sup</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/static/style.css" />
|
<link rel="stylesheet" type="text/css" href="/static/style.css" />
|
||||||
|
<script hx-preserve="true" src="https://unpkg.com/htmx.org@1.9.12"
|
||||||
|
integrity="sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2"
|
||||||
|
crossorigin="anonymous" defer></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-base text-text">
|
<body class="bg-base text-text">
|
||||||
Page Template
|
Page Template
|
||||||
<nav hx-boost="true">
|
<nav>
|
||||||
<a href="/">Index</a>
|
<a href="/">Index</a>
|
||||||
<a href="/about">About</a>
|
<a href="/about">About</a>
|
||||||
</nav>
|
</nav>
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.12"
|
|
||||||
integrity="sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
|
use std::io;
|
||||||
|
|
||||||
use crate::{prelude::*, tailwind};
|
use crate::{prelude::*, tailwind};
|
||||||
use axum::{serve, Router};
|
use axum::{serve, Router};
|
||||||
|
|
||||||
pub async fn webserver(router: Router, with_watchers: bool) -> Result<()> {
|
#[instrument(skip(router))]
|
||||||
|
pub async fn webserver(
|
||||||
|
router: Router,
|
||||||
|
with_watchers: bool,
|
||||||
|
host: Option<&str>,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> Result<(), io::Error> {
|
||||||
if with_watchers {
|
if with_watchers {
|
||||||
tokio::spawn(async move { tailwind::start_watcher() });
|
tokio::spawn(async move { tailwind::start_watcher() });
|
||||||
}
|
}
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
let listener = tokio::net::TcpListener::bind((host.unwrap_or("::1"), port.unwrap_or(3000)))
|
||||||
info!("Listening on {listener:?}");
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let addr = listener.local_addr()?;
|
||||||
|
info!(%addr);
|
||||||
Ok(serve(listener, router).await?)
|
Ok(serve(listener, router).await?)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue