use std::ops::Deref;
pub mod error;
pub use minijinja::context;
use minijinja::Environment;
use minijinja::Value;
pub struct Templates {
pub entry: Entry,
pub output: Entry,
pub rest: Vec<Entry>,
}
impl Templates {
pub fn new(entry: Index, output: Entry, rest: Rest) -> Self {
Self {
entry: entry.0,
output,
rest: rest.0,
}
}
}
pub struct Index(Entry);
impl Index {
pub fn new(name: &'static str, template: &'static str) -> Self {
Self(Entry::new(name, template))
}
}
impl Deref for Index {
type Target = Entry;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct Rest(Vec<Entry>);
impl Rest {
pub fn new(vals: Vec<Entry>) -> Self {
Self(vals)
}
pub fn push(&mut self, entry: Entry) {
self.0.push(entry);
}
}
impl Deref for Rest {
type Target = Vec<Entry>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct Entry {
pub name: &'static str,
pub template: &'static str,
}
impl Entry {
pub fn new(name: &'static str, template: &'static str) -> Self {
Self { name, template }
}
}
impl IntoIterator for Templates {
type Item = (&'static str, &'static str);
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut v = Vec::new();
v.push((self.entry.name, self.entry.template));
v.push((self.output.name, self.output.template));
for e in self.rest {
v.push((e.name, e.template));
}
v.into_iter()
}
}
fn indexify(given: String, index: usize) -> String {
format!("{given}[{index}]")
}
pub fn render(
entry: &str,
templates: Templates,
ctx: Value,
tracker: fn(String, String) -> String,
) -> Result<String, error::RenderError> {
let mut env = Environment::new();
for (name, template) in templates.into_iter() {
env.add_template(name, template)
.expect("template should be added");
}
env.add_filter("on", tracker);
env.add_filter("append", indexify);
let tmpl = env.get_template(entry)?;
let rendered = tmpl.render(&ctx).map_err(|e| {
println!("Could not render template: {:#}", e);
let mut err = &e as &dyn std::error::Error;
while let Some(next_err) = err.source() {
println!("caused by: {:#}", next_err);
err = next_err;
}
error::RenderError::from(e)
})?;
Ok(rendered)
}
#[macro_export]
macro_rules! prelude_bindgen {
(
$guest: ident,
$component: ident,
$pagecontext: ident,
$context:ident,
$state:ident
) => {
use $crate::prelude::*;
use std::collections::HashMap;
use std::sync::Mutex;
type ListenerMap = HashMap<String, String>;
lazy_static! {
static ref LISTENERS_MAP: Mutex<ListenerMap> = Mutex::new(HashMap::new());
static ref $state: Mutex<Option<$pagecontext>> = Mutex::new(None);
}
mod wurbo_tracker {
pub fn track(elem_id: String, ty: String) -> String {
let mut listeners = super::LISTENERS_MAP.lock().unwrap();
if elem_id.is_empty() {
eprintln!("WARNING: elem_id is empty for listener type {ty}");
} else {
listeners.insert(format!("#{elem_id}"), ty);
}
elem_id
}
}
impl $guest for $component {
fn render(context: $context) -> Result<String, String> {
let templates = get_templates();
let page_context = $pagecontext::from(&context);
let mut last_state = $state.lock().unwrap();
let target = if let Some(ref target) = page_context.target {
target
} else if last_state.is_none() {
templates.entry.name
} else {
templates.output.name
};
*last_state = Some(page_context.clone());
let ctx = Value::from_struct_object(page_context.clone());
Ok(wurbo::jinja::render(
&target,
templates,
ctx,
wurbo_tracker::track,
)?)
}
fn activate(selectors: Option<Vec<String>>) {
let listeners = LISTENERS_MAP.lock().unwrap();
for (selector, ty) in listeners.iter() {
if let Some(selectors) = &selectors {
if !selectors.contains(&selector.to_string()) {
continue;
}
}
let deets = wurbo_in::ListenDetails {
selector: selector.to_string(),
ty: ty.to_string(),
};
wurbo_in::addeventlistener(&deets);
}
}
}
};
}
#[cfg(test)]
mod jinja_unit_tests {
#[test]
fn smoke() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
}