use crate::error::tracer::DynTracerError;
use crate::error::TracerError;
use crate::template::pipeline::{Pipeline, PipelineValue};
use crate::template::pipelines::date_time::DatePipeline;
use crate::template::pipelines::{PipelineManager, PrefixPipeline};
use crate::{tracer_dyn_err, tracer_err};
use alloc::boxed::Box;
use core::any::Any;
use core::fmt::Debug;
use hashbrown::HashMap;
use regex::Regex;
use spin::Mutex;
static PIPELINES: Mutex<Option<PipelineManager>> = Mutex::new(None);
#[derive(Debug, Clone)]
pub struct TemplateEngine {
pipelines: PipelineManager,
}
impl TemplateEngine {
fn get_pipelines_default() -> PipelineManager {
let mut map = PipelineManager::default();
map.insert("date".to_string(), DatePipeline::new().boxed_clone());
map.insert("prefix".to_string(), PrefixPipeline::new().boxed_clone());
map
}
pub fn update_pipeline<P: Pipeline + Send + Sync + 'static>(name: &str, pipeline: P) {
let mut pipelines = PIPELINES.lock();
pipelines
.get_or_insert_with(Self::get_pipelines_default)
.insert(name.to_string(), Box::new(pipeline));
}
pub fn get_pipeline(name: &str) -> Option<Box<dyn Pipeline + Send + Sync>> {
let mut pipelines = PIPELINES.lock();
pipelines
.get_or_insert_with(Self::get_pipelines_default)
.get(name)
.map(|t| t.boxed_clone())
}
pub fn get_pipelines() -> PipelineManager {
let mut pipelines = PIPELINES.lock();
pipelines
.get_or_insert_with(Self::get_pipelines_default)
.clone()
}
pub fn new() -> Self {
TemplateEngine {
pipelines: Self::get_pipelines(),
}
}
pub fn reload_pipelines(&mut self) {
self.pipelines = Self::get_pipelines();
}
pub fn render(
&self,
template: &str,
context: &TemplateContext,
) -> Result<String, DynTracerError> {
let mut output = template.to_string();
let re = Regex::new(r"\{\{\s*(.*?)\s*\}\}")
.map_err(|_| tracer_dyn_err!("Failed to create regex"))?;
for cap in re.captures_iter(&output.clone()) {
let full_match = &cap[0];
let mut parts = cap[1].split('|').map(str::trim);
if let Some(key) = parts.next() {
if let Some(initial) = context.get(key) {
let mut current_value = None;
for pipe_segment in parts {
let pipe_parts: Vec<&str> = pipe_segment.splitn(2, ':').collect();
let pipe_name = pipe_parts[0].trim();
let pipe_options = if pipe_parts.len() > 1 {
let option = pipe_parts[1].trim().replace(r"\'", "'");
if option.starts_with('\'') && option.ends_with('\'') {
option[1..option.len() - 1].to_string()
} else {
option
}
} else {
"".to_string()
};
if let Some(pipe) = self.pipelines.get(pipe_name) {
let pipeline = if pipe_options.is_empty() {
pipe.boxed_clone()
} else {
pipe.options(&pipe_options).boxed_clone()
};
if let Some(t) = current_value {
current_value = Some(pipeline.format(&t));
} else {
current_value = Some(pipeline.format(initial));
}
} else {
return Err(tracer_dyn_err!(format!(
"Pipeline '{}' not found",
pipe_name
)));
}
}
if let Some(current_value) = current_value {
output = output.replace(full_match, ¤t_value.to_string());
} else {
output = output.replace(full_match, &initial.to_string());
}
}
}
}
Ok(output)
}
}
pub struct TemplateContext {
variables: HashMap<String, Box<dyn PipelineValue + Send + Sync>>,
}
impl TemplateContext {
pub fn new() -> Self {
TemplateContext {
variables: HashMap::new(),
}
}
pub fn insert(&mut self, key: &str, value: Box<dyn PipelineValue + Send + Sync>) {
self.variables.insert(key.to_string(), value);
}
pub fn get(&self, key: &str) -> Option<&Box<dyn PipelineValue + Send + Sync>> {
self.variables.get(key)
}
}
impl Clone for TemplateContext {
fn clone(&self) -> Self {
let mut variables = HashMap::new();
for (k, v) in self.variables.iter() {
variables.insert(k.clone(), v.boxed_clone());
}
TemplateContext { variables }
}
}