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}