use std::collections::BTreeMap;
use handlebars::{Handlebars, TemplateError};
use serde::Serialize;
use crate::song::Song;
const TEMPLATE_NAME: &str = "meta";
pub struct MetaTemplate {
registry: Handlebars<'static>,
blank: bool,
}
impl MetaTemplate {
pub fn parse(source: &str) -> Result<MetaTemplate, TemplateError> {
let mut registry = Handlebars::new();
registry.register_escape_fn(handlebars::no_escape);
registry.set_strict_mode(false);
registry.register_template_string(TEMPLATE_NAME, source)?;
Ok(MetaTemplate {
registry,
blank: source.trim().is_empty(),
})
}
pub fn is_blank(&self) -> bool {
self.blank
}
pub fn render<V: Serialize>(&self, variables: &V) -> Option<String> {
if self.blank {
return None;
}
match self.registry.render(TEMPLATE_NAME, variables) {
Ok(rendered) => {
let trimmed = rendered.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
Err(_) => None,
}
}
pub fn render_song(&self, song: &Song) -> Option<String> {
self.render(&variables_for_song(song))
}
}
pub fn variables_for_song(song: &Song) -> BTreeMap<String, String> {
let mut variables = song.tags().clone();
variables.insert("title".to_string(), song.title.clone());
variables
}
pub fn render_metadata<V: Serialize>(
template_string: &str,
metadata: &V,
) -> Result<String, TemplateError> {
Ok(MetaTemplate::parse(template_string)?
.render(metadata)
.unwrap_or_default())
}
#[cfg(test)]
mod tests {
use super::*;
fn variables(pairs: &[(&str, &str)]) -> BTreeMap<String, String> {
pairs
.iter()
.map(|(key, value)| (key.to_string(), value.to_string()))
.collect()
}
#[test]
fn test_render_metadata() {
let data = variables(&[("title", "Amazing Grace"), ("author", "John Newton")]);
assert_eq!(
render_metadata("{{title}} ({{author}})", &data).unwrap(),
"Amazing Grace (John Newton)"
);
}
#[test]
fn test_unknown_placeholder_renders_as_nothing() {
let data = variables(&[("title", "Amazing Grace")]);
assert_eq!(
render_metadata("{{title}} ({{nonexisting}})", &data).unwrap(),
"Amazing Grace ()"
);
}
#[test]
fn test_text_is_not_html_escaped() {
let data = variables(&[("title", "Rock & Roll"), ("author", "O'Brien <arr.>")]);
assert_eq!(
render_metadata("{{title}} — {{author}}", &data).unwrap(),
"Rock & Roll — O'Brien <arr.>"
);
}
#[test]
fn test_malformed_template_is_reported() {
let error = MetaTemplate::parse("{{#if author}}never closed");
assert!(error.is_err(), "an unclosed block should not compile");
}
#[test]
fn test_blank_template_renders_nothing() {
let data = variables(&[("title", "Amazing Grace")]);
for source in ["", " ", "\n"] {
let template = MetaTemplate::parse(source).unwrap();
assert!(template.is_blank());
assert_eq!(template.render(&data), None);
}
}
#[test]
fn test_template_without_any_content_yields_none() {
let data = variables(&[("title", "Amazing Grace")]);
let template = MetaTemplate::parse("{{author}}").unwrap();
assert_eq!(template.render(&data), None);
let template = MetaTemplate::parse(" {{author}} ").unwrap();
assert_eq!(template.render(&data), None);
}
#[test]
fn test_conditionals_work() {
let template =
MetaTemplate::parse("{{title}}{{#if author}} ({{author}}){{/if}}").unwrap();
assert_eq!(
template.render(&variables(&[("title", "A"), ("author", "B")])),
Some("A (B)".to_string())
);
assert_eq!(
template.render(&variables(&[("title", "A")])),
Some("A".to_string())
);
}
#[test]
fn test_variables_for_song() {
let mut song = Song::new("The Title");
song.set_tag("author", "The Author");
song.set_tag("title", "Wrong");
let variables = variables_for_song(&song);
assert_eq!(variables["title"], "The Title");
assert_eq!(variables["author"], "The Author");
}
#[test]
fn test_render_song() {
let mut song = Song::new("Weiß ich den Weg auch nicht");
song.set_tag("author", "Hedwig Von Redern");
song.set_tag("ccli_song_number", "5973691");
let template = MetaTemplate::parse("{{title}} — {{author}} (CCLI {{ccli_song_number}})")
.unwrap();
assert_eq!(
template.render_song(&song).unwrap(),
"Weiß ich den Weg auch nicht — Hedwig Von Redern (CCLI 5973691)"
);
}
}