Simple templating language for HTML. Define components and rewrite parts of HTML with them.
at trunk 4.4 kB view raw
1#![warn(clippy::pedantic)] 2 3use std::collections::HashMap; 4 5use miette::miette; 6use pest::{Parser, iterators::Pairs}; 7 8#[derive(pest_derive::Parser)] 9#[grammar = "grammar.pest"] 10struct MyParser; 11 12pub type RewriterFn = fn(&str, HashMap<&str, &str>) -> String; 13 14#[derive(Default)] 15pub struct Rewriter { 16 pub components: HashMap<String, RewriterFn>, 17} 18 19impl Rewriter { 20 pub fn add_component(&mut self, name: &str, func: RewriterFn) { 21 self.components.insert(name.to_string(), func); 22 } 23 pub fn rewrite(&self, input: &str) -> miette::Result<String> { 24 let mut parse = 25 MyParser::parse(Rule::document, input).map_err(pest::error::Error::into_miette)?; 26 27 let mut str_builder = String::new(); 28 29 let doc = parse.next().unwrap(); 30 31 for tag in doc.into_inner() { 32 match tag.as_rule() { 33 Rule::statement => { 34 let mut inner_tags: Pairs<_> = tag.into_inner(); 35 36 let tag_open = inner_tags.next().unwrap(); 37 assert_eq!(tag_open.as_rule(), Rule::tag_open); 38 let mut tag_open = tag_open.into_inner(); 39 40 let tag_ident = tag_open.next().unwrap(); 41 assert_eq!(tag_ident.as_rule(), Rule::tag_ident); 42 let tag_ident = tag_ident.as_str(); 43 44 let attrs = tag_open.next(); 45 let attrs: HashMap<&str, &str> = if let Some(attrs) = attrs { 46 assert_eq!(attrs.as_rule(), Rule::attr_list); 47 let attrs = attrs.into_inner(); 48 attrs 49 .map(|attr| { 50 let mut elements = attr.into_inner(); 51 let key = elements.next().unwrap(); 52 let value = elements.next().unwrap(); 53 (key.as_str(), value.as_str()) 54 }) 55 .collect() 56 } else { 57 HashMap::default() 58 }; 59 60 let tag_text = inner_tags.next().unwrap(); 61 assert_eq!(tag_text.as_rule(), Rule::text); 62 let tag_text = tag_text.as_str(); 63 64 let component_fn = self 65 .components 66 .get(tag_ident) 67 .ok_or(miette!("Unregistered component"))?; 68 let resp = component_fn(tag_text, attrs); 69 70 str_builder.push_str(&resp); 71 } 72 Rule::text => { 73 str_builder.push_str(tag.as_span().as_str()); 74 } 75 _ => {} 76 } 77 } 78 79 Ok(str_builder) 80 } 81} 82 83#[cfg(test)] 84mod tests { 85 use super::*; 86 87 #[test] 88 fn simple_example() -> miette::Result<()> { 89 fn foo_rewriter(input: &str, _attrs: HashMap<&str, &str>) -> String { 90 String::from(format!("<span>Hello {input}</span>")) 91 } 92 93 const MARKUP: &str = r#"<body> 94 <h1>Hello!</h1> 95 96 <p>Lorem lorem</p> 97 98 <#hello>World</#hello> 99</body>"#; 100 101 const COMPILED_MARKUP: &str = r#"<body> 102 <h1>Hello!</h1> 103 104 <p>Lorem lorem</p> 105 106 <span>Hello World</span> 107</body>"#; 108 109 let mut writer = Rewriter::default(); 110 writer.add_component("hello", foo_rewriter); 111 112 let rewrited = writer.rewrite(MARKUP)?; 113 114 assert_eq!(COMPILED_MARKUP, rewrited); 115 116 Ok(()) 117 } 118 119 #[test] 120 fn attr_example() -> miette::Result<()> { 121 fn attr_rewriter(input: &str, attrs: HashMap<&str, &str>) -> String { 122 let el = attrs["element"]; 123 String::from(format!("<{el}>{input}</{el}>")) 124 } 125 126 const MARKUP: &str = r#"<#foo element="span" test="Hello World. Foo bar.">Lorem</#foo>"#; 127 const COMPILED_MARKUP: &str = r#"<span>Lorem</span>"#; 128 129 let mut writer = Rewriter::default(); 130 writer.add_component("foo", attr_rewriter); 131 132 let rewrited = writer.rewrite(MARKUP)?; 133 134 assert_eq!(COMPILED_MARKUP, rewrited); 135 136 Ok(()) 137 } 138 139 #[test] 140 fn blog_example() -> miette::Result<()> { 141 const MARKUP: &str = include_str!("test.html"); 142 143 let writer = Rewriter::default(); 144 let rewrited = writer.rewrite(include_str!("test.html"))?; 145 146 assert_eq!(MARKUP, rewrited); 147 148 Ok(()) 149 } 150}