use std::fmt::{self, Write};
use crate::formatter::{
ErrorKind, FormatTable, Render, TemplateError,
block::{Block, BlockBuilder, BlockMode},
condition::ConditionalToken,
lexer::{Lex, lex_str},
tag::Tag,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Template<'a> {
args: Vec<TemplateItem<'a>>,
}
impl<'a> Template<'a> {
pub(super) fn from_lex(
lexes: impl IntoIterator<Item = Lex<'a>>,
) -> Result<Template<'a>, TemplateError> {
let mut args = Vec::new();
let mut escaped = false;
let mut stack: Vec<ParserEntry<'_>> = Vec::new();
for lex in lexes.into_iter() {
if escaped {
push_arg(&mut stack, &mut args, TemplateItem::Text(lex.to_str()));
escaped = false;
} else {
match lex {
Lex::BlockStart => stack.push(ParserEntry::Block(BlockBuilder::default())),
Lex::BlockEnd => end_block(&mut stack, &mut args)?,
Lex::Variable => {
end_tag(&mut stack, &mut args);
stack.push(ParserEntry::Tag(Tag::default()));
}
Lex::Conditional | Lex::Prefix | Lex::Suffix | Lex::Fallback => block_mode_switch(
&mut stack,
&mut args,
lex.to_block_mode()
.expect("The lex was already matched on block modes. This should not fail"),
)?,
Lex::Or | Lex::And | Lex::Not => push_conditional_token(
&mut stack,
&mut args,
lex.to_condition_token().expect(
"The lex was already matched on conditional tokens. This should not fail",
),
),
Lex::Space => {
end_tag(&mut stack, &mut args);
push_arg(&mut stack, &mut args, TemplateItem::Text(lex.to_str()));
}
Lex::Escape => escaped = true,
Lex::Text(str) => push_arg(&mut stack, &mut args, TemplateItem::Text(str)),
}
}
}
end_tag(&mut stack, &mut args);
if !stack.is_empty() {
return Err(TemplateError::new(ErrorKind::BadClosure(
"Completed parsing all lexes, but the stack was not empty. This means a tag or block was left unclosed",
)));
}
Ok(Template { args })
}
}
impl<'a> TryFrom<&'a str> for Template<'a> {
type Error = TemplateError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let lexes = lex_str(value);
Template::from_lex(lexes)
}
}
impl<'a> Render for Template<'a> {
fn render(&self, format_table: &FormatTable) -> String {
self.args.render(format_table)
}
}
impl fmt::Display for Template<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for arg in &self.args {
arg.fmt(f)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum TemplateItem<'a> {
Text(&'a str),
Tag(Tag<'a>),
Block(Block<'a>),
}
impl Render for TemplateItem<'_> {
fn render(&self, format_table: &super::FormatTable) -> String {
match self {
TemplateItem::Text(str) => str.to_string(),
TemplateItem::Tag(tag) => tag.render(format_table),
TemplateItem::Block(block) => block.render(format_table),
}
}
}
impl Render for [TemplateItem<'_>] {
fn render(&self, format_table: &FormatTable) -> String {
self.iter().map(|arg| arg.render(format_table)).collect()
}
}
enum ParserEntry<'a> {
Tag(Tag<'a>),
Block(BlockBuilder<'a>),
}
#[inline(always)]
fn push_arg<'a>(
stack: &mut Vec<ParserEntry<'a>>,
args: &mut Vec<TemplateItem<'a>>,
arg: TemplateItem<'a>,
) {
match stack.last_mut() {
Some(ParserEntry::Block(block_builder)) => {
block_builder.push_arg(arg);
}
Some(ParserEntry::Tag(tag)) => tag.args.push(arg),
None => args.push(arg),
}
}
fn push_conditional_token<'a>(
stack: &mut Vec<ParserEntry<'a>>,
args: &mut Vec<TemplateItem<'a>>,
token: ConditionalToken<'a>,
) {
if let Some(ParserEntry::Block(block)) = stack.last_mut() {
block.push_conditional_token(token);
} else {
push_arg(stack, args, TemplateItem::Text(token.to_str()));
}
}
fn end_tag<'a>(stack: &mut Vec<ParserEntry<'a>>, args: &mut Vec<TemplateItem<'a>>) {
if let Some(ParserEntry::Tag(_)) = stack.last() {
let tag = match stack.pop().unwrap() {
ParserEntry::Tag(tag) => tag,
_ => unreachable!(),
};
push_arg(stack, args, TemplateItem::Tag(tag));
}
}
fn end_tag_if_in_block<'a>(stack: &mut Vec<ParserEntry<'a>>, args: &mut Vec<TemplateItem<'a>>) {
if let Some(ParserEntry::Tag(_)) = stack.last()
&& let Some(ParserEntry::Block(_)) = stack.get(stack.len() - 2)
{
let tag = match stack.pop().unwrap() {
ParserEntry::Tag(tag) => tag,
_ => unreachable!(),
};
push_arg(stack, args, TemplateItem::Tag(tag));
}
}
fn end_block<'a>(
stack: &mut Vec<ParserEntry<'a>>,
args: &mut Vec<TemplateItem<'a>>,
) -> Result<(), TemplateError> {
end_tag(stack, args);
let block = match stack.pop() {
Some(ParserEntry::Block(block)) => block,
Some(_) => {
panic!("'}}' was found unescaped but it didn't close a block");
}
None => {
return Err(TemplateError::new(ErrorKind::BadClosure(
"'}' was found unescaped with nothing to close",
)));
}
};
push_arg(stack, args, TemplateItem::Block(block.build()));
Ok(())
}
fn block_mode_switch<'a>(
stack: &mut Vec<ParserEntry<'a>>,
args: &mut Vec<TemplateItem<'a>>,
mode: BlockMode,
) -> Result<(), TemplateError> {
end_tag_if_in_block(stack, args);
if let Some(ParserEntry::Block(block)) = stack.last_mut() {
block.set_mode(mode)
} else {
args.push(TemplateItem::Text(mode.to_str()));
Ok(())
}
}
pub(super) fn write_escaped(f: &mut fmt::Formatter<'_>, s: &str, tag_context: bool) -> fmt::Result {
for byte in s.bytes() {
if byte == b' ' && !tag_context {
f.write_char(' ')?;
continue;
}
if matches!(
byte,
b'{' | b'}' | b'$' | b'@' | b'<' | b'>' | b'?' | b'|' | b'&' | b'!' | b'\\' | b' '
) {
f.write_char('\\')?;
}
f.write_char(byte as char)?;
}
Ok(())
}
impl fmt::Display for TemplateItem<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TemplateItem::Text(s) => write_escaped(f, s, false),
TemplateItem::Tag(tag) => tag.fmt(f),
TemplateItem::Block(block) => block.fmt(f),
}
}
}
#[derive(Debug)]
pub struct TemplateOwned {
args: Template<'static>,
string: Box<str>,
}
impl TemplateOwned {
pub fn new(s: impl Into<String>) -> Result<Self, TemplateError> {
let string: Box<str> = s.into().into_boxed_str();
let args = {
let str_ref: &'static str = unsafe { &*(&*string as *const str) };
let lexes = lex_str(str_ref);
Template::from_lex(lexes)?
};
Ok(Self { args, string })
}
pub fn as_arguments(&self) -> &Template<'_> {
&self.args
}
pub fn as_str(&self) -> &str {
&self.string
}
}
impl Clone for TemplateOwned {
fn clone(&self) -> Self {
Self::new(self.string.clone()).expect("Previously valid string cannot suddenly fail")
}
}
impl PartialEq for TemplateOwned {
fn eq(&self, other: &Self) -> bool {
self.args == other.args
}
}
impl Eq for TemplateOwned {}
impl From<Template<'_>> for TemplateOwned {
fn from(value: Template<'_>) -> Self {
TemplateOwned::new(value.to_string()).expect("Arguments to_string are always valid")
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for TemplateOwned {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.string)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for TemplateOwned {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
TemplateOwned::new(s).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Template<'_> {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.to_string())
}
}