1#![doc = include_str!("./lib.md")]
3#![deny(missing_docs)]
4#![warn(clippy::pedantic)]
5#![allow(
7 clippy::needless_pass_by_value,
8 clippy::enum_glob_use,
9 clippy::enum_variant_names,
10)]
11
12use fnv::FnvBuildHasher;
13use lru::LruCache;
14use serde::Serialize;
15use serde_json::json;
16use winnow::{
17 ascii::multispace0,
18 combinator::{alt, cut_err, delimited, repeat, separated},
19 error::{AddContext, ErrMode, ErrorKind, ParserError as WParserError},
20 stream::{FindSlice, Stream},
21 token::{literal, take_while},
22 PResult,
23 Parser,
24 Stateful,
25};
26use yoke::{Yoke, Yokeable};
27use std::{
28 borrow::{Borrow, Cow},
29 cell::RefCell,
30 collections::HashMap,
31 fmt::{Debug, Display},
32 fs,
33 hash::{BuildHasher, BuildHasherDefault, Hash},
34 io::{self, Write},
35 num::NonZeroUsize,
36 ops::Deref,
37 path::{Path, PathBuf, MAIN_SEPARATOR_STR},
38 rc::Rc,
39 str,
40};
41use walkdir::WalkDir;
42
43#[cfg(test)]
44mod tests;
45
46#[derive(PartialEq, Debug)]
54enum Fragment<'src> {
55 Literal(&'src str),
56 EscapedVariable(&'src str),
57 UnescapedVariable(&'src str),
58 Section(&'src str),
59 InvertedSection(&'src str),
60 Partial(&'src str),
61}
62
63#[derive(Debug)]
66struct State<'src, 'skips> {
67 fragment_index: usize,
68 section_index: usize,
69 section_starts: Vec<SectionMeta<'src>>,
70 section_skips: &'skips mut Vec<SectionSkip>,
71}
72
73impl<'src, 'skips> State<'src, 'skips> {
78 fn visited_fragment(&mut self) {
79 self.fragment_index += 1;
80 }
81 fn visited_section_start(&mut self, name: &'src str) {
82 self.section_starts.push(SectionMeta {
83 name,
84 section_index: self.section_index,
85 fragment_index: self.fragment_index,
86 });
87 self.section_skips.push(SectionSkip {
88 nested_sections: 0,
89 nested_fragments: 0,
90 });
91 self.fragment_index += 1;
92 self.section_index += 1;
93 }
94 fn visited_section_end(&mut self, name: &'src str) -> Result<(), ()> {
95 let start = self.section_starts
96 .pop()
97 .ok_or(())?;
98 if start.name != name {
99 return Err(());
100 }
101 let skip = &mut self.section_skips[start.section_index];
102 skip.nested_sections = u16::try_from((self.section_index - 1) - start.section_index)
103 .expect("can't have more than 65k sections within a section");
104 skip.nested_fragments = u16::try_from((self.fragment_index - 1) - start.fragment_index)
105 .expect("can't have more than 65k fragments within a section");
106 Ok(())
107 }
108 fn still_expecting_section_ends(&self) -> bool {
109 !self.section_starts.is_empty()
110 }
111}
112
113#[derive(Debug)]
116struct SectionMeta<'src> {
117 name: &'src str,
118 section_index: usize,
119 fragment_index: usize,
120}
121
122#[derive(Debug, PartialEq)]
136struct SectionSkip {
137 nested_sections: u16,
138 nested_fragments: u16,
139}
140
141type Input<'src, 'skips> = Stateful<&'src str, State<'src, 'skips>>;
142
143#[inline]
146fn new_input<'src, 'skips>(template: &'src str, skips: &'skips mut Vec<SectionSkip>) -> Input<'src, 'skips> {
147 Input {
148 input: template,
149 state: State {
150 fragment_index: 0,
151 section_index: 0,
152 section_starts: Vec::new(),
153 section_skips: skips,
154 },
155 }
156}
157
158fn parse<S: Into<Cow<'static, str>>>(source: S) -> Result<Template, InternalError> {
160 let source = match source.into() {
161 Cow::Owned(s) => Yoke::attach_to_cart(s, |s| &s[..]).wrap_cart_in_option(),
162 Cow::Borrowed(s) => Yoke::new_owned(s),
163 };
164 let mut skips = Vec::new();
165
166 let fragments = source.try_map_project(|source, _| {
167 let input = new_input(source, &mut skips);
168 match _parse.parse(input) {
169 Ok(frags) => Ok(Fragments(frags)),
170 Err(err) => Err(err.into_inner()),
171 }
172 })?;
173
174 Ok(Template { fragments, skips })
175}
176
177#[inline]
179fn _parse<'src>(
180 input: &mut Input<'src, '_>,
181) -> PResult<Vec<Fragment<'src>>, InternalError> {
182 if input.input.is_empty() {
183 return Err(ErrMode::Cut(InternalError::ParseErrorNoContent));
184 }
185
186 let frags = repeat(1.., alt((
187 parse_literal.map(Some),
188 parse_section_end.map(|()| None),
189 parse_section_start.map(Some),
190 parse_inverted_section_start.map(Some),
191 parse_unescaped_variable.map(Some),
192 parse_comment.map(|()| None),
193 parse_partial.map(Some),
194 parse_escaped_variable.map(Some),
195 )))
196 .fold(Vec::new, |mut acc, item: Option<Fragment>| {
197 if let Some(item) = item {
198 acc.push(item);
199 }
200 acc
201 })
202 .parse_next(input)?;
203
204 if input.state.still_expecting_section_ends() {
206 return Err(ErrMode::Cut(InternalError::ParseErrorUnclosedSectionTags));
207 }
208
209 Ok(frags)
210}
211
212fn parse_literal<'src>(
215 input: &mut Input<'src, '_>,
216) -> PResult<Fragment<'src>, InternalError> {
217 if input.is_empty() {
218 return Err(ErrMode::Backtrack(InternalError::ParseErrorGeneric));
219 }
220
221 if let Some(range) = input.input.find_slice("{{") {
222 if range.start == 0 {
223 return Err(ErrMode::Backtrack(InternalError::ParseErrorGeneric));
224 }
225 let literal = &input.input[..range.start];
226 let frag = Fragment::Literal(literal);
227 input.input = &input.input[range.start..];
228 input.state.visited_fragment();
229 Ok(frag)
230 } else {
231 let frag = Fragment::Literal(input);
232 input.input = &input.input[input.input.len()..];
233 input.state.visited_fragment();
234 Ok(frag)
235 }
236}
237
238fn is_variable_name(c: char) -> bool {
241 matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_')
242}
243
244fn parse_variable_name<'src>(
247 input: &mut Input<'src, '_>,
248) -> PResult<&'src str, InternalError> {
249 take_while(1.., is_variable_name)
250 .parse_next(input)
251}
252
253fn parse_variable_path<'src>(
256 input: &mut Input<'src, '_>,
257) -> PResult<&'src str, InternalError> {
258 delimited(
259 multispace0,
260 alt((
261 separated(
262 1..,
263 parse_variable_name,
264 '.'
265 ).map(|()| ()).take(),
266 literal("."),
267 )),
268 multispace0,
269 )
270 .parse_next(input)
271}
272
273fn parse_escaped_variable<'src>(
275 input: &mut Input<'src, '_>,
276) -> PResult<Fragment<'src>, InternalError> {
277 let result = delimited(
278 literal("{{"),
279 cut_err(parse_variable_path),
280 cut_err(literal("}}"))
281 )
282 .context(InternalError::ParseErrorInvalidEscapedVariableTag)
283 .parse_next(input)
284 .map(Fragment::EscapedVariable);
285 if result.is_ok() {
286 input.state.visited_fragment();
287 }
288 result
289}
290
291fn parse_unescaped_variable<'src>(
293 input: &mut Input<'src, '_>,
294) -> PResult<Fragment<'src>, InternalError> {
295 let result = delimited(
296 literal("{{{"),
297 cut_err(parse_variable_path),
298 cut_err(literal("}}}"))
299 )
300 .context(InternalError::ParseErrorInvalidUnescapedVariableTag)
301 .parse_next(input)
302 .map(Fragment::UnescapedVariable);
303 if result.is_ok() {
304 input.state.visited_fragment();
305 }
306 result
307}
308
309fn parse_comment(
311 input: &mut Input<'_, '_>
312) -> PResult<(), InternalError> {
313 if input.input.starts_with("{{!") {
314 if let Some(range) = input.input.find_slice("}}") {
315 input.input = &input.input[range.end..];
316 return Ok(());
317 }
318 return Err(ErrMode::Cut(InternalError::ParseErrorInvalidCommentTag));
319 }
320 Err(ErrMode::Backtrack(InternalError::ParseErrorGeneric))
321}
322
323fn parse_section_start<'src>(
325 input: &mut Input<'src, '_>
326) -> PResult<Fragment<'src>, InternalError> {
327 let variable = delimited(
328 literal("{{#"),
329 cut_err(parse_variable_path),
330 cut_err(literal("}}")),
331 )
332 .context(InternalError::ParseErrorInvalidSectionStartTag)
333 .parse_next(input)?;
334
335 input.state.visited_section_start(variable);
336
337 Ok(Fragment::Section(variable))
338}
339
340fn parse_inverted_section_start<'src>(
342 input: &mut Input<'src, '_>,
343) -> PResult<Fragment<'src>, InternalError> {
344 let variable = delimited(
345 literal("{{^"),
346 cut_err(parse_variable_path),
347 cut_err(literal("}}")),
348 )
349 .context(InternalError::ParseErrorInvalidInvertedSectionStartTag)
350 .parse_next(input)?;
351
352 input.state.visited_section_start(variable);
353
354 Ok(Fragment::InvertedSection(variable))
355}
356
357fn parse_section_end(
359 input: &mut Input<'_, '_>,
360) -> PResult<(), InternalError> {
361 let variable = delimited(
362 literal("{{/"),
363 cut_err(parse_variable_path),
364 cut_err(literal("}}")),
365 )
366 .context(InternalError::ParseErrorInvalidSectionEndTag)
367 .parse_next(input)?;
368
369 if input.state.visited_section_end(variable).is_err() {
370 return Err(ErrMode::Cut(InternalError::ParseErrorMismatchedSectionEndTag));
371 }
372
373 Ok(())
374}
375
376const fn valid_file_chars() -> u128 {
381 let mut bitfield = 0u128;
382
383 let mut b = b'0';
384 while b <= b'9' {
385 bitfield |= 1u128 << b;
386 b += 1;
387 }
388
389 b = b'a';
390 while b <= b'z' {
391 bitfield |= 1u128 << b;
392 b += 1;
393 }
394
395 b = b'A';
396 while b <= b'Z' {
397 bitfield |= 1u128 << b;
398 b += 1;
399 }
400
401 let bytes = b"_-.,!@#$%^&()+=[]~";
402 let mut i = 0;
403 while i < bytes.len() {
404 b = bytes[i];
405 bitfield |= 1u128 << b;
406 i += 1;
407 }
408
409 bitfield
410}
411
412const VALID_FILE_CHARS: u128 = valid_file_chars();
414
415#[inline]
417fn is_file_name(c: char) -> bool {
418 c.is_ascii() && (VALID_FILE_CHARS & (1u128 << c as u32)) != 0
419}
420
421fn parse_file_name<'src>(
426 input: &mut Input<'src, '_>
427) -> PResult<&'src str, InternalError> {
428 take_while(1.., is_file_name)
429 .parse_next(input)
430}
431
432fn parse_file_path<'src>(
435 input: &mut Input<'src, '_>,
436) -> PResult<&'src str, InternalError> {
437 delimited(
438 multispace0,
439 separated(
440 1..,
441 parse_file_name,
442 '/'
443 ).map(|()| ()).take(),
444 multispace0,
445 )
446 .parse_next(input)
447}
448
449fn parse_partial<'src>(
451 input: &mut Input<'src, '_>,
452) -> PResult<Fragment<'src>, InternalError> {
453 let result = delimited(
454 literal("{{>"),
455 cut_err(parse_file_path),
456 cut_err(literal("}}")),
457 )
458 .context(InternalError::ParseErrorInvalidPartialTag)
459 .parse_next(input)
460 .map(Fragment::Partial);
461 if result.is_ok() {
462 input.state.visited_fragment();
463 }
464 result
465}
466
467pub struct Template {
481 fragments: Yoke<Fragments<'static>, Option<String>>,
483 skips: Vec<SectionSkip>,
485}
486#[derive(Yokeable)]
487struct Fragments<'src>(Vec<Fragment<'src>>);
488
489impl Debug for Template {
490 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491 f.debug_struct("Template")
492 .field("fragments", &self.fragments.get().0)
493 .field("skips", &self.skips)
494 .finish()
495 }
496}
497impl PartialEq for Template {
498 fn eq(&self, other: &Self) -> bool {
499 self.fragments.get().0 == other.fragments.get().0 && self.skips == other.skips
500 }
501}
502
503impl Template {
504 #[inline]
512 pub fn parse<S: Into<Cow<'static, str>>>(source: S) -> Result<Template, MoostacheError> {
513 match parse(source) {
514 Err(err) => {
515 Err(MoostacheError::from_internal(err, String::new()))
516 },
517 Ok(template) => Ok(template),
518 }
519 }
520
521 #[inline]
528 pub fn render<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized, W: Write>(
529 &self,
530 loader: &T,
531 value: &serde_json::Value,
532 writer: &mut W,
533 ) -> Result<(), T::Error> {
534 let mut scopes = Vec::new();
535 scopes.push(value);
536 _render(
537 &self.fragments.get().0,
538 &self.skips,
539 loader,
540 &mut scopes,
541 writer
542 )
543 }
544
545 #[inline]
553 pub fn render_serializable<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized, W: Write, S: Serialize>(
554 &self,
555 loader: &T,
556 serializeable: &S,
557 writer: &mut W,
558 ) -> Result<(), T::Error> {
559 let value = serde_json::to_value(serializeable)
560 .map_err(|_| MoostacheError::SerializationError)?;
561 self.render(
562 loader,
563 &value,
564 writer
565 )
566 }
567
568 #[inline]
576 pub fn render_no_partials<W: Write>(
577 &self,
578 value: &serde_json::Value,
579 writer: &mut W,
580 ) -> Result<(), MoostacheError> {
581 self.render(
582 &(),
583 value,
584 writer
585 )
586 }
587
588 #[inline]
597 pub fn render_serializable_no_partials<S: Serialize, W: Write>(
598 &self,
599 serializeable: &S,
600 writer: &mut W,
601 ) -> Result<(), MoostacheError> {
602 self.render_serializable(
603 &(),
604 serializeable,
605 writer
606 )
607 }
608
609 #[inline]
616 pub fn render_to_string<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized>(
617 &self,
618 loader: &T,
619 value: &serde_json::Value,
620 ) -> Result<String, T::Error> {
621 let mut writer = Vec::<u8>::new();
622 self.render(
623 loader,
624 value,
625 &mut writer
626 )?;
627 let rendered = unsafe {
628 debug_assert!(str::from_utf8(&writer).is_ok());
632 String::from_utf8_unchecked(writer)
633 };
634 Ok(rendered)
635 }
636
637 #[inline]
646 pub fn render_no_partials_to_string(
647 &self,
648 value: &serde_json::Value,
649 ) -> Result<String, MoostacheError> {
650 self.render_to_string(
651 &(),
652 value,
653 )
654 }
655
656 #[inline]
664 pub fn render_serializable_to_string<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized, S: Serialize>(
665 &self,
666 loader: &T,
667 serializable: &S,
668 ) -> Result<String, T::Error> {
669 let value = serde_json::to_value(serializable)
670 .map_err(|_| MoostacheError::SerializationError)?;
671 self.render_to_string(
672 loader,
673 &value,
674 )
675 }
676
677 #[inline]
687 pub fn render_serializable_no_partials_to_string<S: Serialize>(
688 &self,
689 serializable: &S,
690 ) -> Result<String, MoostacheError> {
691 let value = serde_json::to_value(serializable)
692 .map_err(|_| MoostacheError::SerializationError)?;
693 self.render_to_string(
694 &(),
695 &value,
696 )
697 }
698}
699
700impl TryFrom<&'static str> for Template {
706 type Error = MoostacheError;
707 fn try_from(source: &'static str) -> Result<Self, Self::Error> {
708 Self::parse(source)
709 }
710}
711
712impl TryFrom<String> for Template {
713 type Error = MoostacheError;
714 fn try_from(source: String) -> Result<Self, Self::Error> {
715 Self::parse(source)
716 }
717}
718
719pub trait TemplateLoader<K: Borrow<str> + Eq + Hash = String> {
725 type Output<'a>: Deref<Target = Template> + 'a where Self: 'a;
727 type Error: From<MoostacheError>;
729
730 fn get<'a>(&'a self, name: &str) -> Result<Self::Output<'a>, Self::Error>;
742
743 fn insert(&mut self, name: K, value: Template) -> Option<Template>;
745
746 fn remove(&mut self, name: &str) -> Option<Template>;
748
749 #[inline]
757 fn render<W: Write>(
758 &self,
759 name: &str,
760 value: &serde_json::Value,
761 writer: &mut W,
762 ) -> Result<(), Self::Error> {
763 let template = self.get(name)?;
764 template.render(self, value, writer)
765 }
766
767 #[inline]
776 fn render_serializable<W: Write, S: Serialize>(
777 &self,
778 name: &str,
779 serializeable: &S,
780 writer: &mut W,
781 ) -> Result<(), Self::Error> {
782 let value = serde_json::to_value(serializeable)
783 .map_err(|_| MoostacheError::SerializationError)?;
784 self.render(
785 name,
786 &value,
787 writer
788 )
789 }
790
791 #[inline]
799 fn render_to_string(
800 &self,
801 name: &str,
802 value: &serde_json::Value,
803 ) -> Result<String, Self::Error> {
804 let mut writer = Vec::<u8>::new();
805 self.render(
806 name,
807 value,
808 &mut writer
809 )?;
810 let rendered = unsafe {
811 debug_assert!(str::from_utf8(&writer).is_ok());
815 String::from_utf8_unchecked(writer)
816 };
817 Ok(rendered)
818 }
819
820 #[inline]
829 fn render_serializable_to_string<S: Serialize>(
830 &self,
831 name: &str,
832 serializable: &S,
833 ) -> Result<String, Self::Error> {
834 let value = serde_json::to_value(serializable)
835 .map_err(|_| MoostacheError::SerializationError)?;
836 self.render_to_string(
837 name,
838 &value,
839 )
840 }
841}
842
843#[derive(Clone, Debug, PartialEq)]
879pub struct LoaderConfig<'a> {
880 pub templates_directory: &'a str,
882 pub templates_extension: &'a str,
884 pub cache_size: usize,
886}
887
888#[cfg(windows)]
889const DEFAULT_TEMPLATES_DIRECTORY: &str = ".\\templates\\";
890
891#[cfg(not(windows))]
892const DEFAULT_TEMPLATES_DIRECTORY: &str = "./templates/";
893
894impl Default for LoaderConfig<'_> {
895 fn default() -> Self {
896 Self {
897 templates_directory: DEFAULT_TEMPLATES_DIRECTORY,
898 templates_extension: ".html",
899 cache_size: 200,
900 }
901 }
902}
903
904impl TryFrom<LoaderConfig<'_>> for HashMapLoader {
905 type Error = MoostacheError;
906 fn try_from(config: LoaderConfig<'_>) -> Result<Self, MoostacheError> {
907 let mut dir: String = config.templates_directory.into();
908 if !dir.ends_with(MAIN_SEPARATOR_STR) {
909 dir.push_str(MAIN_SEPARATOR_STR);
910 }
911 let dir_path: &Path = dir.as_ref();
912 let mut ext: String = config.templates_extension.into();
913 if !ext.starts_with('.') {
914 ext.insert(0, '.');
915 }
916 let max_size = NonZeroUsize::new(config.cache_size)
917 .ok_or(MoostacheError::ConfigErrorNonPositiveCacheSize)?;
918 let max_size: usize = max_size.into();
919
920 if !dir_path.is_dir() {
921 return Err(MoostacheError::ConfigErrorInvalidTemplatesDirectory(dir_path.into()));
922 }
923
924 let mut current_size = 0usize;
925 let mut templates: HashMap<String, Template, FnvBuildHasher> = HashMap::with_hasher(BuildHasherDefault::default());
926 for entry in WalkDir::new(dir_path).into_iter().filter_map(Result::ok) {
927 if entry.file_type().is_file() {
928 let entry_path = entry.path();
929 let entry_path_str = entry_path
930 .to_str()
931 .ok_or_else(|| MoostacheError::LoaderErrorNonUtf8FilePath(entry_path.into()))?;
932 if entry_path_str.ends_with(&ext) {
933 let name = entry_path_str
934 .strip_prefix(&dir)
935 .and_then(|path| path.strip_suffix(&ext))
936 .unwrap()
937 .to_string();
938 let source = fs::read_to_string(entry_path)
939 .map_err(|err| MoostacheError::from_io(err, name.clone()))?;
940 let template = Template::parse(source)
941 .map_err(|err| err.set_name(&name))?;
942 templates.insert(name, template);
943 current_size += 1;
944 if current_size > max_size {
945 return Err(MoostacheError::ConfigErrorTooManyTemplates);
946 }
947 }
948 }
949 }
950
951 Ok(HashMapLoader {
952 templates
953 })
954 }
955}
956
957#[derive(Debug)]
980pub struct HashMapLoader<K: Borrow<str> + Eq + Hash = String, H: BuildHasher + Default = FnvBuildHasher> {
981 templates: HashMap<K, Template, H>,
982}
983
984impl<K: Borrow<str> + Eq + Hash, H: BuildHasher + Default> TemplateLoader<K> for HashMapLoader<K, H> {
985 type Output<'a> = &'a Template where K: 'a, H: 'a;
986 type Error = MoostacheError;
987 fn get(&self, name: &str) -> Result<&Template, MoostacheError> {
988 self.templates.get(name)
989 .ok_or_else(|| MoostacheError::LoaderErrorTemplateNotFound(name.into()))
990 }
991 fn insert(&mut self, name: K, value: Template) -> Option<Template> {
992 self.templates.insert(name, value)
993 }
994 fn remove(&mut self, name: &str) -> Option<Template> {
995 self.templates.remove(name)
996 }
997}
998
999#[derive(Debug)]
1012pub struct FileLoader<H: BuildHasher + Default = FnvBuildHasher> {
1013 templates_directory: String,
1014 templates_extension: String,
1015 path_buf: RefCell<String>,
1016 templates: RefCell<LruCache<String, Rc<Template>, H>>,
1017}
1018
1019impl TemplateLoader for FileLoader {
1020 type Output<'a> = Rc<Template>;
1021 type Error = MoostacheError;
1022 fn get(&self, name: &str) -> Result<Rc<Template>, MoostacheError> {
1023 let mut templates = self.templates.borrow_mut();
1024 let template = templates.get(name);
1025 if let Some(template) = template {
1026 return Ok(Rc::clone(template));
1027 }
1028 let mut path_buf = self.path_buf.borrow_mut();
1029 path_buf.clear();
1030 path_buf.push_str(&self.templates_directory);
1031 path_buf.push_str(name);
1032 path_buf.push_str(&self.templates_extension);
1033 let source = fs::read_to_string::<&Path>(path_buf.as_ref())
1034 .map_err(|err| MoostacheError::from_io(err, name.into()))?;
1035 let template = Template::parse(source)
1036 .map_err(|err| err.set_name(name))?;
1037 let template = Rc::new(template);
1038 templates.put(name.into(), Rc::clone(&template));
1039 Ok(template)
1040 }
1041 fn insert(&mut self, name: String, value: Template) -> Option<Template> {
1042 let option = self.templates
1043 .borrow_mut()
1044 .put(name, Rc::new(value));
1045 match option {
1046 Some(template) => {
1047 Rc::into_inner(template)
1048 },
1049 None => None,
1050 }
1051 }
1052 fn remove(&mut self, name: &str) -> Option<Template> {
1053 let option = self.templates
1054 .borrow_mut()
1055 .pop(name);
1056 match option {
1057 Some(template) => {
1058 Rc::into_inner(template)
1059 },
1060 None => None,
1061 }
1062 }
1063}
1064
1065impl TryFrom<LoaderConfig<'_>> for FileLoader {
1066 type Error = MoostacheError;
1067 fn try_from(config: LoaderConfig<'_>) -> Result<Self, MoostacheError> {
1068 let mut dir: String = config.templates_directory.into();
1069 if !dir.ends_with(MAIN_SEPARATOR_STR) {
1070 dir.push_str(MAIN_SEPARATOR_STR);
1071 }
1072 let dir_path: &Path = dir.as_ref();
1073 let mut ext: String = config.templates_extension.into();
1074 if !ext.starts_with('.') {
1075 ext.insert(0, '.');
1076 }
1077 let max_size = NonZeroUsize::new(config.cache_size)
1078 .ok_or(MoostacheError::ConfigErrorNonPositiveCacheSize)?;
1079
1080 if !dir_path.is_dir() {
1081 return Err(MoostacheError::ConfigErrorInvalidTemplatesDirectory(dir_path.into()));
1082 }
1083
1084 let templates = RefCell::new(LruCache::with_hasher(max_size, BuildHasherDefault::default()));
1085
1086 Ok(FileLoader {
1087 templates_directory: dir,
1088 templates_extension: ext,
1089 path_buf: RefCell::new(String::new()),
1090 templates,
1091 })
1092 }
1093}
1094
1095impl<K: Borrow<str> + Eq + Hash, V: Into<Cow<'static, str>>> TryFrom<HashMap<K, V>> for HashMapLoader<K> {
1096 type Error = MoostacheError;
1097 fn try_from(map: HashMap<K, V>) -> Result<Self, Self::Error> {
1098 let templates = map
1099 .into_iter()
1100 .map(|(key, value)| {
1101 match parse(value) {
1102 Ok(template) => Ok((key, template)),
1103 Err(err) => Err(MoostacheError::from_internal(err, key.borrow().to_owned())),
1104 }
1105 })
1106 .collect::<Result<_, _>>();
1107 templates.map(|templates| HashMapLoader {
1108 templates,
1109 })
1110 }
1111}
1112
1113impl TemplateLoader<&'static str> for () {
1114 type Output<'a> = &'a Template;
1115 type Error = MoostacheError;
1116 fn get(&self, name: &str) -> Result<&Template, MoostacheError> {
1117 Err(MoostacheError::LoaderErrorTemplateNotFound(name.into()))
1118 }
1119 fn insert(&mut self, _: &'static str, _: Template) -> Option<Template> {
1120 None
1121 }
1122 fn remove(&mut self, _: &str) -> Option<Template> {
1123 None
1124 }
1125}
1126
1127fn is_truthy(value: &serde_json::Value) -> bool {
1133 use serde_json::Value;
1134 match value {
1135 Value::Null => false,
1136 Value::Bool(b) => *b,
1137 Value::Number(_) => value != &json!(0),
1138 Value::String(string) => !string.is_empty(),
1139 Value::Array(array) => !array.is_empty(),
1140 Value::Object(object) => !object.is_empty(),
1141 }
1142}
1143
1144struct EscapeHtml<'a, W: Write>(&'a mut W);
1147
1148impl<W: Write> Write for EscapeHtml<'_, W> {
1151 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1152 let written = buf.len();
1153 self.write_all(buf)
1154 .map(|()| written)
1155 }
1156 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
1157 let mut start = 0;
1158 let mut end = 0;
1159 for byte in buf {
1160 match byte {
1161 b'&' => {
1162 if start < end {
1163 self.0.write_all(&buf[start..end])?;
1164 }
1165 end += 1;
1166 start = end;
1167 self.0.write_all(b"&")?;
1168 },
1169 b'<' => {
1170 if start < end {
1171 self.0.write_all(&buf[start..end])?;
1172 }
1173 end += 1;
1174 start = end;
1175 self.0.write_all(b"<")?;
1176 },
1177 b'>' => {
1178 if start < end {
1179 self.0.write_all(&buf[start..end])?;
1180 }
1181 end += 1;
1182 start = end;
1183 self.0.write_all(b">")?;
1184 },
1185 b'"' => {
1186 if start < end {
1187 self.0.write_all(&buf[start..end])?;
1188 }
1189 end += 1;
1190 start = end;
1191 self.0.write_all(b""")?;
1192 },
1193 b'\'' => {
1194 if start < end {
1195 self.0.write_all(&buf[start..end])?;
1196 }
1197 end += 1;
1198 start = end;
1199 self.0.write_all(b"'")?;
1200 },
1201 _ => {
1202 end += 1;
1203 },
1204 }
1205 }
1206 if start < end {
1207 self.0.write_all(&buf[start..end])?;
1208 }
1209 Ok(())
1210 }
1211 fn flush(&mut self) -> io::Result<()> {
1212 self.0.flush()
1213 }
1214}
1215
1216fn write_value<W: Write>(
1218 value: &serde_json::Value,
1219 writer: &mut W,
1220) -> Result<(), MoostacheError> {
1221 use serde_json::Value;
1222 match value {
1223 Value::Null => {
1224 },
1228 Value::String(string) => {
1229 writer.write_all(string.as_bytes())
1233 .map_err(|err| MoostacheError::from_io(err, String::new()))?;
1234 },
1235 _ => {
1237 let mut serializer = serde_json::Serializer::new(writer);
1238 value.serialize(&mut serializer)
1239 .map_err(|_| MoostacheError::SerializationError)?;
1240 },
1241 }
1242 Ok(())
1243}
1244
1245fn resolve_value<'a>(path: &str, scopes: &[&'a serde_json::Value]) -> &'a serde_json::Value {
1250 use serde_json::Value;
1251 if path == "." {
1252 return scopes[scopes.len() - 1];
1253 }
1254 let mut resolved_value = &Value::Null;
1255 'parent: for value in scopes.iter().rev() {
1256 resolved_value = *value;
1257 for (idx, key) in path.split('.').enumerate() {
1258 match resolved_value {
1259 Value::Array(array) => {
1260 let parsed_index = key.parse::<usize>();
1263 if let Ok(index) = parsed_index {
1264 let get_option = array.get(index);
1265 match get_option {
1266 Some(get) => {
1267 resolved_value = get;
1268 },
1269 None => {
1270 return &Value::Null;
1271 },
1272 }
1273 } else {
1274 if idx == 0 {
1276 continue 'parent;
1278 }
1279 return &Value::Null;
1280 }
1281 },
1282 Value::Object(object) => {
1283 let get_option = object.get(key);
1284 if let Some(get) = get_option {
1285 resolved_value = get;
1286 } else {
1287 if idx == 0 {
1289 continue 'parent;
1291 }
1292 return &Value::Null;
1293 }
1294 },
1295 _ => {
1298 if idx == 0 {
1300 continue 'parent;
1302 }
1303 return &Value::Null;
1304 }
1305 }
1306 }
1307 return resolved_value;
1308 }
1309 resolved_value
1310}
1311
1312fn _render<K: Borrow<str> + Eq + Hash, T: TemplateLoader<K> + ?Sized, W: Write>(
1316 frags: &[Fragment<'_>],
1317 skips: &[SectionSkip],
1318 loader: &T,
1319 scopes: &mut Vec<&serde_json::Value>,
1320 writer: &mut W,
1321) -> Result<(), T::Error> {
1322 use serde_json::Value;
1323 let mut frag_idx = 0;
1324 let mut section_idx = 0;
1325 while frag_idx < frags.len() {
1326 let frag = &frags[frag_idx];
1327 match frag {
1328 Fragment::Literal(literal) => {
1330 writer.write_all(literal.as_bytes())
1331 .map_err(|err| MoostacheError::from_io(err, String::new()))?;
1332 frag_idx += 1;
1333 },
1334 Fragment::EscapedVariable(name) => {
1336 let resolved_value = resolve_value(name, scopes);
1337 write_value(resolved_value, &mut EscapeHtml(writer))?;
1338 frag_idx += 1;
1339 },
1340 Fragment::UnescapedVariable(name) => {
1342 let resolved_value = resolve_value(name, scopes);
1343 write_value(resolved_value, writer)?;
1344 frag_idx += 1;
1345 },
1346 Fragment::Section(name) => {
1351 let resolved_value = resolve_value(name, scopes);
1352 let start_frag = frag_idx + 1;
1353 let end_frag = start_frag + skips[section_idx].nested_fragments as usize;
1354 let start_section = section_idx + 1;
1355 let end_section = start_section + skips[section_idx].nested_sections as usize;
1356 if is_truthy(resolved_value) {
1357 if let Value::Array(array) = resolved_value {
1358 for value in array {
1359 scopes.push(value);
1360 _render(
1361 &frags[start_frag..end_frag],
1362 &skips[start_section..end_section],
1363 loader,
1364 scopes,
1365 writer,
1366 )?;
1367 scopes.pop();
1368 }
1369 } else {
1370 scopes.push(resolved_value);
1371 _render(
1372 &frags[start_frag..end_frag],
1373 &skips[start_section..end_section],
1374 loader,
1375 scopes,
1376 writer,
1377 )?;
1378 scopes.pop();
1379 }
1380 }
1381 frag_idx += 1 + skips[section_idx].nested_fragments as usize;
1382 section_idx += 1 + skips[section_idx].nested_sections as usize;
1383 },
1384 Fragment::InvertedSection(name) => {
1387 let resolved_value = resolve_value(name, scopes);
1388 let start_frag = frag_idx + 1;
1389 let end_frag = start_frag + skips[section_idx].nested_fragments as usize;
1390 let start_section = section_idx + 1;
1391 let end_section = start_section + skips[section_idx].nested_sections as usize;
1392 if !is_truthy(resolved_value) {
1393 scopes.push(resolved_value);
1394 _render(
1395 &frags[start_frag..end_frag],
1396 &skips[start_section..end_section],
1397 loader,
1398 scopes,
1399 writer,
1400 )?;
1401 scopes.pop();
1402 }
1403 frag_idx += 1 + skips[section_idx].nested_fragments as usize;
1404 section_idx += 1 + skips[section_idx].nested_sections as usize;
1405 },
1406 Fragment::Partial(path) => {
1408 let template = loader.get(path)?;
1409 _render(
1410 &template.fragments.get().0,
1411 &template.skips,
1412 loader,
1413 scopes,
1414 writer,
1415 )?;
1416 frag_idx += 1;
1417 },
1418 }
1419 }
1420 Ok(())
1421}
1422
1423#[derive(Debug, Clone, PartialEq)]
1434pub enum MoostacheError {
1435 IoError(String, std::io::ErrorKind),
1438 ParseErrorGeneric(String),
1441 ParseErrorNoContent(String),
1443 ParseErrorUnclosedSectionTags(String),
1446 ParseErrorInvalidEscapedVariableTag(String),
1449 ParseErrorInvalidUnescapedVariableTag(String),
1452 ParseErrorInvalidSectionEndTag(String),
1455 ParseErrorMismatchedSectionEndTag(String),
1458 ParseErrorInvalidCommentTag(String),
1460 ParseErrorInvalidSectionStartTag(String),
1462 ParseErrorInvalidInvertedSectionStartTag(String),
1464 ParseErrorInvalidPartialTag(String),
1466 LoaderErrorTemplateNotFound(String),
1469 LoaderErrorNonUtf8FilePath(PathBuf),
1472 ConfigErrorNonPositiveCacheSize,
1474 ConfigErrorInvalidTemplatesDirectory(PathBuf),
1476 ConfigErrorTooManyTemplates,
1481 SerializationError,
1484}
1485
1486impl MoostacheError {
1487 fn from_internal(internal: InternalError, s: String) -> Self {
1488 match internal {
1489 InternalError::ParseErrorGeneric => MoostacheError::ParseErrorGeneric(s),
1490 InternalError::ParseErrorNoContent => MoostacheError::ParseErrorNoContent(s),
1491 InternalError::ParseErrorUnclosedSectionTags => MoostacheError::ParseErrorUnclosedSectionTags(s),
1492 InternalError::ParseErrorInvalidEscapedVariableTag => MoostacheError::ParseErrorInvalidEscapedVariableTag(s),
1493 InternalError::ParseErrorInvalidUnescapedVariableTag => MoostacheError::ParseErrorInvalidUnescapedVariableTag(s),
1494 InternalError::ParseErrorInvalidSectionEndTag => MoostacheError::ParseErrorInvalidSectionEndTag(s),
1495 InternalError::ParseErrorMismatchedSectionEndTag => MoostacheError::ParseErrorMismatchedSectionEndTag(s),
1496 InternalError::ParseErrorInvalidCommentTag => MoostacheError::ParseErrorInvalidCommentTag(s),
1497 InternalError::ParseErrorInvalidSectionStartTag => MoostacheError::ParseErrorInvalidSectionStartTag(s),
1498 InternalError::ParseErrorInvalidInvertedSectionStartTag => MoostacheError::ParseErrorInvalidInvertedSectionStartTag(s),
1499 InternalError::ParseErrorInvalidPartialTag => MoostacheError::ParseErrorInvalidPartialTag(s),
1500 }
1501 }
1502 fn set_name(mut self, name: &str) -> Self {
1503 use MoostacheError::*;
1504 match &mut self {
1505 ParseErrorGeneric(s) |
1506 ParseErrorNoContent(s) |
1507 ParseErrorUnclosedSectionTags(s) |
1508 ParseErrorInvalidEscapedVariableTag(s) |
1509 ParseErrorInvalidUnescapedVariableTag(s) |
1510 ParseErrorInvalidSectionEndTag(s) |
1511 ParseErrorMismatchedSectionEndTag(s) |
1512 ParseErrorInvalidCommentTag(s) |
1513 ParseErrorInvalidSectionStartTag(s) |
1514 ParseErrorInvalidInvertedSectionStartTag(s) |
1515 ParseErrorInvalidPartialTag(s) |
1516 IoError(s, _) |
1517 LoaderErrorTemplateNotFound(s) => {
1518 s.clear();
1519 s.push_str(name);
1520 },
1521 _ => unreachable!("trying to set name for parse error"),
1522 };
1523 self
1524 }
1525 fn from_io(io: std::io::Error, s: String) -> Self {
1526 let kind = io.kind();
1527 MoostacheError::IoError(s, kind)
1528 }
1529}
1530
1531impl std::error::Error for MoostacheError {}
1532
1533impl Display for MoostacheError {
1534 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1535 use MoostacheError::*;
1536 fn template_name(name: &str) -> String {
1537 if name.is_empty() {
1538 return "anonymous".to_owned();
1539 }
1540 format!("\"{name}\"")
1541 }
1542 match self {
1543 ParseErrorGeneric(s) => write!(f, "error parsing {} template", template_name(s)),
1544 ParseErrorNoContent(s) => write!(f, "error parsing {} template: empty template", template_name(s)),
1545 ParseErrorUnclosedSectionTags(s) => write!(f, "error parsing {} template: unclosed section tags", template_name(s)),
1546 ParseErrorInvalidEscapedVariableTag(s) => write!(f, "error parsing {} template: invalid escaped variable tag, expected {{{{ variable }}}}", template_name(s)),
1547 ParseErrorInvalidUnescapedVariableTag(s) => write!(f, "error parsing {} template: invalid unescaped variable tag, expected {{{{{{ variable }}}}}}", template_name(s)),
1548 ParseErrorInvalidSectionEndTag(s) => write!(f, "error parsing {} template: invalid section eng tag, expected {{{{/ section }}}}", template_name(s)),
1549 ParseErrorMismatchedSectionEndTag(s) => write!(f, "error parsing {} template: mismatched section eng tag, expected {{{{# section }}}} ... {{{{/ section }}}}", template_name(s)),
1550 ParseErrorInvalidCommentTag(s) => write!(f, "error parsing {} template: invalid comment tag, expected {{{{! comment }}}}", template_name(s)),
1551 ParseErrorInvalidSectionStartTag(s) => write!(f, "error parsing {} template: invalid section start tag, expected {{{{# section }}}}", template_name(s)),
1552 ParseErrorInvalidInvertedSectionStartTag(s) => write!(f, "error parsing {} template: invalid inverted section start tag, expected {{{{^ section }}}}", template_name(s)),
1553 ParseErrorInvalidPartialTag(s) => write!(f, "error parsing {} template: invalid partial tag, expected {{{{> partial }}}}", template_name(s)),
1554 IoError(s, error_kind) => write!(f, "error reading {} template: {}", template_name(s), error_kind),
1555 LoaderErrorTemplateNotFound(s) => write!(f, "loader error: {} template not found", template_name(s)),
1556 LoaderErrorNonUtf8FilePath(s) => write!(f, "loader error: can't load non-utf8 file path: {}", s.display()),
1557 ConfigErrorNonPositiveCacheSize => write!(f, "config error: cache size must be positive"),
1558 ConfigErrorInvalidTemplatesDirectory(s) => write!(f, "config error: invalid templates directory: {}", s.display()),
1559 ConfigErrorTooManyTemplates => write!(f, "config error: templates in directory exceeds cache size"),
1560 SerializationError => write!(f, "serialization error: could not serialize data to serde_json::Value"),
1561 }
1562 }
1563}
1564
1565#[derive(Debug, Copy, Clone, PartialEq)]
1574enum InternalError {
1575 ParseErrorGeneric,
1576 ParseErrorNoContent,
1577 ParseErrorUnclosedSectionTags,
1578 ParseErrorInvalidEscapedVariableTag,
1579 ParseErrorInvalidUnescapedVariableTag,
1580 ParseErrorInvalidSectionEndTag,
1581 ParseErrorMismatchedSectionEndTag,
1582 ParseErrorInvalidCommentTag,
1583 ParseErrorInvalidSectionStartTag,
1584 ParseErrorInvalidInvertedSectionStartTag,
1585 ParseErrorInvalidPartialTag,
1586}
1587
1588impl std::error::Error for InternalError {}
1589
1590impl Display for InternalError {
1591 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1592 use InternalError::*;
1593 match self {
1594 ParseErrorGeneric => write!(f, "generic parse error"),
1595 ParseErrorNoContent => write!(f, "parse error: empty moostache template"),
1596 ParseErrorUnclosedSectionTags => write!(f, "parse error: unclosed section tags"),
1597 ParseErrorInvalidEscapedVariableTag => write!(f, "parse error: invalid escaped variable tag, expected {{{{ variable }}}}"),
1598 ParseErrorInvalidUnescapedVariableTag => write!(f, "parse error: invalid unescaped variable tag, expected {{{{{{ variable }}}}}}"),
1599 ParseErrorInvalidSectionEndTag => write!(f, "parse error: invalid section eng tag, expected {{{{/ section }}}}"),
1600 ParseErrorMismatchedSectionEndTag => write!(f, "parse error: mismatched section eng tag, expected {{{{# section }}}} ... {{{{/ section }}}}"),
1601 ParseErrorInvalidCommentTag => write!(f, "parse error: invalid comment tag, expected {{{{! comment }}}}"),
1602 ParseErrorInvalidSectionStartTag => write!(f, "parse error: invalid section start tag, expected {{{{# section }}}}"),
1603 ParseErrorInvalidInvertedSectionStartTag => write!(f, "parse error: invalid inverted section start tag, expected {{{{^ section }}}}"),
1604 ParseErrorInvalidPartialTag => write!(f, "parse error: invalid partial tag, expected {{{{> partial }}}}"),
1605 }
1606 }
1607}
1608
1609impl<I: Stream> WParserError<I> for InternalError {
1611 #[inline]
1612 fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self {
1613 InternalError::ParseErrorGeneric
1614 }
1615
1616 #[inline]
1617 fn append(
1618 self,
1619 _input: &I,
1620 _token_start: &<I as Stream>::Checkpoint,
1621 _kind: ErrorKind,
1622 ) -> Self {
1623 self
1624 }
1625
1626 #[inline]
1627 fn or(self, other: Self) -> Self {
1628 other
1629 }
1630}
1631
1632impl<I: Stream> AddContext<I, Self> for InternalError {
1634 #[inline]
1635 fn add_context(
1636 self,
1637 _input: &I,
1638 _token_start: &<I as Stream>::Checkpoint,
1639 context: Self,
1640 ) -> Self {
1641 context
1642 }
1643}