mod parser;
use parser::{Parser, Token};
mod error;
use error::{Error, Result};
#[cfg(feature = "struct_context")]
extern crate serde_json;
#[cfg(feature = "struct_context")]
use serde::Serialize;
use std::collections::HashMap;
const DEFAULT_START_PLACEHOLDER: &str = "{{";
const DEFAULT_END_PLACEHOLDER: &str = "}}";
pub struct Template<'t> {
tokens: Vec<Token<'t>>,
}
impl<'t> Template<'t> {
pub fn new(text: &'t str) -> Self {
Self {
tokens: Parser::new(text, DEFAULT_START_PLACEHOLDER, DEFAULT_END_PLACEHOLDER).parse(),
}
}
pub fn new_with_placeholder(text: &'t str, start: &'t str, end: &'t str) -> Self {
Self {
tokens: Parser::new(text, start, end).parse(),
}
}
pub fn fill_with_hashmap(&self, replacements: &HashMap<&str, &str>) -> String {
let mut result = String::new();
for segment in &self.tokens {
match segment {
Token::Text(s) => result.push_str(s),
Token::Placeholder(s) => match replacements.get(s) {
Some(value) => result.push_str(value),
_ => {}
},
}
}
result
}
pub fn fill_with_hashmap_strict(&self, replacements: &HashMap<&str, &str>) -> Result<String> {
let mut result = String::new();
for segment in &self.tokens {
match segment {
Token::Text(s) => result.push_str(s),
Token::Placeholder(s) => match replacements.get(s) {
Some(value) => result.push_str(value),
None => {
let message = format!("missing value for placeholder named '{}'.", s);
return Err(Error::PlaceholderError(message));
}
},
}
}
Ok(result)
}
#[cfg(feature = "struct_context")]
pub fn fill_with_struct<R>(&self, replacements: &R) -> Result<String>
where
R: Serialize,
{
let mut result = String::new();
let replacements = serde_json::to_value(replacements)?;
for segment in &self.tokens {
match segment {
Token::Text(s) => result.push_str(s),
Token::Placeholder(s) => match replacements.get(s) {
Some(value) => result.push_str(value.as_str().unwrap_or("")),
_ => {}
},
}
}
Ok(result)
}
#[cfg(feature = "struct_context")]
pub fn fill_with_struct_strict<R>(&self, replacements: &R) -> Result<String>
where
R: Serialize,
{
let mut result = String::new();
let replacements = serde_json::to_value(replacements)?;
for segment in &self.tokens {
match segment {
Token::Text(s) => result.push_str(s),
Token::Placeholder(s) => match replacements.get(s) {
Some(value) => match value.as_str() {
Some(value) => result.push_str(value),
None => {
let message = format!("missing value for placeholder named '{}'.", s);
return Err(Error::PlaceholderError(message));
}
},
None => {
let message = format!("missing value for placeholder named '{}'.", s);
return Err(Error::PlaceholderError(message));
}
},
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::Template;
use std::collections::HashMap;
#[cfg(feature = "struct_context")]
use serde::Serialize;
#[test]
fn test_hashmap_no_replacements() {
let table = HashMap::new();
assert_eq!(
Template::new("hello world").fill_with_hashmap(&table),
"hello world"
);
}
#[test]
fn test_hashmap_replacement_start_line() {
let mut table = HashMap::new();
table.insert("placeholder", "hello");
assert_eq!(
Template::new("{{placeholder}} world").fill_with_hashmap(&table),
"hello world"
);
}
#[test]
fn test_hashmap_replacement_middle_line() {
let mut table = HashMap::new();
table.insert("placeholder", "crazy");
assert_eq!(
Template::new("hello {{placeholder}} world").fill_with_hashmap(&table),
"hello crazy world"
);
}
#[test]
fn test_hashmap_replacement_end_line() {
let mut table = HashMap::new();
table.insert("placeholder", "world");
assert_eq!(
Template::new("hello {{placeholder}}").fill_with_hashmap(&table),
"hello world"
);
}
#[test]
fn test_hashmap_multiple_replacements() {
let mut table = HashMap::new();
table.insert("first", "one");
table.insert("second", "two");
table.insert("third", "three");
assert_eq!(
Template::new("{{first}} {{second}} {{third}}").fill_with_hashmap(&table),
"one two three"
);
}
#[test]
fn test_hashmap_missing_starting_boundaries() {
let mut table = HashMap::new();
table.insert("placeholder", "world");
assert_eq!(
Template::new("hello placeholder}}").fill_with_hashmap(&table),
"hello placeholder}}"
);
}
#[test]
fn test_hashmap_missing_closing_boundaries() {
let mut table = HashMap::new();
table.insert("placeholder", "world");
assert_eq!(
Template::new("hello {{placeholder").fill_with_hashmap(&table),
"hello {{placeholder"
);
}
#[test]
fn test_hashmap_missing_replacements() {
let table = HashMap::new();
assert_eq!(
Template::new("hello {{placeholder}}").fill_with_hashmap(&table),
"hello "
);
}
#[test]
fn test_hashmap_strict_no_replacements() {
let table = HashMap::new();
assert_eq!(
Template::new("hello world")
.fill_with_hashmap_strict(&table)
.unwrap(),
"hello world"
);
}
#[test]
fn test_hashmap_strict_replacement_start_line() {
let mut table = HashMap::new();
table.insert("placeholder", "hello");
assert_eq!(
Template::new("{{placeholder}} world")
.fill_with_hashmap_strict(&table)
.unwrap(),
"hello world"
);
}
#[test]
fn test_hashmap_strict_replacement_middle_line() {
let mut table = HashMap::new();
table.insert("placeholder", "crazy");
assert_eq!(
Template::new("hello {{placeholder}} world")
.fill_with_hashmap_strict(&table)
.unwrap(),
"hello crazy world"
);
}
#[test]
fn test_hashmap_strict_replacement_end_line() {
let mut table = HashMap::new();
table.insert("placeholder", "world");
assert_eq!(
Template::new("hello {{placeholder}}")
.fill_with_hashmap_strict(&table)
.unwrap(),
"hello world"
);
}
#[test]
fn test_hashmap_strict_multiple_replacements() {
let mut table = HashMap::new();
table.insert("first", "one");
table.insert("second", "two");
table.insert("third", "three");
assert_eq!(
Template::new("{{first}} {{second}} {{third}}")
.fill_with_hashmap_strict(&table)
.unwrap(),
"one two three"
);
}
#[test]
fn test_hashmap_strict_missing_starting_boundaries() {
let mut table = HashMap::new();
table.insert("placeholder", "world");
assert_eq!(
Template::new("hello placeholder}}")
.fill_with_hashmap_strict(&table)
.unwrap(),
"hello placeholder}}"
);
}
#[test]
fn test_hashmap_strict_missing_closing_boundaries() {
let mut table = HashMap::new();
table.insert("placeholder", "world");
assert_eq!(
Template::new("hello {{placeholder")
.fill_with_hashmap_strict(&table)
.unwrap(),
"hello {{placeholder"
);
}
#[test]
fn test_hashmap_strict_missing_replacements() {
let table = HashMap::new();
assert_eq!(
Template::new("hello {{placeholder}}").fill_with_hashmap_strict(&table).map_err(|e| e.to_string()),
Err("Error while replacing placeholder. Reason: missing value for placeholder named 'placeholder'.".to_owned())
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_no_replacements() {
#[derive(Serialize)]
struct Context {}
let context = Context {};
assert_eq!(
Template::new("hello world")
.fill_with_struct(&context)
.unwrap(),
"hello world"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_replacement_start_line() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "hello".to_string(),
};
assert_eq!(
Template::new("{{placeholder}} world")
.fill_with_struct(&context)
.unwrap(),
"hello world"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_replacement_middle_line() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "crazy".to_string(),
};
assert_eq!(
Template::new("hello {{placeholder}} world")
.fill_with_struct(&context)
.unwrap(),
"hello crazy world"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_replacement_end_line() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "world".to_string(),
};
assert_eq!(
Template::new("hello {{placeholder}}")
.fill_with_struct(&context)
.unwrap(),
"hello world"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_multiple_replacements() {
#[derive(Serialize)]
struct Context {
first: String,
second: String,
third: String,
}
let context = Context {
first: "one".to_string(),
second: "two".to_string(),
third: "three".to_string(),
};
assert_eq!(
Template::new("{{first}} {{second}} {{third}}")
.fill_with_struct(&context)
.unwrap(),
"one two three"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_missing_starting_boundaries() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "world".to_string(),
};
assert_eq!(
Template::new("hello placeholder}}")
.fill_with_struct(&context)
.unwrap(),
"hello placeholder}}"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_missing_closing_boundaries() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "world".to_string(),
};
assert_eq!(
Template::new("hello {{placeholder")
.fill_with_struct(&context)
.unwrap(),
"hello {{placeholder"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_missing_replacements() {
#[derive(Serialize)]
struct Context {
different: String,
}
let context = Context {
different: "world".to_string(),
};
assert_eq!(
Template::new("hello {{placeholder}}")
.fill_with_struct(&context)
.unwrap(),
"hello "
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_strict_no_replacements() {
#[derive(Serialize)]
struct Context {}
let context = Context {};
assert_eq!(
Template::new("hello world")
.fill_with_struct_strict(&context)
.unwrap(),
"hello world"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_strict_replacement_start_line() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "hello".to_string(),
};
assert_eq!(
Template::new("{{placeholder}} world")
.fill_with_struct_strict(&context)
.unwrap(),
"hello world"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_strict_replacement_middle_line() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "crazy".to_string(),
};
assert_eq!(
Template::new("hello {{placeholder}} world")
.fill_with_struct_strict(&context)
.unwrap(),
"hello crazy world"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_strict_replacement_end_line() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "world".to_string(),
};
assert_eq!(
Template::new("hello {{placeholder}}")
.fill_with_struct_strict(&context)
.unwrap(),
"hello world"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_strict_multiple_replacements() {
#[derive(Serialize)]
struct Context {
first: String,
second: String,
third: String,
}
let context = Context {
first: "one".to_string(),
second: "two".to_string(),
third: "three".to_string(),
};
assert_eq!(
Template::new("{{first}} {{second}} {{third}}")
.fill_with_struct_strict(&context)
.unwrap(),
"one two three"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_strict_missing_starting_boundaries() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "world".to_string(),
};
assert_eq!(
Template::new("hello placeholder}}")
.fill_with_struct_strict(&context)
.unwrap(),
"hello placeholder}}"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_strict_missing_closing_boundaries() {
#[derive(Serialize)]
struct Context {
placeholder: String,
}
let context = Context {
placeholder: "world".to_string(),
};
assert_eq!(
Template::new("hello {{placeholder")
.fill_with_struct_strict(&context)
.unwrap(),
"hello {{placeholder"
);
}
#[cfg(feature = "struct_context")]
#[test]
fn test_struct_strict_missing_replacements() {
#[derive(Serialize)]
struct Context {
different: String,
}
let context = Context {
different: "world".to_string(),
};
assert_eq!(
Template::new("hello {{placeholder}}").fill_with_struct_strict(&context).map_err(|e| e.to_string()),
Err("Error while replacing placeholder. Reason: missing value for placeholder named 'placeholder'.".to_owned())
);
}
}