diff --git a/Cargo.lock b/Cargo.lock index 3300f43..7860f0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2741,6 +2741,15 @@ dependencies = [ "syn", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -3390,6 +3399,26 @@ dependencies = [ "serde", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.3" @@ -3667,6 +3696,26 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "kqueue" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -3951,6 +4000,16 @@ dependencies = [ "serde", ] +[[package]] +name = "minijinja-autoreload" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4caaefe206f2225f4da38d7f08aa7e1702920acc5505f01a7f33396766ae60a" +dependencies = [ + "minijinja", + "notify", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4262,6 +4321,23 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" +dependencies = [ + "bitflags", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio 0.8.4", + "walkdir", + "winapi 0.3.9", +] + [[package]] name = "num-bigint" version = "0.3.3" @@ -6248,6 +6324,7 @@ dependencies = [ "env_logger 0.9.1", "log", "minijinja", + "minijinja-autoreload", ] [[package]] diff --git a/templating/minijinja/Cargo.toml b/templating/minijinja/Cargo.toml index d9071ac..8a1ce6b 100644 --- a/templating/minijinja/Cargo.toml +++ b/templating/minijinja/Cargo.toml @@ -11,3 +11,4 @@ actix-utils = "3" env_logger = "0.9" log = "0.4" minijinja = { version = "0.23", features = ["source"] } +minijinja-autoreload = "0.23" diff --git a/templating/minijinja/README.md b/templating/minijinja/README.md index 4ad6050..ecc573d 100644 --- a/templating/minijinja/README.md +++ b/templating/minijinja/README.md @@ -6,7 +6,7 @@ Minimal example of using the template engine [MiniJinja](https://github.com/Keat ```sh cd templating/minijinja -cargo run +TEMPLATE_AUTORELOAD=true cargo run ``` - diff --git a/templating/minijinja/src/main.rs b/templating/minijinja/src/main.rs index 37fee7f..1f35e69 100644 --- a/templating/minijinja/src/main.rs +++ b/templating/minijinja/src/main.rs @@ -1,5 +1,6 @@ use actix_utils::future::{ready, Ready}; -use std::collections::HashMap; +use minijinja_autoreload::AutoReloader; +use std::{collections::HashMap, env, path::PathBuf}; use actix_web::{ dev::{self, ServiceResponse}, @@ -10,9 +11,8 @@ use actix_web::{ }; use actix_web_lab::respond::Html; -#[derive(Debug)] struct MiniJinjaRenderer { - tmpl_env: web::Data>, + tmpl_env: web::Data, } impl MiniJinjaRenderer { @@ -22,6 +22,8 @@ impl MiniJinjaRenderer { ctx: impl Into, ) -> actix_web::Result { self.tmpl_env + .acquire_env() + .map_err(|_| error::ErrorInternalServerError("could not acquire template env"))? .get_template(tmpl) .map_err(|_| error::ErrorInternalServerError("could not find template"))? .render(ctx.into()) @@ -37,8 +39,8 @@ impl FromRequest for MiniJinjaRenderer { type Error = actix_web::Error; type Future = Ready>; - fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - let tmpl_env = >>::from_request(req, payload) + fn from_request(req: &HttpRequest, _pl: &mut dev::Payload) -> Self::Future { + let tmpl_env = >::extract(req) .into_inner() .unwrap(); @@ -67,17 +69,40 @@ async fn index( async fn main() -> std::io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - log::info!("starting HTTP server at http://localhost:8080"); + // If TEMPLATE_AUTORELOAD is set, then the path tracking is enabled. + let enable_template_autoreload = env::var("TEMPLATE_AUTORELOAD").as_deref() == Ok("true"); - let mut env: minijinja::Environment<'static> = minijinja::Environment::new(); - env.set_source(minijinja::Source::from_path(concat!( - env!("CARGO_MANIFEST_DIR"), - "/templates" - ))); + if enable_template_autoreload { + log::info!("template auto-reloading is enabled"); + } else { + log::info!( + "template auto-reloading is disabled; run with TEMPLATE_AUTORELOAD=true to enable" + ); + } + + // The closure is invoked every time the environment is outdated to recreate it. + let tmpl_reloader = AutoReloader::new(move |notifier| { + let mut env: minijinja::Environment<'static> = minijinja::Environment::new(); + + let tmpl_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates"); + + // if watch_path is never called, no fs watcher is created + if enable_template_autoreload { + notifier.watch_path(&tmpl_path, true); + } + + env.set_source(minijinja::Source::from_path(tmpl_path)); + + Ok(env) + }); + + let tmpl_reloader = web::Data::new(tmpl_reloader); + + log::info!("starting HTTP server at http://localhost:8080"); HttpServer::new(move || { App::new() - .app_data(web::Data::new(env.clone())) + .app_data(tmpl_reloader.clone()) .service(web::resource("/").route(web::get().to(index))) .wrap(ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)) .wrap(Logger::default())