1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
pub use erst_derive::Template; use std::fmt::{Display, Write}; pub trait Template { fn render_into(&self, writer: &mut std::fmt::Write) -> std::fmt::Result; fn size_hint() -> usize; fn render(&self) -> Result<String, std::fmt::Error> { let mut buffer = String::with_capacity(Self::size_hint()); self.render_into(&mut buffer)?; Ok(buffer) } } pub struct Html<T>(pub T); pub struct Raw<T>(pub T); pub struct HtmlWriter<'a, 'b: 'a>(&'a mut std::fmt::Formatter<'b>); impl HtmlWriter<'_, '_> { fn write_slice(&mut self, bytes: &[u8]) -> std::fmt::Result { let bstr = erst_shared::exp::B(bytes); if let Ok(s) = bstr.to_str() { self.0.write_str(s)?; } else { for chr in bstr.chars() { self.0.write_char(chr)?; } } Ok(()) } } impl std::io::Write for HtmlWriter<'_, '_> { fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> { let bytes_len = bytes.len(); let mut frm = 0; let mut escape = |frm: &mut usize, to: usize, rep| -> std::io::Result<()> { if *frm < to { self.write_slice(&bytes[*frm..to]) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; } self.0 .write_str(rep) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; *frm = to + 1; Ok(()) }; for (idx, byte) in bytes.iter().enumerate() { match byte { b'<' => escape(&mut frm, idx, "<")?, b'>' => escape(&mut frm, idx, ">")?, b'&' => escape(&mut frm, idx, "&")?, b'"' => escape(&mut frm, idx, """)?, b'\'' => escape(&mut frm, idx, "'")?, b'/' => escape(&mut frm, idx, "/")?, _ => {} } } if frm < bytes_len { self.write_slice(&bytes[frm..bytes_len]) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; } Ok(bytes_len) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } impl<T> Display for Html<T> where T: Display, { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use std::io::Write; write!(HtmlWriter(f), "{}", &self.0).map_err(|_| std::fmt::Error)?; Ok(()) } } impl<T> Display for Html<Raw<T>> where T: Display, { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let Html(Raw(ref inner)) = *self; inner.fmt(f) } } #[cfg(feature = "dynamic")] pub mod dynamic { pub use erst_shared::dynamic::*; use std::collections::HashMap; pub fn get(path: &str, idx: usize) -> Option<String> { use std::sync::Mutex; lazy_static::lazy_static! { static ref MAP: Mutex<HashMap<String, HashMap<usize, String>>> = Mutex::new(HashMap::new()); } if let Ok(lock) = MAP.lock() { if let Some(inner_map) = lock.get(path) { return inner_map.get(&idx).cloned(); } } if let Ok(mut lock) = MAP.lock() { if let Ok(inner_map) = parse(path) { lock.insert(path.into(), inner_map.clone()); if let Some(out) = inner_map.get(&idx) { return Some(out.clone()); } } } None } fn parse(path: &str) -> erst_shared::err::Result<HashMap<usize, String>> { use erst_shared::{ exp::Parser as _, parser::{ErstParser, Rule}, }; let template = std::fs::read_to_string(&path)?; let pairs = ErstParser::parse(Rule::template, &template) .map_err(|e| erst_shared::err::Error::Parse(e.to_string()) )?; let mut map = HashMap::new(); for (idx, pair) in pairs.enumerate() { match pair.as_rule() { Rule::text => { map.insert(idx, pair.as_str().into()); } _ => {} } } Ok(map) } }