lazy_template/system.rs
1use crate::{
2 iter::{LazyParseIter, ParsedTemplate},
3 Parse, Template,
4};
5use core::marker::PhantomData;
6use pipe_trait::Pipe;
7
8#[derive(Debug, Clone, Copy)]
9pub struct TemplateSystem<Parser, Query> {
10 parser: Parser,
11 _query: PhantomData<Query>, // phantom Query is necessary to enable type inference later on
12}
13
14impl<Parser, Query> TemplateSystem<Parser, Query> {
15 pub fn new(parser: Parser) -> Self {
16 TemplateSystem {
17 parser,
18 _query: PhantomData,
19 }
20 }
21}
22
23impl<'a, Parser, Query> TemplateSystem<Parser, Query>
24where
25 Parser: Parse<'a>,
26{
27 /// Create a [`Template`] from a template string.
28 ///
29 /// ```
30 /// # #[cfg(not(feature = "std"))] fn main() {}
31 /// # #[cfg(feature = "std")] fn main() {
32 /// # use pretty_assertions::assert_eq;
33 /// use lazy_template::{Template, simple_curly_braces};
34 /// let system = simple_curly_braces();
35 /// let template: Template<_, _> = system.lazy_parse("{name} is a {age} years old {gender}");
36 /// let output = template
37 /// .to_string(|query| match query {
38 /// "name" => Ok("Alice"),
39 /// "age" => Ok("20"),
40 /// "gender" => Ok("girl"),
41 /// _ => Err(format!("Can't answer {query:?}")),
42 /// })
43 /// .unwrap();
44 /// assert_eq!(output, "Alice is a 20 years old girl");
45 /// # }
46 /// ```
47 ///
48 /// [`Template`] only parses each segment just before it is needed, meaning that even a template with syntax error
49 /// can produce a partial output:
50 ///
51 /// ```
52 /// # #[cfg(not(feature = "std"))] fn main() {}
53 /// # #[cfg(feature = "std")] fn main() {
54 /// # use pretty_assertions::assert_eq;
55 /// let template_string = "{name} is a {age} years } old {gender})"; // incorrectly placed closing curly bracket
56 /// let mut output = String::new();
57 /// let error = lazy_template::simple_curly_braces()
58 /// .lazy_parse(template_string)
59 /// .write_to(&mut output, |query| match query {
60 /// "name" => Ok("Alice"),
61 /// "age" => Ok("20"),
62 /// "gender" => Ok("girl"),
63 /// _ => Err(format!("Can't answer {query:?}")),
64 /// })
65 /// .unwrap_err();
66 /// # assert_eq!(
67 /// # error.to_string(),
68 /// # "Fail to parse query: Unexpected token '}'"
69 /// # );
70 /// assert_eq!(output, "Alice is a 20 years "); // output is partially written
71 /// # }
72 /// ```
73 pub fn lazy_parse(&'a self, text: &'a str) -> Template<LazyParseIter<'a, Parser>, Query> {
74 LazyParseIter::new(text, &self.parser).pipe(Template::new)
75 }
76
77 /// Parse the template string ahead of time.
78 ///
79 /// The returned parsed template can be used multiple times with different responders to generate different outputs:
80 ///
81 /// ```
82 /// # #[cfg(not(feature = "std"))] fn main() {}
83 /// # #[cfg(feature = "std")] fn main() {
84 /// # use pretty_assertions::assert_eq;
85 /// let system = lazy_template::simple_curly_braces();
86 /// let parsed_template = system.eager_parse::<Vec<_>>("Hello, {name}!").unwrap();
87 /// let output = parsed_template
88 /// .to_template()
89 /// .to_string(|query| (query == "name").then_some("Alice").ok_or("Invalid query"))
90 /// .unwrap();
91 /// assert_eq!(output, "Hello, Alice!");
92 /// let output = parsed_template
93 /// .to_template()
94 /// .to_string(|query| (query == "name").then_some("Bob").ok_or("Invalid query"))
95 /// .unwrap();
96 /// assert_eq!(output, "Hello, Bob!");
97 /// # }
98 /// ```
99 ///
100 /// Unlike [`lazy_parse`](Self::lazy_parse), this function would fail if the template fails to parse (e.g. syntax error):
101 ///
102 /// ```
103 /// # #[cfg(not(feature = "std"))] fn main() {}
104 /// # #[cfg(feature = "std")] fn main() {
105 /// # use pretty_assertions::assert_eq;
106 /// let template_string = "Hello, {name!"; // missing a closing curly bracket
107 /// let error = lazy_template::simple_curly_braces()
108 /// .eager_parse::<Vec<_>>(template_string)
109 /// .unwrap_err();
110 /// # assert_eq!(
111 /// # error.to_string(),
112 /// # "Fail to parse query: Unexpected end of input",
113 /// # );
114 /// # }
115 /// ```
116 pub fn eager_parse<SegmentContainer>(
117 &'a self,
118 text: &'a str,
119 ) -> Result<ParsedTemplate<SegmentContainer, Query>, Parser::Error>
120 where
121 SegmentContainer: FromIterator<Parser::Output>,
122 {
123 LazyParseIter::new(text, &self.parser)
124 .collect::<Result<SegmentContainer, Parser::Error>>()
125 .map(ParsedTemplate::new)
126 }
127}
128
129/// Convert a [parser](Parse) into a [`TemplateSystem`].
130pub trait IntoTemplateSystem: Sized {
131 fn into_template_system<Query>(self) -> TemplateSystem<Self, Query> {
132 TemplateSystem::new(self)
133 }
134}
135impl<'a, Parser> IntoTemplateSystem for Parser where Parser: Parse<'a> {}