1use std::ops::Range;
2use bytes::Bytes;
3
4
5
6const FRAGMENT_START: &[u8] = b"{{: ";
7const FRAGMENT_END: &[u8] = b" :}}";
8
9pub const MAX_FRAGMENT_LEN: usize = 256;
12
13
14pub struct Appender<'a>(&'a mut Vec<u8>);
16
17impl Appender<'_> {
18 pub fn append(&mut self, s: &[u8]) {
19 self.0.extend_from_slice(s);
20 }
21}
22
23#[derive(Debug)]
40pub struct Template {
41 raw: Bytes,
42 fragments: Vec<SpannedFragment>,
43}
44
45#[derive(Debug, thiserror::Error)]
47pub enum Error {
48 #[error("template fragment does not contain valid UTF8: {0:?}")]
49 NonUtf8TemplateFragment(Vec<u8>),
50 #[error("unknown template fragment specifier '{0}'")]
51 UnknownTemplateSpecifier(String),
52}
53
54#[derive(Debug)]
56struct SpannedFragment {
57 span: Range<usize>,
58 kind: Fragment,
59}
60
61#[derive(Debug)]
63pub enum Fragment {
64 Path(String),
71
72 Include(String),
74
75 Var(String),
77}
78
79impl Fragment {
80 fn parse(bytes: &[u8]) -> Result<Self, Error> {
81 let val = |s: &str| s[s.find(':').unwrap() + 1..].to_string();
82
83 let s = std::str::from_utf8(bytes)
84 .map_err(|_| Error::NonUtf8TemplateFragment(bytes.into()))?
85 .trim();
86
87 match () {
88 () if s.starts_with("path:") => Ok(Self::Path(val(s))),
89 () if s.starts_with("include:") => Ok(Self::Include(val(s))),
90 () if s.starts_with("var:") => Ok(Self::Var(val(s))),
91
92 _ => {
93 let specifier = s[..s.find(':').unwrap_or(s.len())].to_string();
94 Err(Error::UnknownTemplateSpecifier(specifier))
95 }
96 }
97 }
98
99 pub fn as_include(&self) -> Option<&str> {
100 match self {
101 Self::Include(p) => Some(p),
102 _ => None,
103 }
104 }
105}
106
107impl Template {
108 pub fn parse(input: Bytes) -> Result<Self, Error> {
110 let fragments = FragmentSpans::new(&input)
111 .map(|span| {
112 let kind = Fragment::parse(&input[span.clone()])?;
113 Ok(SpannedFragment { span, kind })
114 })
115 .collect::<Result<_, _>>()?;
116
117 Ok(Self {
118 raw: input,
119 fragments,
120 })
121 }
122
123 pub fn into_already_rendered(self) -> Result<Bytes, Self> {
126 if self.fragments.is_empty() {
127 Ok(self.raw)
128 } else {
129 Err(self)
130 }
131 }
132
133 pub fn fragments(&self) -> impl Iterator<Item = &Fragment> {
135 self.fragments.iter().map(|f| &f.kind)
136 }
137
138 pub fn raw_input(&self) -> &Bytes {
140 &self.raw
141 }
142
143 pub fn render<R, E>(self, mut replacer: R) -> Result<Bytes, E>
158 where
159 R: FnMut(Fragment, Appender) -> Result<(), E>,
160 {
161 if self.fragments.is_empty() {
162 return Ok(self.raw);
163 }
164
165 let mut out = Vec::new();
166 let mut last_fragment_end = 0;
167
168 for fragment in self.fragments {
169 out.extend_from_slice(
172 &self.raw[last_fragment_end..fragment.span.start - FRAGMENT_START.len()]
173 );
174
175 replacer(fragment.kind, Appender(&mut out))?;
177
178 last_fragment_end = fragment.span.end + FRAGMENT_END.len();
179 }
180
181 out.extend_from_slice(&self.raw[last_fragment_end..]);
183
184 Ok(out.into())
185 }
186}
187
188
189pub struct FragmentSpans<'a> {
203 input: &'a [u8],
204 idx: usize,
205}
206
207impl<'a> FragmentSpans<'a> {
208 pub fn new(input: &'a [u8]) -> Self {
209 Self {
210 input,
211 idx: 0,
212 }
213 }
214}
215
216impl Iterator for FragmentSpans<'_> {
217 type Item = Range<usize>;
218 fn next(&mut self) -> Option<Self::Item> {
219 if self.idx >= self.input.len() {
220 return None;
221 }
222
223 while let Some(start_pos) = find(&self.input[self.idx..], FRAGMENT_START) {
224 let end_pos = self.input[self.idx + start_pos..]
227 .windows(FRAGMENT_END.len())
228 .take(MAX_FRAGMENT_LEN - FRAGMENT_END.len() + 1)
229 .take_while(|win| win[0] != b'\n')
230 .position(|win| win == FRAGMENT_END);
231
232 match end_pos {
233 None => {
235 self.idx += start_pos + FRAGMENT_START.len();
236 }
237
238 Some(end_pos) => {
240 let start = self.idx + start_pos;
241 self.idx = start + end_pos + FRAGMENT_END.len();
242
243 return Some(start + FRAGMENT_START.len()..start + end_pos);
244 }
245 }
246 }
247
248 self.idx = self.input.len();
249 None
250 }
251}
252
253fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
254 memchr::memmem::find(haystack, needle)
255}
256
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 fn dummy_replacer(f: Fragment, mut appender: Appender) -> Result<(), ()> {
263 match f {
264 Fragment::Include(p) => {
265 appender.append(b"i-");
266 appender.append(p.to_uppercase().as_bytes());
267 }
268 Fragment::Path(p) => {
269 appender.append(b"p-");
270 appender.append(&p.bytes().rev().collect::<Vec<_>>())
271 }
272 Fragment::Var(k) => {
273 appender.append(b"v-");
274 appender.append(k.to_lowercase().as_bytes())
275 }
276 }
277
278 Ok(())
279 }
280
281 fn render(input: &[u8]) -> Bytes {
282 let template = Template::parse(Bytes::copy_from_slice(input)).expect("failed to parse");
283 template.render(dummy_replacer).unwrap()
284 }
285
286 #[test]
287 fn render_no_fragments() {
288 let s = b"foo, bar, baz";
289 let res = render(s);
290 assert_eq!(res, s as &[_]);
291 }
292
293 #[test]
294 fn render_simple_fragments() {
295 assert_eq!(
296 render(b"{{: include:banana :}}"),
297 b"i-BANANA" as &[u8],
298 );
299 assert_eq!(
300 render(b"foo {{: path:cat :}}baz"),
301 b"foo p-tacbaz" as &[u8],
302 );
303 assert_eq!(
304 render(b"foo {{: include:cat :}}baz{{: var:DOG :}}"),
305 b"foo i-CATbazv-dog" as &[u8],
306 );
307 }
308
309 #[test]
310 fn render_ignored_fragments() {
311 assert_eq!(
312 render(b"x{{: a\nb :}}y"),
313 b"x{{: a\nb :}}y" as &[u8],
314 );
315 assert_eq!(
316 render(b"x{{: a\n {{: include:kiwi :}}y"),
317 b"x{{: a\n i-KIWIy" as &[u8],
318 );
319
320 let long = b"foo {:: \
321 abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy\
322 abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy\
323 abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy\
324 abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy\
325 abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy\
326 abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy\
327 yo ::} bar\
328 " as &[u8];
329 assert_eq!(render(long), long);
330 }
331}