use std::sync::Arc;
use tera::{Context, Tera};
#[derive(Debug, thiserror::Error)]
pub enum WebError {
#[error("template load error: {0}")]
Load(#[from] tera::Error),
}
pub type WebResult<T> = std::result::Result<T, WebError>;
#[derive(Clone, Debug)]
pub struct AppContext {
pub app_display: &'static str,
pub entity_singular: &'static str,
pub entity_plural: &'static str,
}
impl AppContext {
pub const fn default() -> Self {
Self {
app_display: "Place Service",
entity_singular: "place",
entity_plural: "places",
}
}
pub fn apply(&self, ctx: &mut Context) {
ctx.insert("app_display", self.app_display);
ctx.insert("entity_singular", self.entity_singular);
ctx.insert("entity_plural", self.entity_plural);
}
}
#[derive(Clone)]
pub struct WebState {
pub tera: Arc<Tera>,
pub app: AppContext,
pub themes: Vec<Theme>,
}
impl WebState {
pub fn new() -> WebResult<Self> {
Ok(Self {
tera: Arc::new(engine()?),
app: AppContext::default(),
themes: scan_themes(),
})
}
pub fn context(&self) -> Context {
let mut ctx = Context::new();
self.app.apply(&mut ctx);
ctx.insert("themes", &self.themes);
ctx
}
pub fn render(&self, template: &str, ctx: &Context) -> tera::Result<String> {
self.tera.render(template, ctx)
}
}
pub fn engine() -> WebResult<Tera> {
let pattern = "assets/views/**/*.tera";
Tera::new(pattern).map_err(WebError::Load)
}
#[derive(Clone, Debug, serde::Serialize)]
pub struct Theme {
pub slug: String,
pub label: String,
}
fn scan_themes() -> Vec<Theme> {
let dir = std::path::Path::new("assets/static/css/themes");
let entries = match std::fs::read_dir(dir) {
Ok(it) => it,
Err(_) => return Vec::new(),
};
let mut slugs: Vec<String> = entries
.filter_map(|e| e.ok())
.filter_map(|e| {
let path = e.path();
if path.extension().and_then(|s| s.to_str()) != Some("css") {
return None;
}
path.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
})
.collect();
slugs.sort();
slugs.dedup();
slugs
.into_iter()
.map(|slug| {
let label = label_for_slug(&slug);
Theme { slug, label }
})
.collect()
}
fn label_for_slug(slug: &str) -> String {
match slug {
"cmyk" => "CMYK".to_string(),
"lofi" => "Lo-fi".to_string(),
"caramellatte" => "Caramel latte".to_string(),
"united-kingdom-national-health-service-england" =>
"United Kingdom NHS England".to_string(),
"united-kingdom-national-health-service-scotland" =>
"United Kingdom NHS Scotland".to_string(),
"united-kingdom-national-health-service-wales-patients" =>
"United Kingdom NHS Wales (Patients)".to_string(),
"united-kingdom-national-health-service-wales-practitioners" =>
"United Kingdom NHS Wales (Practitioners)".to_string(),
s => {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(c) => c.to_ascii_uppercase().to_string() + chars.as_str(),
}
}
}
}