use arc_swap::ArcSwap;
use hornbeam_interpreter::localisation::fluent::{
FluentLocalisationError, FluentLocalisationSystem,
};
use hornbeam_interpreter::{InterpreterError, LoadedTemplates};
use std::convert::Infallible;
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub type TemplateManager = Arc<ArcSwap<LoadedTemplates<FluentLocalisationSystem>>>;
pub type TemplateError = InterpreterError<FluentLocalisationError, Infallible>;
pub use hornbeam_interpreter::Params;
use crate::interpreted::hot_reload::start_hot_reloader;
pub use lazy_static::lazy_static;
#[cfg(feature = "hot_reload")]
pub mod hot_reload;
#[macro_export]
macro_rules! make_template_manager {
{
static ref $name:ident = {
default_locale: $locale:expr,
};
} => {
$crate::lazy_static! {
static ref $name: $crate::TemplateManager =
$crate::new_template_manager($locale, env!("CARGO_PKG_NAME"));
}
}
}
pub fn new_template_manager(
default_locale: &'static str,
crate_name: &'static str,
) -> TemplateManager {
fn is_valid_base_dir(path: &Path) -> bool {
path.is_dir() && path.join("translations").is_dir() && path.join("templates").is_dir()
}
let base_dir = if let Ok(base_dir_env) = std::env::var("HORNBEAM_BASE") {
let path = PathBuf::from(&base_dir_env);
if is_valid_base_dir(&path) {
path
} else {
panic!("Could not find templates at position of HORNBEAM_BASE environment variable ({base_dir_env:?})");
}
} else {
let try_paths = [crate_name, "."];
let mut successful = None;
for path in try_paths {
let path = PathBuf::from(path);
if is_valid_base_dir(&path) {
successful = Some(path);
break;
}
}
successful.unwrap_or_else(|| panic!("Could not find Hornbeam templates/translations: tried looking in {try_paths:?} and no HORNBEAM_BASE environment variable set!"))
};
let template_sys = match load_new_template_system(
default_locale,
&base_dir.join("templates"),
&base_dir.join("translations"),
) {
Ok(v) => v,
Err(err) => {
panic!("Failed to create Hornbeam environment! {err}");
}
};
let templates = Arc::new(ArcSwap::new(Arc::new(template_sys)));
#[cfg(feature = "hot_reload")]
if is_hot_reload_enabled() {
start_hot_reloader(
default_locale.to_owned(),
templates.clone(),
base_dir.join("templates"),
base_dir.join("translations"),
);
}
templates
}
fn load_new_template_system(
default_locale: &str,
template_dir: &Path,
translation_dir: &Path,
) -> Result<LoadedTemplates<FluentLocalisationSystem>, InterpreterError<Infallible, Infallible>> {
let localisation_system = FluentLocalisationSystem::new(translation_dir, default_locale)
.map_err(|err| {
InterpreterError::TemplateFindError(format!(
"Failed to create Fluent localisation system for Hornbeam environment: {err:?}"
))
})?;
let mut templates = LoadedTemplates::new(localisation_system);
templates.load_templates_from_dir(template_dir)?;
Ok(templates)
}
#[cfg(feature = "hot_reload")]
pub fn is_hot_reload_enabled() -> bool {
#[cfg(debug_assertions)]
const DEFAULT_HOT_RELOAD: bool = true;
#[cfg(not(debug_assertions))]
const DEFAULT_HOT_RELOAD: bool = false;
std::env::var("HORNBEAM_HOT")
.map(|env_var| {
if let Ok(i) = env_var.parse::<u32>() {
return i != 0;
}
if let Ok(b) = env_var.parse::<bool>() {
return b;
}
if env_var.eq_ignore_ascii_case("no") {
return false;
}
if env_var.eq_ignore_ascii_case("off") {
return false;
}
eprintln!("Not sure how to interpret HORNBEAM_HOT={env_var:?}, assuming yes.");
true
})
.unwrap_or(DEFAULT_HOT_RELOAD)
}
#[cfg(not(feature = "hot_reload"))]
pub const fn is_hot_reload_enabled() -> bool {
false
}
#[macro_export]
macro_rules! initialise_template_manager {
($name:ident) => {
let _ = $name.load();
};
}
#[macro_export]
macro_rules! render_template_string {
($manager:ident, $template_name:ident, $locale:expr) => {
$crate::render_template_string!($manager, $template_name, $locale, {})
};
($manager:ident, $template_name:ident, $locale:expr, { $($params:tt)* }) => {
{
let manager = $manager.load();
let prepared = manager.prepare(stringify!($template_name), None, $crate::interpreter_params!{ $($params)* }, $locale);
let finished_string = prepared.render_to_string();
if $crate::is_hot_reload_enabled() {
finished_string.map(|string| string.replace("<head>", r#"<head><script type="text/javascript">
async function __hot_reload() {
let response = await fetch(`${window.location.origin.replace(/:[0-9]+/, "")}:7015/wait_hot_reload`);
if (response.status == 502) {
window.setTimeout(__hot_reload, 100);
} else if (response.status != 200) {
console.log("hot reload fail: ", response.statusText);
window.setTimeout(__hot_reload, 1000);
} else {
window.location.reload();
}
}
window.setTimeout(__hot_reload, 100);
</script>"#))
} else {
finished_string
}
}
};
}
#[macro_export]
macro_rules! interpreter_params {
{ $($all:tt)* } => {
{
let mut params = $crate::Params::default();
$crate::interpreter_setters!{ params; $($all)* }
params
}
};
}
#[macro_export]
macro_rules! interpreter_setters {
{ $params:ident; } => {};
{ $params:ident; $k:ident : $v:expr } => {
$params = $params.set(stringify!($k), $v);
};
{ $params:ident; $k:ident } => {
$params = $params.set(stringify!($k), $k);
};
{ $params:ident; $k:ident : $v:expr, $($tail:tt)* } => {
$params = $params.set(stringify!($k), $v);
$crate::interpreter_setters!{ $params; $($tail)* }
};
{ $params:ident; $k:ident, $($tail:tt)* } => {
$params = $params.set(stringify!($k), $k);
$crate::interpreter_setters!{ $params; $($tail)* }
};
}