use crate::formatter::lexer::lex_str;
#[derive(Debug, Clone, Copy)]
pub enum ErrorKind {
BadClosure(&'static str),
DoubleDefinition(&'static str),
}
#[derive(Debug, thiserror::Error)]
#[error("{kind:?}")]
pub struct TemplateError {
kind: ErrorKind,
}
impl TemplateError {
#[must_use]
pub fn new(kind: ErrorKind) -> Self {
Self { kind }
}
#[must_use]
pub fn kind(&self) -> ErrorKind {
self.kind
}
}
mod block;
mod condition;
mod lexer;
mod tag;
mod template;
pub use template::{Template, TemplateOwned};
mod format_table;
pub use format_table::*;
mod taggable;
pub use taggable::*;
trait Render {
fn render(&self, format_table: &FormatTable) -> String;
}
pub fn parse_template(format_string: &str) -> Result<Template<'_>, TemplateError> {
let lexes = lex_str(format_string);
Template::from_lex(lexes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn template_equality() {
let template = Template::try_from("Hello, world!").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "Hello, world!");
}
#[test]
fn template_arguments_to_string_equality() {
const FORMAT_STR: &str = r"{$tag_block} ${block_tag} {{{nested_block}}} \{escaped_block\} \$escaped_tag {$weird\ tag} {fully_conditioned_block@condition<prefix>postfix?fallback}";
let template = parse_template(FORMAT_STR).unwrap();
assert_eq!(template.to_string(), FORMAT_STR);
}
#[test]
fn template_simple_tag() {
let table = FormatTable::from([("tag", "Hello, world!")]);
let template = Template::try_from("$tag").unwrap();
let result = table.render(&template);
assert_eq!(result, "Hello, world!");
}
#[test]
fn template_block_tag() {
let table = FormatTable::from([("tag", "Hello, world!")]);
let template = Template::try_from("${tag}").unwrap();
let result = table.render(&template);
assert_eq!(result, "Hello, world!");
}
#[test]
fn template_double_tag() {
let table = FormatTable::from([("foo", "foo"), ("bar", "bar")]);
let template = Template::try_from("$foo$bar").unwrap();
let result = table.render(&template);
assert_eq!(result, "foobar");
}
#[test]
fn template_tag_ends_at_spaces() {
let table = FormatTable::from([("foo", "foo"), ("bar", "bar")]);
let template = Template::try_from("$foo $bar").unwrap();
let result = table.render(&template);
assert_eq!(result, "foo bar");
}
#[test]
fn template_recursive_block_tag() {
let table = FormatTable::from([("foo", "bar"), ("bar", "Hello, world!")]);
let template = Template::try_from("${$foo}").unwrap();
let result = table.render(&template);
assert_eq!(result, "Hello, world!");
}
#[test]
fn template_conditional_block() {
let template = Template::try_from("{Hello, world!@$invalid}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "");
}
#[test]
fn template_inverted_conditional_block() {
let template = Template::try_from("{Hello, world!@!$invalid}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "Hello, world!");
}
#[test]
fn template_double_invert() {
let template = Template::try_from("{Hello, world!@!!$invalid}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "");
}
#[test]
fn template_double_definition_fails() {
let result = Template::try_from("{content<prefix>suffix?fallback<prefix_again}");
match result {
Err(err) if matches!(err.kind(), ErrorKind::DoubleDefinition(_)) => (),
_ => panic!("Unexpected result: {result:?}"),
}
}
#[test]
fn template_prefix_and_suffix() {
let template = Template::try_from("{content<prefix > suffix}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "prefix content suffix");
}
#[test]
fn template_no_content_no_prefix_and_suffix() {
let template = Template::try_from("{$empty<prefix > suffix}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "");
}
#[test]
fn template_fallback() {
let template = Template::try_from("{$invalid?fallback}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "fallback");
}
#[test]
fn template_fallback_uses_prefix_and_suffix() {
let template = Template::try_from("{$empty<prefix > suffix?fallback}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "prefix fallback suffix");
}
#[test]
fn template_fallback_condition() {
let template = Template::try_from("{$invalid?fallback@$invalid}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "");
}
#[test]
fn template_or_condition() {
let table = FormatTable::from([("a", "foo")]);
let template = Template::try_from("{Hello, world!@$invalid||$a}").unwrap();
let result = table.render(&template);
assert_eq!(result, "Hello, world!");
}
#[test]
fn template_and_condition() {
let table = FormatTable::from([("a", "foo"), ("b", "bar")]);
let template = Template::try_from("{Hello, world!@$a&&$b}").unwrap();
let result = table.render(&template);
assert_eq!(result, "Hello, world!");
}
#[test]
fn template_nand_condition() {
let table = FormatTable::from([("a", "foo")]);
let template = Template::try_from("{Hello, world!@$a!&$invalid}").unwrap();
let result = table.render(&template);
assert_eq!(result, "");
}
#[test]
fn template_nor_condition() {
let template = Template::try_from("{Hello, world!@$invalid!|$invalid}").unwrap();
let result = FormatTable::new().render(&template);
assert_eq!(result, "");
}
#[test]
fn template_verify_conditional_left_to_right() {
let table = FormatTable::from([("a", "foo")]);
let template = Template::try_from("{Hello, world!@$invalid&&$invalid||$a&&$a}").unwrap();
let result = table.render(&template);
assert_eq!(result, "Hello, world!");
}
}