#![doc = include_str!("../README.md")]
use std::fmt::{self, Write};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::str::FromStr;
#[cfg(feature = "derive")]
pub use typlate_derive::TemplateParams;
pub trait TemplateParams {
const FIELDS: &'static [&'static str];
fn fmt_field(&self, f: &mut fmt::Formatter, index: usize) -> fmt::Result;
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum TemplateElement {
Text(String),
Var(usize),
}
pub struct TemplateString<T> {
elements: Vec<TemplateElement>,
phantom: PhantomData<T>,
}
impl<T: TemplateParams> TemplateString<T> {
pub fn format(&self, params: &T) -> String {
format!("{}", ParameterizedTemplate(self, params))
}
}
struct ParameterizedTemplate<'i, T>(&'i TemplateString<T>, &'i T);
impl<'i, T: TemplateParams> fmt::Display for ParameterizedTemplate<'i, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for element in &self.0.elements {
match element {
TemplateElement::Text(text) => f.write_str(text)?,
TemplateElement::Var(index) => self.1.fmt_field(f, *index)?,
}
}
Ok(())
}
}
impl<T: TemplateParams> FromStr for TemplateString<T> {
type Err = String;
fn from_str(template: &str) -> Result<Self, Self::Err> {
let mut elements = vec![];
let mut chars = template.chars().peekable();
let mut text = String::new();
'outer: while let Some(char) = chars.next() {
match char {
'{' => {
if chars.peek() == Some(&'{') {
chars.next();
text.push('{');
continue;
}
if !text.is_empty() {
elements.push(TemplateElement::Text(text.clone()));
text.clear();
}
let mut name = String::new();
for char in chars.by_ref() {
if char == '}' {
let index = T::FIELDS
.iter()
.position(|&f| f == name)
.ok_or_else(|| format!("Unknown field name: {}", name))?;
elements.push(TemplateElement::Var(index));
continue 'outer;
} else {
name.push(char);
}
}
return Err("Unclosed bracket in template".to_string());
}
'}' => {
if chars.peek() == Some(&'}') {
chars.next();
text.push('}');
} else {
return Err("Unmatched closing bracket".to_string());
}
}
_ => text.push(char),
}
}
if !text.is_empty() {
elements.push(TemplateElement::Text(text));
}
Ok(Self {
elements,
phantom: PhantomData,
})
}
}
impl<T> Clone for TemplateString<T> {
fn clone(&self) -> Self {
Self {
elements: self.elements.clone(),
phantom: PhantomData,
}
}
}
impl<T> PartialEq for TemplateString<T> {
fn eq(&self, other: &Self) -> bool {
self.elements == other.elements
}
}
impl<T> Eq for TemplateString<T> {}
impl<T> PartialOrd for TemplateString<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for TemplateString<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.elements.cmp(&other.elements)
}
}
impl<T> Hash for TemplateString<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.elements.hash(state);
}
}
impl<T> fmt::Debug for TemplateString<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("TemplateString")
.field("elements", &self.elements)
.finish()
}
}
impl<T: TemplateParams> fmt::Display for TemplateString<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for element in &self.elements {
match element {
TemplateElement::Text(text) => {
for char in text.chars() {
match char {
'{' => f.write_str("{{")?,
'}' => f.write_str("}}")?,
_ => f.write_char(char)?,
}
}
}
TemplateElement::Var(index) => {
f.write_char('{')?;
f.write_str(T::FIELDS[*index])?;
f.write_char('}')?;
}
}
}
Ok(())
}
}
#[cfg(feature = "serde")]
mod serde_impl {
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use super::*;
impl<T: TemplateParams> Serialize for TemplateString<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl<'de, T: TemplateParams> Deserialize<'de> for TemplateString<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(TemplateStringVisitor { phantom: PhantomData })
}
}
struct TemplateStringVisitor<T> {
phantom: PhantomData<T>,
}
impl<'de, T: TemplateParams> de::Visitor<'de> for TemplateStringVisitor<T> {
type Value = TemplateString<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a template string")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
value.parse().map_err(de::Error::custom)
}
}
}