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}