gunny 0.3.0

A library for rendering static text content from templates.
Documentation
//! # Gunny
//!
//! Gunny weaves data through your templates to produce static text content.

pub mod errors;
pub mod number;
pub mod variables;

use handlebars::Handlebars;
use serde::Serialize;

use crate::{
    errors::{RenderError, TemplateError},
    variables::Variables,
};

/// The name of the default template if no template is specified for a pipeline.
pub const DEFAULT_TEMPLATE_NAME: &str = "default";

/// Configuration for a static content generation pipeline.
#[derive(Debug, Clone)]
pub struct PipelineConfig<'a> {
    pub template_engine: &'a TemplateEngine<'a>,
    /// The name of the template to use from the template engine.
    pub maybe_template_name: Option<&'a str>,
    pub variables: Variables,
}

impl<'a> PipelineConfig<'a> {
    /// Render this pipeline to a string.
    pub fn render(&self) -> Result<String, RenderError> {
        self.template_engine.render(
            self.maybe_template_name.unwrap_or(DEFAULT_TEMPLATE_NAME),
            &self.variables,
        )
    }
}

/// A template engine, from which to source templates. At present, we only support Handlebars.
#[derive(Debug, Clone)]
pub enum TemplateEngine<'a> {
    Handlebars(Handlebars<'a>),
}

impl<'a> TemplateEngine<'a> {
    /// Create a new Handlebars-based template engine.
    pub fn new_handlebars_engine() -> Self {
        Self::Handlebars(Handlebars::new())
    }

    /// Parse and register the specified template as the default template for this engine.
    pub fn register_default_template_string(
        &mut self,
        template: &str,
    ) -> Result<(), TemplateError> {
        self.register_template_string(DEFAULT_TEMPLATE_NAME, template)
    }

    /// Parse and register the given template under the specified name for this engine.
    pub fn register_template_string(
        &mut self,
        name: &str,
        template: &str,
    ) -> Result<(), TemplateError> {
        match self {
            Self::Handlebars(registry) => registry
                .register_template_string(name, template)
                .map_err(Into::into),
        }
    }

    /// Render the given data through the template with the specified name.
    pub fn render<T: Serialize>(
        &self,
        template_name: &str,
        data: &T,
    ) -> Result<String, RenderError> {
        match self {
            Self::Handlebars(registry) => {
                if !registry.has_template(template_name) {
                    return Err(RenderError::NoSuchTemplate(template_name.to_string()));
                }
                registry.render(template_name, data).map_err(Into::into)
            }
        }
    }
}

#[cfg(test)]
mod test {
    use crate::{PipelineConfig, TemplateEngine, variables::Variables};

    #[test]
    fn rendering_simple_handlebars_template_works() {
        let mut template_engine = TemplateEngine::new_handlebars_engine();
        template_engine
            .register_default_template_string("Hello {{name}}! Today is {{day_of_week}}.")
            .unwrap();
        let mut variables = Variables::new();
        variables.set("name", "Michael");
        variables.set("day_of_week", "Wednesday");
        let pipeline_config = PipelineConfig {
            template_engine: &template_engine,
            maybe_template_name: None,
            variables,
        };
        let rendered = pipeline_config.render().unwrap();
        assert_eq!(rendered, "Hello Michael! Today is Wednesday.");
    }
}