Skip to main content

hcl/format/
mod.rs

1//! Format data structures as HCL.
2//!
3//! This module provides the [`Formatter`] type and the convienince functions [`to_string`],
4//! [`to_vec`] and [`to_writer`] for formatting the data structures provided by this crate as HCL.
5//!
6//! For serialization of other Rust data structures implementing [`serde::Serialize`] refer to the
7//! documentation of the [`ser`](crate::ser) module.
8//!
9//! # Examples
10//!
11//! Format an HCL block as string:
12//!
13//! ```
14//! # use std::error::Error;
15//! #
16//! # fn main() -> Result<(), Box<dyn Error>> {
17//! let block = hcl::Block::builder("user")
18//!     .add_label("johndoe")
19//!     .add_attribute(("age", 34))
20//!     .add_attribute(("email", "johndoe@example.com"))
21//!     .build();
22//!
23//! let expected = r#"
24//! user "johndoe" {
25//!   age = 34
26//!   email = "johndoe@example.com"
27//! }
28//! "#.trim_start();
29//!
30//! let formatted = hcl::format::to_string(&block)?;
31//!
32//! assert_eq!(formatted, expected);
33//! #   Ok(())
34//! # }
35//! ```
36
37mod escape;
38mod impls;
39
40use self::escape::{CharEscape, ESCAPE};
41use crate::Result;
42use hcl_primitives::template::escape_markers;
43use std::io;
44
45mod private {
46    pub trait Sealed {}
47}
48
49/// A trait to format data structures as HCL.
50///
51/// This trait is sealed to prevent implementation outside of this crate.
52pub trait Format: private::Sealed {
53    /// Formats a HCL structure using a formatter and writes the result to the provided writer.
54    ///
55    /// # Errors
56    ///
57    /// Formatting the data structure or writing to the writer may fail with an `Error`.
58    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
59    where
60        W: io::Write;
61
62    /// Formats a HCL structure using a formatter and returns the result as a `Vec<u8>`.
63    ///
64    /// # Errors
65    ///
66    /// Formatting the data structure or writing to the writer may fail with an `Error`.
67    fn format_vec<W>(&self, fmt: &mut Formatter<W>) -> Result<Vec<u8>>
68    where
69        W: io::Write + AsMut<Vec<u8>>,
70    {
71        self.format(fmt)?;
72        // "Drain" the buffer by splitting off all bytes, leaving the formatter's buffer empty
73        // ready for reuse.
74        Ok(fmt.writer.as_mut().split_off(0))
75    }
76
77    /// Formats a HCL structure using a formatter and returns the result as a `String`.
78    ///
79    /// # Errors
80    ///
81    /// Formatting the data structure or writing to the writer may fail with an `Error`.
82    fn format_string<W>(&self, fmt: &mut Formatter<W>) -> Result<String>
83    where
84        W: io::Write + AsMut<Vec<u8>>,
85    {
86        let bytes = self.format_vec(fmt)?;
87        // SAFETY: The `Formatter` never emits invalid UTF-8.
88        Ok(unsafe { String::from_utf8_unchecked(bytes) })
89    }
90}
91
92#[derive(PartialEq)]
93enum FormatState {
94    Initial,
95    AttributeStart,
96    AttributeEnd,
97    BlockStart,
98    BlockEnd,
99    BlockBodyStart,
100}
101
102struct FormatConfig<'a> {
103    indent: &'a [u8],
104    dense: bool,
105    compact_arrays: bool,
106    compact_objects: bool,
107    prefer_ident_keys: bool,
108}
109
110impl Default for FormatConfig<'_> {
111    fn default() -> Self {
112        FormatConfig {
113            indent: b"  ",
114            dense: false,
115            compact_arrays: false,
116            compact_objects: false,
117            prefer_ident_keys: false,
118        }
119    }
120}
121
122/// A pretty printing HCL formatter.
123///
124/// # Examples
125///
126/// Format an HCL block as string:
127///
128/// ```
129/// # use std::error::Error;
130/// #
131/// # fn main() -> Result<(), Box<dyn Error>> {
132/// use hcl::format::{Format, Formatter};
133///
134/// let mut buf = Vec::new();
135/// let mut formatter = Formatter::new(&mut buf);
136///
137/// let block = hcl::Block::builder("user")
138///     .add_label("johndoe")
139///     .add_attribute(("age", 34))
140///     .add_attribute(("email", "johndoe@example.com"))
141///     .build();
142///
143/// block.format(&mut formatter)?;
144///
145/// let expected = r#"
146/// user "johndoe" {
147///   age = 34
148///   email = "johndoe@example.com"
149/// }
150/// "#.trim_start();
151///
152/// let formatted = String::from_utf8(buf)?;
153///
154/// assert_eq!(formatted, expected);
155/// #   Ok(())
156/// # }
157/// ```
158///
159/// The [`builder()`](Formatter::builder) method can be used to construct a custom `Formatter` for
160/// use with a [`Serializer`][Serializer]:
161///
162/// ```
163/// use hcl::{format::Formatter, ser::Serializer};
164/// # let mut writer = Vec::new();
165///
166/// let formatter = Formatter::builder()
167///     .indent(b"  ")
168///     .dense(false)
169///     .build(&mut writer);
170///
171/// let ser = Serializer::with_formatter(formatter);
172/// ```
173///
174/// [Serializer]: ../ser/struct.Serializer.html
175pub struct Formatter<'a, W> {
176    writer: W,
177    config: FormatConfig<'a>,
178    state: FormatState,
179    first_element: bool,
180    current_indent: usize,
181    has_value: bool,
182    compact_mode_level: u64,
183    nesting_state: Vec<(bool, bool)>,
184}
185
186/// A builder to create a `Formatter`.
187///
188/// See the documentation of [`Formatter`] for a usage example.
189pub struct FormatterBuilder<'a> {
190    config: FormatConfig<'a>,
191}
192
193impl<'a> FormatterBuilder<'a> {
194    /// Set the indent for indenting nested HCL structures.
195    ///
196    /// The default indentation is two spaces.
197    pub fn indent(mut self, indent: &'a [u8]) -> Self {
198        self.config.indent = indent;
199        self
200    }
201
202    /// If set, blocks are not visually separated by empty lines from attributes and adjacent
203    /// blocks.
204    ///
205    /// Default formatting:
206    ///
207    /// ```hcl
208    /// attr1 = "value1"
209    /// attr2 = "value2"
210    ///
211    /// block1 {}
212    ///
213    /// block2 {}
214    /// ```
215    ///
216    /// Dense formatting:
217    ///
218    /// ```hcl
219    /// attr1 = "value1"
220    /// attr2 = "value2"
221    /// block1 {}
222    /// block2 {}
223    /// ```
224    pub fn dense(mut self, yes: bool) -> Self {
225        self.config.dense = yes;
226        self
227    }
228
229    /// If set, arrays and objects are formatted in a more compact way.
230    ///
231    /// See the method documation of [`compact_arrays`][FormatterBuilder::compact_arrays] and
232    /// [`compact_objects`][FormatterBuilder::compact_objects].
233    pub fn compact(self, yes: bool) -> Self {
234        self.compact_arrays(yes).compact_objects(yes)
235    }
236
237    /// Controls the array formatting.
238    ///
239    /// By default, array elements are separated by newlines:
240    ///
241    /// ```hcl
242    /// array = [
243    ///   1,
244    ///   2,
245    ///   3,
246    /// ]
247    /// ```
248    ///
249    /// When compact array formatting is enabled no newlines are inserted between elements:
250    ///
251    /// ```hcl
252    /// array = [1, 2, 3]
253    /// ```
254    pub fn compact_arrays(mut self, yes: bool) -> Self {
255        self.config.compact_arrays = yes;
256        self
257    }
258
259    /// Controls the object formatting.
260    ///
261    /// By default, object items are separated by newlines:
262    ///
263    /// ```hcl
264    /// object = {
265    ///   one = "foo"
266    ///   two = "bar"
267    ///   three = "baz"
268    /// }
269    /// ```
270    ///
271    /// When compact object formatting is enabled no newlines are inserted between items:
272    ///
273    /// ```hcl
274    /// object = { one = "foo", two = "bar", three = "baz" }
275    /// ```
276    pub fn compact_objects(mut self, yes: bool) -> Self {
277        self.config.compact_objects = yes;
278        self
279    }
280
281    /// Controls the object key quoting.
282    ///
283    /// By default, object keys are formatted as quoted strings (unless they are of variant
284    /// [`ObjectKey::Identifier`][ident-variant]).
285    ///
286    /// ```hcl
287    /// object = {
288    ///   "foo" = 1
289    ///   "bar baz" = 2
290    /// }
291    /// ```
292    ///
293    /// When identifier keys are preferred, object keys that are also valid HCL identifiers are
294    /// not quoted:
295    ///
296    /// ```hcl
297    /// object = {
298    ///   foo = 1
299    ///   "bar baz" = 2
300    /// }
301    /// ```
302    ///
303    /// [ident-variant]: crate::expr::ObjectKey::Identifier
304    pub fn prefer_ident_keys(mut self, yes: bool) -> Self {
305        self.config.prefer_ident_keys = yes;
306        self
307    }
308
309    /// Consumes the `FormatterBuilder` and turns it into a `Formatter` which writes HCL to the
310    /// provided writer.
311    pub fn build<W>(self, writer: W) -> Formatter<'a, W>
312    where
313        W: io::Write,
314    {
315        Formatter {
316            writer,
317            config: self.config,
318            state: FormatState::Initial,
319            first_element: false,
320            current_indent: 0,
321            has_value: false,
322            compact_mode_level: 0,
323            nesting_state: Vec::new(),
324        }
325    }
326
327    /// Consumes the `FormatterBuilder` and turns it into a `Formatter` which is specialized to use
328    /// a pre-allocated `Vec<u8>` as internal buffer.
329    ///
330    /// The returned formatter can be passed to the [`format_string`][Format::format_string] or
331    /// [`format_vec`][Format::format_vec] method of types implementing [`Format`].
332    ///
333    /// Alternatively, the internal buffer can be obtained by calling
334    /// [`into_inner`][Formatter::into_inner] on the returned `Formatter` after passing it to the
335    /// [`format`][Format::format] method of a type implementing [`Format`].
336    ///
337    /// # Examples
338    ///
339    /// ```
340    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
341    /// use hcl::format::{Format, Formatter};
342    /// use hcl::structure::Attribute;
343    ///
344    /// let mut formatter = Formatter::builder()
345    ///     .compact_arrays(true)
346    ///     .build_vec();
347    ///
348    /// let attr = Attribute::new("foo", vec![1, 2, 3]);
349    ///
350    /// assert_eq!(attr.format_string(&mut formatter)?, "foo = [1, 2, 3]\n");
351    /// #    Ok(())
352    /// # }
353    /// ```
354    pub fn build_vec(self) -> Formatter<'a, Vec<u8>> {
355        let vec = Vec::with_capacity(128);
356        self.build(vec)
357    }
358}
359
360impl Default for Formatter<'_, Vec<u8>> {
361    /// Creates the default `Formatter` which is specialized to use a pre-allocated `Vec<u8>` as
362    /// internal buffer.
363    ///
364    /// The formatter can be passed to the [`format_string`][Format::format_string] or
365    /// [`format_vec`][Format::format_vec] method of types implementing [`Format`].
366    ///
367    /// Alternatively, the internal buffer can be obtained by calling
368    /// [`into_inner`][Formatter::into_inner] after passing it to the [`format`][Format::format]
369    /// method of a type implementing [`Format`].
370    fn default() -> Self {
371        Formatter::builder().build_vec()
372    }
373}
374
375// Public API.
376impl<'a> Formatter<'a, ()> {
377    /// Creates a new [`FormatterBuilder`] to start building a new `Formatter`.
378    pub fn builder() -> FormatterBuilder<'a> {
379        FormatterBuilder {
380            config: FormatConfig::default(),
381        }
382    }
383}
384
385// Public API.
386impl<'a, W> Formatter<'a, W>
387where
388    W: io::Write,
389{
390    /// Creates a new `Formatter` which writes HCL to the provided writer.
391    pub fn new(writer: W) -> Formatter<'a, W> {
392        Formatter::builder().build(writer)
393    }
394
395    /// Takes ownership of the `Formatter` and returns the underlying writer.
396    pub fn into_inner(self) -> W {
397        self.writer
398    }
399}
400
401// Internal formatter API.
402impl<W> Formatter<'_, W>
403where
404    W: io::Write,
405{
406    /// Writes `null` to the writer.
407    fn write_null(&mut self) -> Result<()> {
408        self.write_bytes(b"null")
409    }
410
411    /// Writes a boolean value to the writer.
412    fn write_bool(&mut self, value: bool) -> Result<()> {
413        let s = if value {
414            b"true" as &[u8]
415        } else {
416            b"false" as &[u8]
417        };
418        self.write_bytes(s)
419    }
420
421    /// Writes an integer value to the writer.
422    fn write_int<T>(&mut self, value: T) -> Result<()>
423    where
424        T: itoa::Integer,
425    {
426        let mut buffer = itoa::Buffer::new();
427        let s = buffer.format(value);
428        self.write_bytes(s.as_bytes())
429    }
430
431    /// Writes a quoted string to the writer.
432    fn write_quoted_string(&mut self, s: &str) -> Result<()> {
433        self.write_bytes(b"\"")?;
434        self.write_string_fragment(s)?;
435        self.write_bytes(b"\"")
436    }
437
438    /// Writes a quoted string to the writer after escaping it.
439    fn write_quoted_string_escaped(&mut self, s: &str) -> Result<()> {
440        self.write_bytes(b"\"")?;
441        self.write_escaped_string(s)?;
442        self.write_bytes(b"\"")
443    }
444
445    /// Writes a string fragment to the writer. No escaping occurs.
446    fn write_string_fragment(&mut self, s: &str) -> Result<()> {
447        self.write_bytes(s.as_bytes())
448    }
449
450    /// Writes a string to the writer and escapes control characters and quotes that might be
451    /// contained in it.
452    fn write_escaped_string(&mut self, value: &str) -> Result<()> {
453        let value = escape_markers(value);
454        let bytes = value.as_bytes();
455
456        let mut start = 0;
457
458        for (i, &byte) in bytes.iter().enumerate() {
459            let escape = ESCAPE[byte as usize];
460            if escape == 0 {
461                continue;
462            }
463
464            if start < i {
465                self.write_string_fragment(&value[start..i])?;
466            }
467
468            let char_escape = CharEscape::from_escape_table(escape, byte);
469            char_escape.write_escaped(&mut self.writer)?;
470
471            start = i + 1;
472        }
473
474        if start != bytes.len() {
475            self.write_string_fragment(&value[start..])?;
476        }
477
478        Ok(())
479    }
480
481    /// Signals the start of an array to the formatter.
482    fn begin_array(&mut self) -> Result<()> {
483        self.nesting_state
484            .push((self.first_element, self.has_value));
485        if !self.compact_arrays() {
486            self.current_indent += 1;
487        }
488        self.has_value = false;
489        self.first_element = true;
490        self.write_bytes(b"[")
491    }
492
493    /// Signals the start of an array value to the formatter.
494    fn begin_array_value(&mut self) -> Result<()> {
495        if self.first_element {
496            self.first_element = false;
497            if !self.compact_arrays() {
498                self.write_bytes(b"\n")?;
499                self.write_indent(self.current_indent)?;
500            }
501        } else if self.compact_arrays() {
502            self.write_bytes(b", ")?;
503        } else {
504            self.write_bytes(b",\n")?;
505            self.write_indent(self.current_indent)?;
506        }
507
508        Ok(())
509    }
510
511    /// Signals the end of an array value to the formatter.
512    fn end_array_value(&mut self) -> Result<()> {
513        self.has_value = true;
514        Ok(())
515    }
516
517    /// Signals the end of an array to the formatter.
518    fn end_array(&mut self) -> Result<()> {
519        if !self.compact_arrays() {
520            self.current_indent -= 1;
521
522            if self.has_value {
523                self.write_bytes(b"\n")?;
524                self.write_indent(self.current_indent)?;
525            }
526        }
527
528        let result = self.write_bytes(b"]");
529        if let Some((first_element, has_value)) = self.nesting_state.pop() {
530            self.first_element = first_element;
531            self.has_value = has_value;
532        }
533        result
534    }
535
536    /// Signals the start of an object to the formatter.
537    fn begin_object(&mut self) -> Result<()> {
538        self.nesting_state
539            .push((self.first_element, self.has_value));
540        if !self.compact_objects() {
541            self.current_indent += 1;
542        }
543        self.has_value = false;
544        self.first_element = true;
545        self.write_bytes(b"{")
546    }
547
548    /// Signals the start of an object key to the formatter.
549    fn begin_object_key(&mut self) -> Result<()> {
550        if self.first_element {
551            self.first_element = false;
552            if self.compact_objects() {
553                self.write_bytes(b" ")?;
554            } else {
555                self.write_bytes(b"\n")?;
556                self.write_indent(self.current_indent)?;
557            }
558        } else if self.compact_objects() {
559            self.write_bytes(b", ")?;
560        } else {
561            self.write_bytes(b"\n")?;
562            self.write_indent(self.current_indent)?;
563        }
564
565        Ok(())
566    }
567
568    /// Signals the start of an object value to the formatter.
569    fn begin_object_value(&mut self) -> Result<()> {
570        self.write_bytes(b" = ")
571    }
572
573    /// Signals the end of an object value to the formatter.
574    fn end_object_value(&mut self) -> Result<()> {
575        self.end_array_value()
576    }
577
578    /// Signals the end of an object to the formatter.
579    fn end_object(&mut self) -> Result<()> {
580        if self.compact_objects() {
581            if self.has_value {
582                self.write_bytes(b" ")?;
583            }
584        } else {
585            self.current_indent -= 1;
586
587            if self.has_value {
588                self.write_bytes(b"\n")?;
589                self.write_indent(self.current_indent)?;
590            }
591        }
592
593        let result = self.write_bytes(b"}");
594        if let Some((first_element, has_value)) = self.nesting_state.pop() {
595            self.first_element = first_element;
596            self.has_value = has_value;
597        }
598        result
599    }
600
601    /// Signals the start of an attribute to the formatter.
602    fn begin_attribute(&mut self) -> Result<()> {
603        self.maybe_write_newline(FormatState::AttributeStart)?;
604        self.write_indent(self.current_indent)
605    }
606
607    /// Signals the start of an attribute value to the formatter.
608    fn begin_attribute_value(&mut self) -> Result<()> {
609        self.write_bytes(b" = ")
610    }
611
612    /// Signals the end of an attribute to the formatter.
613    fn end_attribute(&mut self) -> Result<()> {
614        self.state = FormatState::AttributeEnd;
615        self.write_bytes(b"\n")
616    }
617
618    /// Signals the start of a block to the formatter.
619    fn begin_block(&mut self) -> Result<()> {
620        self.maybe_write_newline(FormatState::BlockStart)?;
621        self.write_indent(self.current_indent)
622    }
623
624    /// Signals the start of a block body to the formatter.
625    fn begin_block_body(&mut self) -> Result<()> {
626        self.current_indent += 1;
627        self.state = FormatState::BlockBodyStart;
628        self.write_bytes(b" {")
629    }
630
631    /// Signals the end of a block to the formatter.
632    fn end_block(&mut self) -> Result<()> {
633        self.state = FormatState::BlockEnd;
634        self.current_indent -= 1;
635        self.write_indent(self.current_indent)?;
636        self.write_bytes(b"}\n")
637    }
638
639    // Conditionally writes a newline character depending on the formatter configuration and the
640    // current and next state. Updates the state to `next_state`.
641    fn maybe_write_newline(&mut self, next_state: FormatState) -> Result<()> {
642        let newline = match &self.state {
643            FormatState::AttributeEnd if !self.config.dense => {
644                matches!(next_state, FormatState::BlockStart)
645            }
646            FormatState::BlockEnd if !self.config.dense => {
647                matches!(
648                    next_state,
649                    FormatState::BlockStart | FormatState::AttributeStart
650                )
651            }
652            other => matches!(other, FormatState::BlockBodyStart),
653        };
654
655        if newline {
656            self.write_bytes(b"\n")?;
657        }
658
659        self.state = next_state;
660        Ok(())
661    }
662
663    fn write_indent(&mut self, n: usize) -> Result<()> {
664        for _ in 0..n {
665            self.write_bytes(self.config.indent)?;
666        }
667
668        Ok(())
669    }
670
671    fn write_indented(&mut self, n: usize, s: &str) -> Result<()> {
672        for (i, line) in s.lines().enumerate() {
673            if i > 0 {
674                self.write_bytes(b"\n")?;
675            }
676
677            self.write_indent(n)?;
678            self.write_string_fragment(line)?;
679        }
680
681        if s.ends_with('\n') {
682            self.write_bytes(b"\n")?;
683        }
684
685        Ok(())
686    }
687
688    fn write_bytes(&mut self, buf: &[u8]) -> Result<()> {
689        self.writer.write_all(buf)?;
690        Ok(())
691    }
692
693    /// Enables compact mode, runs the closure and disables compact mode again unless it's enabled
694    /// via another call to `with_compact_mode`.
695    ///
696    /// This is mostly used for serializing array and object function arguments.
697    fn with_compact_mode<F>(&mut self, f: F) -> Result<()>
698    where
699        F: FnOnce(&mut Self) -> Result<()>,
700    {
701        self.compact_mode_level += 1;
702        let result = f(self);
703        self.compact_mode_level -= 1;
704        result
705    }
706
707    fn compact_arrays(&self) -> bool {
708        self.config.compact_arrays || self.in_compact_mode()
709    }
710
711    fn compact_objects(&self) -> bool {
712        self.config.compact_objects || self.in_compact_mode()
713    }
714
715    fn in_compact_mode(&self) -> bool {
716        self.compact_mode_level > 0
717    }
718}
719
720/// Format the given value as an HCL byte vector.
721///
722/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
723/// [`hcl::to_vec`](crate::to_vec) instead.
724///
725/// # Errors
726///
727/// Formatting a value as byte vector cannot fail.
728pub fn to_vec<T>(value: &T) -> Result<Vec<u8>>
729where
730    T: ?Sized + Format,
731{
732    let mut formatter = Formatter::default();
733    value.format_vec(&mut formatter)
734}
735
736/// Format the given value as an HCL string.
737///
738/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
739/// [`hcl::to_string`](crate::to_string) instead.
740///
741/// # Errors
742///
743/// Formatting a value as string cannot fail.
744pub fn to_string<T>(value: &T) -> Result<String>
745where
746    T: ?Sized + Format,
747{
748    let mut formatter = Formatter::default();
749    value.format_string(&mut formatter)
750}
751
752/// Format the given value as HCL into the IO stream.
753///
754/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
755/// [`hcl::to_writer`](crate::to_writer) instead.
756///
757/// # Errors
758///
759/// Formatting fails if any operation on the writer fails.
760pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
761where
762    W: io::Write,
763    T: ?Sized + Format,
764{
765    let mut formatter = Formatter::new(writer);
766    value.format(&mut formatter)
767}
768
769/// Format the given value as an interpolated HCL string.
770///
771/// It is the callers responsiblity to ensure that the value is not an HCL structure (i.e. `Body`,
772/// `Structure`, `Block` or `Attribute`). Otherwise this will produce invalid HCL.
773///
774/// # Errors
775///
776/// Formatting a value as string cannot fail.
777pub(crate) fn to_interpolated_string<T>(value: &T) -> Result<String>
778where
779    T: ?Sized + Format,
780{
781    let mut formatter = Formatter::builder().compact(true).build_vec();
782    formatter.writer.extend([b'$', b'{']);
783    let mut string = value.format_string(&mut formatter)?;
784    string.push('}');
785    Ok(string)
786}
787
788#[cfg(test)]
789mod tests {
790    use super::to_interpolated_string;
791    use crate::expr::{BinaryOp, BinaryOperator, FuncCall};
792    use pretty_assertions::assert_eq;
793
794    #[test]
795    fn format_interpolated_string() {
796        let binop = BinaryOp::new(1, BinaryOperator::Plus, 1);
797        assert_eq!(to_interpolated_string(&binop).unwrap(), "${1 + 1}");
798
799        let expr = FuncCall::builder("add").arg(1).arg(1).build();
800        assert_eq!(to_interpolated_string(&expr).unwrap(), "${add(1, 1)}");
801    }
802}