hcl/template/mod.rs
1//! Types to represent the HCL template sub-language.
2//!
3//! When parsing an HCL document, template expressions are emitted as [`TemplateExpr`] (as the
4//! `TemplateExpr` variant of the [`Expression`] enum) which contains the raw unparsed template
5//! expressions.
6//!
7//! These template expressions can be further parsed into a [`Template`] which is composed of
8//! literal strings, template interpolations and template directives.
9//!
10//! Refer to the [HCL syntax specification][hcl-syntax-spec] for the details.
11//!
12//! [hcl-syntax-spec]: https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md#templates
13//!
14//! # Example
15//!
16//! Parse a `TemplateExpr` into a `Template`:
17//!
18//! ```
19//! # use std::error::Error;
20//! #
21//! # fn main() -> Result<(), Box<dyn Error>> {
22//! use hcl::Template;
23//! use hcl::expr::{TemplateExpr, Variable};
24//!
25//! let expr = TemplateExpr::from("Hello ${name}!");
26//! let template = Template::from_expr(&expr)?;
27//!
28//! let expected = Template::new()
29//! .add_literal("Hello ")
30//! .add_interpolation(Variable::new("name")?)
31//! .add_literal("!");
32//!
33//! assert_eq!(expected, template);
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! It is also possible to use the template sub-language in a standalone way to parse template
39//! strings directly:
40//!
41//! ```
42//! # use std::error::Error;
43//! #
44//! # fn main() -> Result<(), Box<dyn Error>> {
45//! use hcl::Identifier;
46//! use hcl::expr::Variable;
47//! use hcl::template::{ForDirective, Strip, Template};
48//! use std::str::FromStr;
49//!
50//! let raw = r#"
51//! Bill of materials:
52//! %{ for item in items ~}
53//! - ${item}
54//! %{ endfor ~}
55//! "#;
56//!
57//! let template = Template::from_str(raw)?;
58//!
59//! let expected = Template::new()
60//! .add_literal("\nBill of materials:\n")
61//! .add_directive(
62//! ForDirective::new(
63//! Identifier::new("item")?,
64//! Variable::new("items")?,
65//! Template::new()
66//! .add_literal("\n- ")
67//! .add_interpolation(Variable::new("item")?)
68//! .add_literal("\n")
69//! )
70//! .with_for_strip(Strip::End)
71//! .with_endfor_strip(Strip::End)
72//! )
73//! .add_literal("\n");
74//!
75//! assert_eq!(expected, template);
76//! # Ok(())
77//! # }
78//! ```
79//!
80//! # Template evaluation
81//!
82//! The [`eval`][crate::eval] module provides evaluation capabilities for templates and
83//! expressions. See the [module-level documentation][crate::eval] for examples.
84
85mod edit;
86
87use crate::de::FromStrVisitor;
88use crate::expr::{Expression, TemplateExpr};
89use crate::{format, Error, Identifier, Result};
90use serde::{Deserialize, Serialize};
91use std::fmt::{self, Display};
92use std::str::FromStr;
93
94// Re-exported for convenience.
95#[doc(inline)]
96pub use hcl_primitives::template::Strip;
97
98/// The main type to represent the HCL template sub-languange.
99///
100/// A template behaves like an expression that always returns a string value. The different
101/// elements of the template are evaluated and combined into a single string to return.
102///
103/// See the [`module level`][`crate::template`] documentation for usage examples.
104#[derive(Debug, Clone, PartialEq, Eq, Default)]
105pub struct Template {
106 elements: Vec<Element>,
107}
108
109impl Template {
110 /// Creates an empty template with no elements.
111 pub fn new() -> Template {
112 Template {
113 elements: Vec::new(),
114 }
115 }
116
117 /// Expands a raw template expression to a template.
118 ///
119 /// ## Errors
120 ///
121 /// Returns an error if the parsing of raw string templates fails or if the template expression
122 /// contains string literals with invalid escape sequences.
123 pub fn from_expr(expr: &TemplateExpr) -> Result<Self> {
124 Template::from_str(expr.as_str())
125 }
126
127 /// Returns a reference to the template elements.
128 pub fn elements(&self) -> &[Element] {
129 &self.elements
130 }
131
132 /// Returns a mutable reference to the template elements.
133 pub fn elements_mut(&mut self) -> &mut [Element] {
134 &mut self.elements
135 }
136}
137
138// Builder methods.
139impl Template {
140 /// Adds a template element (literal, interpolation or directive) to the template.
141 pub fn add_element<T>(mut self, element: T) -> Template
142 where
143 T: Into<Element>,
144 {
145 self.elements.push(element.into());
146 self
147 }
148
149 /// Adds a literal to the template.
150 pub fn add_literal<T>(self, literal: T) -> Template
151 where
152 T: Into<String>,
153 {
154 self.add_element(literal.into())
155 }
156
157 /// Adds an interpolation to the template.
158 pub fn add_interpolation<T>(self, interpolation: T) -> Template
159 where
160 T: Into<Interpolation>,
161 {
162 self.add_element(interpolation.into())
163 }
164
165 /// Adds a directive to the template.
166 pub fn add_directive<T>(self, directive: T) -> Template
167 where
168 T: Into<Directive>,
169 {
170 self.add_element(directive.into())
171 }
172}
173
174impl FromStr for Template {
175 type Err = Error;
176
177 #[inline]
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
179 let template: hcl_edit::template::Template = s.parse()?;
180 Ok(template.into())
181 }
182}
183
184impl<T> FromIterator<T> for Template
185where
186 T: Into<Element>,
187{
188 fn from_iter<I>(iter: I) -> Self
189 where
190 I: IntoIterator<Item = T>,
191 {
192 Template {
193 elements: iter.into_iter().map(Into::into).collect(),
194 }
195 }
196}
197
198impl Display for Template {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 // Formatting a `Template` as string cannot fail.
201 let formatted = format::to_string(self).expect("a Template failed to format unexpectedly");
202 f.write_str(&formatted)
203 }
204}
205
206/// An element of an HCL template.
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub enum Element {
209 /// A literal sequence of characters to include in the resulting string.
210 Literal(String),
211 /// An interpolation sequence that evaluates an expression (written in the expression
212 /// sub-language), and converts the result to a string value.
213 Interpolation(Interpolation),
214 /// A `if` and `for` directive that allows for conditional template evaluation.
215 Directive(Box<Directive>),
216}
217
218impl Element {
219 pub(crate) fn strip(&self) -> Strip {
220 match self {
221 Element::Literal(_) => Strip::None,
222 Element::Interpolation(interp) => interp.strip,
223 Element::Directive(dir) => dir.strip(),
224 }
225 }
226}
227
228impl From<&str> for Element {
229 fn from(literal: &str) -> Self {
230 Element::Literal(literal.to_owned())
231 }
232}
233
234impl From<String> for Element {
235 fn from(literal: String) -> Self {
236 Element::Literal(literal)
237 }
238}
239
240impl From<Interpolation> for Element {
241 fn from(interpolation: Interpolation) -> Self {
242 Element::Interpolation(interpolation)
243 }
244}
245
246impl From<Directive> for Element {
247 fn from(directive: Directive) -> Self {
248 Element::Directive(Box::new(directive))
249 }
250}
251
252/// An interpolation sequence evaluates an expression (written in the expression sub-language),
253/// converts the result to a string value, and replaces itself with the resulting string.
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct Interpolation {
256 /// The interpolated expression.
257 pub expr: Expression,
258 /// The whitespace strip mode to use on the template elements preceeding and following after
259 /// this interpolation sequence.
260 pub strip: Strip,
261}
262
263impl Interpolation {
264 /// Creates a new expression `Interpolation`.
265 pub fn new<T>(expr: T) -> Interpolation
266 where
267 T: Into<Expression>,
268 {
269 Interpolation {
270 expr: expr.into(),
271 strip: Strip::None,
272 }
273 }
274
275 /// Sets the whitespace strip mode to use on the template elements preceeding and following
276 /// after this interpolation sequence and returns the modified `Interpolation`.
277 pub fn with_strip(mut self, strip: Strip) -> Interpolation {
278 self.strip = strip;
279 self
280 }
281}
282
283impl<T> From<T> for Interpolation
284where
285 T: Into<Expression>,
286{
287 fn from(expr: T) -> Self {
288 Interpolation {
289 expr: expr.into(),
290 strip: Strip::default(),
291 }
292 }
293}
294
295/// A template directive that allows for conditional template evaluation.
296#[derive(Debug, Clone, PartialEq, Eq)]
297pub enum Directive {
298 /// Represents a template `if` directive.
299 If(IfDirective),
300 /// Represents a template `for` directive.
301 For(ForDirective),
302}
303
304impl Directive {
305 fn strip(&self) -> Strip {
306 let (start, end) = match self {
307 Directive::If(dir) => (dir.if_strip, dir.endif_strip),
308 Directive::For(dir) => (dir.for_strip, dir.endfor_strip),
309 };
310
311 Strip::from((start.strip_start(), end.strip_end()))
312 }
313}
314
315impl From<IfDirective> for Directive {
316 fn from(directive: IfDirective) -> Self {
317 Directive::If(directive)
318 }
319}
320
321impl From<ForDirective> for Directive {
322 fn from(directive: ForDirective) -> Self {
323 Directive::For(directive)
324 }
325}
326
327/// The template `if` directive is the template equivalent of the conditional expression, allowing
328/// selection of one of two sub-templates based on the condition result.
329#[derive(Debug, Clone, PartialEq, Eq)]
330pub struct IfDirective {
331 /// The condition expression.
332 pub cond_expr: Expression,
333 /// The template that is included in the result string if the conditional expression evaluates
334 /// to `true`.
335 pub true_template: Template,
336 /// The template that is included in the result string if the `if` branch's conditional
337 /// expression evaluates to `false`. This is `None` if there is no `else` branch in which case
338 /// the result string will be empty.
339 pub false_template: Option<Template>,
340 /// The whitespace strip mode to use on the template elements preceeding and following after
341 /// the `if` expression.
342 pub if_strip: Strip,
343 /// The whitespace strip mode to use on the template elements preceeding and following after
344 /// the `else` expression. This has no effect if `false_template` is `None`.
345 pub else_strip: Strip,
346 /// The whitespace strip mode to use on the template elements preceeding and following after
347 /// the `endif` marker of this directive.
348 pub endif_strip: Strip,
349}
350
351impl IfDirective {
352 /// Creates a new `IfDirective` from a conditional expression and a template that is included
353 /// in the result string if the conditional expression evaluates to `true`.
354 pub fn new<T>(cond_expr: T, true_template: Template) -> IfDirective
355 where
356 T: Into<Expression>,
357 {
358 IfDirective {
359 cond_expr: cond_expr.into(),
360 true_template,
361 false_template: None,
362 if_strip: Strip::default(),
363 else_strip: Strip::default(),
364 endif_strip: Strip::default(),
365 }
366 }
367
368 /// Adds a template for the `else` branch which is included in the result string if the
369 /// condition of the `IfDirective` evaluates to `false` and returns the modified `IfDirective`.
370 pub fn with_false_template<T>(mut self, else_template: T) -> IfDirective
371 where
372 T: Into<Template>,
373 {
374 self.false_template = Some(else_template.into());
375 self
376 }
377
378 /// Sets the whitespace strip mode to use on the template elements preceeding and following
379 /// after the `if` expression and returns the modified `IfDirective`.
380 pub fn with_if_strip(mut self, strip: Strip) -> IfDirective {
381 self.if_strip = strip;
382 self
383 }
384
385 /// Sets the whitespace strip mode to use on the template elements preceeding and following
386 /// after the `else` expression and returns the modified `IfDirective`.
387 pub fn with_else_strip(mut self, strip: Strip) -> IfDirective {
388 self.else_strip = strip;
389 self
390 }
391
392 /// Sets the whitespace strip mode to use on the template elements preceeding and following
393 /// after the `endif` marker of this directive and returns the modified `IfDirective`.
394 pub fn with_endif_strip(mut self, strip: Strip) -> IfDirective {
395 self.endif_strip = strip;
396 self
397 }
398}
399
400/// The template `for` directive is the template equivalent of the for expression, producing zero
401/// or more copies of its sub-template based on the elements of a collection.
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct ForDirective {
404 /// Optional iterator key variable identifier.
405 pub key_var: Option<Identifier>,
406 /// The iterator value variable identifier.
407 pub value_var: Identifier,
408 /// The expression that produces the list or object of elements to iterate over.
409 pub collection_expr: Expression,
410 /// The template that is included in the result string for each loop iteration.
411 pub template: Template,
412 /// The whitespace strip mode to use on the template elements preceeding and following after
413 /// the `for` expression.
414 pub for_strip: Strip,
415 /// The whitespace strip mode to use on the template elements preceeding and following after
416 /// the `endfor` marker of this directive.
417 pub endfor_strip: Strip,
418}
419
420impl ForDirective {
421 /// Creates a new `ForDirective` from the provided iterator value identifier, an expression
422 /// that produces the list or object of elements to iterate over, and the template the is
423 /// included in the result string for each loop iteration.
424 pub fn new<T>(value: Identifier, collection_expr: T, template: Template) -> ForDirective
425 where
426 T: Into<Expression>,
427 {
428 ForDirective {
429 key_var: None,
430 value_var: value,
431 collection_expr: collection_expr.into(),
432 template,
433 for_strip: Strip::default(),
434 endfor_strip: Strip::default(),
435 }
436 }
437
438 /// Adds the iterator key variable identifier to the `for` expression and returns the modified
439 /// `ForDirective`.
440 pub fn with_key_var(mut self, key_var: Identifier) -> ForDirective {
441 self.key_var = Some(key_var);
442 self
443 }
444
445 /// Sets the whitespace strip mode to use on the template elements preceeding and following
446 /// after the `for` expression and returns the modified `ForDirective`.
447 pub fn with_for_strip(mut self, strip: Strip) -> ForDirective {
448 self.for_strip = strip;
449 self
450 }
451
452 /// Sets the whitespace strip mode to use on the template elements preceeding and following
453 /// after the `endfor` marker of this directive and returns the modified `ForDirective`.
454 pub fn with_endfor_strip(mut self, strip: Strip) -> ForDirective {
455 self.endfor_strip = strip;
456 self
457 }
458}
459
460impl Serialize for Template {
461 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
462 where
463 S: serde::Serializer,
464 {
465 serializer.serialize_str(&self.to_string())
466 }
467}
468
469impl<'de> Deserialize<'de> for Template {
470 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
471 where
472 D: serde::Deserializer<'de>,
473 {
474 deserializer.deserialize_any(FromStrVisitor::<Self>::new("a template"))
475 }
476}