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}
184
185/// A builder to create a `Formatter`.
186///
187/// See the documentation of [`Formatter`] for a usage example.
188pub struct FormatterBuilder<'a> {
189 config: FormatConfig<'a>,
190}
191
192impl<'a> FormatterBuilder<'a> {
193 /// Set the indent for indenting nested HCL structures.
194 ///
195 /// The default indentation is two spaces.
196 pub fn indent(mut self, indent: &'a [u8]) -> Self {
197 self.config.indent = indent;
198 self
199 }
200
201 /// If set, blocks are not visually separated by empty lines from attributes and adjacent
202 /// blocks.
203 ///
204 /// Default formatting:
205 ///
206 /// ```hcl
207 /// attr1 = "value1"
208 /// attr2 = "value2"
209 ///
210 /// block1 {}
211 ///
212 /// block2 {}
213 /// ```
214 ///
215 /// Dense formatting:
216 ///
217 /// ```hcl
218 /// attr1 = "value1"
219 /// attr2 = "value2"
220 /// block1 {}
221 /// block2 {}
222 /// ```
223 pub fn dense(mut self, yes: bool) -> Self {
224 self.config.dense = yes;
225 self
226 }
227
228 /// If set, arrays and objects are formatted in a more compact way.
229 ///
230 /// See the method documation of [`compact_arrays`][FormatterBuilder::compact_arrays] and
231 /// [`compact_objects`][FormatterBuilder::compact_objects].
232 pub fn compact(self, yes: bool) -> Self {
233 self.compact_arrays(yes).compact_objects(yes)
234 }
235
236 /// Controls the array formatting.
237 ///
238 /// By default, array elements are separated by newlines:
239 ///
240 /// ```hcl
241 /// array = [
242 /// 1,
243 /// 2,
244 /// 3,
245 /// ]
246 /// ```
247 ///
248 /// When compact array formatting is enabled no newlines are inserted between elements:
249 ///
250 /// ```hcl
251 /// array = [1, 2, 3]
252 /// ```
253 pub fn compact_arrays(mut self, yes: bool) -> Self {
254 self.config.compact_arrays = yes;
255 self
256 }
257
258 /// Controls the object formatting.
259 ///
260 /// By default, object items are separated by newlines:
261 ///
262 /// ```hcl
263 /// object = {
264 /// one = "foo"
265 /// two = "bar"
266 /// three = "baz"
267 /// }
268 /// ```
269 ///
270 /// When compact object formatting is enabled no newlines are inserted between items:
271 ///
272 /// ```hcl
273 /// object = { one = "foo", two = "bar", three = "baz" }
274 /// ```
275 pub fn compact_objects(mut self, yes: bool) -> Self {
276 self.config.compact_objects = yes;
277 self
278 }
279
280 /// Controls the object key quoting.
281 ///
282 /// By default, object keys are formatted as quoted strings (unless they are of variant
283 /// [`ObjectKey::Identifier`][ident-variant]).
284 ///
285 /// ```hcl
286 /// object = {
287 /// "foo" = 1
288 /// "bar baz" = 2
289 /// }
290 /// ```
291 ///
292 /// When identifier keys are preferred, object keys that are also valid HCL identifiers are
293 /// not quoted:
294 ///
295 /// ```hcl
296 /// object = {
297 /// foo = 1
298 /// "bar baz" = 2
299 /// }
300 /// ```
301 ///
302 /// [ident-variant]: crate::expr::ObjectKey::Identifier
303 pub fn prefer_ident_keys(mut self, yes: bool) -> Self {
304 self.config.prefer_ident_keys = yes;
305 self
306 }
307
308 /// Consumes the `FormatterBuilder` and turns it into a `Formatter` which writes HCL to the
309 /// provided writer.
310 pub fn build<W>(self, writer: W) -> Formatter<'a, W>
311 where
312 W: io::Write,
313 {
314 Formatter {
315 writer,
316 config: self.config,
317 state: FormatState::Initial,
318 first_element: false,
319 current_indent: 0,
320 has_value: false,
321 compact_mode_level: 0,
322 }
323 }
324
325 /// Consumes the `FormatterBuilder` and turns it into a `Formatter` which is specialized to use
326 /// a pre-allocated `Vec<u8>` as internal buffer.
327 ///
328 /// The returned formatter can be passed to the [`format_string`][Format::format_string] or
329 /// [`format_vec`][Format::format_vec] method of types implementing [`Format`].
330 ///
331 /// Alternatively, the internal buffer can be obtained by calling
332 /// [`into_inner`][Formatter::into_inner] on the returned `Formatter` after passing it to the
333 /// [`format`][Format::format] method of a type implementing [`Format`].
334 ///
335 /// # Examples
336 ///
337 /// ```
338 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
339 /// use hcl::format::{Format, Formatter};
340 /// use hcl::structure::Attribute;
341 ///
342 /// let mut formatter = Formatter::builder()
343 /// .compact_arrays(true)
344 /// .build_vec();
345 ///
346 /// let attr = Attribute::new("foo", vec![1, 2, 3]);
347 ///
348 /// assert_eq!(attr.format_string(&mut formatter)?, "foo = [1, 2, 3]\n");
349 /// # Ok(())
350 /// # }
351 /// ```
352 pub fn build_vec(self) -> Formatter<'a, Vec<u8>> {
353 let vec = Vec::with_capacity(128);
354 self.build(vec)
355 }
356}
357
358impl Default for Formatter<'_, Vec<u8>> {
359 /// Creates the default `Formatter` which is specialized to use a pre-allocated `Vec<u8>` as
360 /// internal buffer.
361 ///
362 /// The formatter can be passed to the [`format_string`][Format::format_string] or
363 /// [`format_vec`][Format::format_vec] method of types implementing [`Format`].
364 ///
365 /// Alternatively, the internal buffer can be obtained by calling
366 /// [`into_inner`][Formatter::into_inner] after passing it to the [`format`][Format::format]
367 /// method of a type implementing [`Format`].
368 fn default() -> Self {
369 Formatter::builder().build_vec()
370 }
371}
372
373// Public API.
374impl<'a> Formatter<'a, ()> {
375 /// Creates a new [`FormatterBuilder`] to start building a new `Formatter`.
376 pub fn builder() -> FormatterBuilder<'a> {
377 FormatterBuilder {
378 config: FormatConfig::default(),
379 }
380 }
381}
382
383// Public API.
384impl<'a, W> Formatter<'a, W>
385where
386 W: io::Write,
387{
388 /// Creates a new `Formatter` which writes HCL to the provided writer.
389 pub fn new(writer: W) -> Formatter<'a, W> {
390 Formatter::builder().build(writer)
391 }
392
393 /// Takes ownership of the `Formatter` and returns the underlying writer.
394 pub fn into_inner(self) -> W {
395 self.writer
396 }
397}
398
399// Internal formatter API.
400impl<W> Formatter<'_, W>
401where
402 W: io::Write,
403{
404 /// Writes `null` to the writer.
405 fn write_null(&mut self) -> Result<()> {
406 self.write_bytes(b"null")
407 }
408
409 /// Writes a boolean value to the writer.
410 fn write_bool(&mut self, value: bool) -> Result<()> {
411 let s = if value {
412 b"true" as &[u8]
413 } else {
414 b"false" as &[u8]
415 };
416 self.write_bytes(s)
417 }
418
419 /// Writes an integer value to the writer.
420 fn write_int<T>(&mut self, value: T) -> Result<()>
421 where
422 T: itoa::Integer,
423 {
424 let mut buffer = itoa::Buffer::new();
425 let s = buffer.format(value);
426 self.write_bytes(s.as_bytes())
427 }
428
429 /// Writes a quoted string to the writer.
430 fn write_quoted_string(&mut self, s: &str) -> Result<()> {
431 self.write_bytes(b"\"")?;
432 self.write_string_fragment(s)?;
433 self.write_bytes(b"\"")
434 }
435
436 /// Writes a quoted string to the writer after escaping it.
437 fn write_quoted_string_escaped(&mut self, s: &str) -> Result<()> {
438 self.write_bytes(b"\"")?;
439 self.write_escaped_string(s)?;
440 self.write_bytes(b"\"")
441 }
442
443 /// Writes a string fragment to the writer. No escaping occurs.
444 fn write_string_fragment(&mut self, s: &str) -> Result<()> {
445 self.write_bytes(s.as_bytes())
446 }
447
448 /// Writes a string to the writer and escapes control characters and quotes that might be
449 /// contained in it.
450 fn write_escaped_string(&mut self, value: &str) -> Result<()> {
451 let value = escape_markers(value);
452 let bytes = value.as_bytes();
453
454 let mut start = 0;
455
456 for (i, &byte) in bytes.iter().enumerate() {
457 let escape = ESCAPE[byte as usize];
458 if escape == 0 {
459 continue;
460 }
461
462 if start < i {
463 self.write_string_fragment(&value[start..i])?;
464 }
465
466 let char_escape = CharEscape::from_escape_table(escape, byte);
467 char_escape.write_escaped(&mut self.writer)?;
468
469 start = i + 1;
470 }
471
472 if start != bytes.len() {
473 self.write_string_fragment(&value[start..])?;
474 }
475
476 Ok(())
477 }
478
479 /// Signals the start of an array to the formatter.
480 fn begin_array(&mut self) -> Result<()> {
481 if !self.compact_arrays() {
482 self.current_indent += 1;
483 }
484 self.has_value = false;
485 self.first_element = true;
486 self.write_bytes(b"[")
487 }
488
489 /// Signals the start of an array value to the formatter.
490 fn begin_array_value(&mut self) -> Result<()> {
491 if self.first_element {
492 self.first_element = false;
493 if !self.compact_arrays() {
494 self.write_bytes(b"\n")?;
495 self.write_indent(self.current_indent)?;
496 }
497 } else if self.compact_arrays() {
498 self.write_bytes(b", ")?;
499 } else {
500 self.write_bytes(b",\n")?;
501 self.write_indent(self.current_indent)?;
502 }
503
504 Ok(())
505 }
506
507 /// Signals the end of an array value to the formatter.
508 fn end_array_value(&mut self) -> Result<()> {
509 self.has_value = true;
510 Ok(())
511 }
512
513 /// Signals the end of an array to the formatter.
514 fn end_array(&mut self) -> Result<()> {
515 if !self.compact_arrays() {
516 self.current_indent -= 1;
517
518 if self.has_value {
519 self.write_bytes(b"\n")?;
520 self.write_indent(self.current_indent)?;
521 }
522 }
523
524 self.write_bytes(b"]")
525 }
526
527 /// Signals the start of an object to the formatter.
528 fn begin_object(&mut self) -> Result<()> {
529 if !self.compact_objects() {
530 self.current_indent += 1;
531 }
532 self.has_value = false;
533 self.first_element = true;
534 self.write_bytes(b"{")
535 }
536
537 /// Signals the start of an object key to the formatter.
538 fn begin_object_key(&mut self) -> Result<()> {
539 if self.first_element {
540 self.first_element = false;
541 if self.compact_objects() {
542 self.write_bytes(b" ")?;
543 } else {
544 self.write_bytes(b"\n")?;
545 self.write_indent(self.current_indent)?;
546 }
547 } else if self.compact_objects() {
548 self.write_bytes(b", ")?;
549 } else {
550 self.write_bytes(b"\n")?;
551 self.write_indent(self.current_indent)?;
552 }
553
554 Ok(())
555 }
556
557 /// Signals the start of an object value to the formatter.
558 fn begin_object_value(&mut self) -> Result<()> {
559 self.write_bytes(b" = ")
560 }
561
562 /// Signals the end of an object value to the formatter.
563 fn end_object_value(&mut self) -> Result<()> {
564 self.end_array_value()
565 }
566
567 /// Signals the end of an object to the formatter.
568 fn end_object(&mut self) -> Result<()> {
569 if self.compact_objects() {
570 if self.has_value {
571 self.write_bytes(b" ")?;
572 }
573 } else {
574 self.current_indent -= 1;
575
576 if self.has_value {
577 self.write_bytes(b"\n")?;
578 self.write_indent(self.current_indent)?;
579 }
580 }
581
582 self.write_bytes(b"}")
583 }
584
585 /// Signals the start of an attribute to the formatter.
586 fn begin_attribute(&mut self) -> Result<()> {
587 self.maybe_write_newline(FormatState::AttributeStart)?;
588 self.write_indent(self.current_indent)
589 }
590
591 /// Signals the start of an attribute value to the formatter.
592 fn begin_attribute_value(&mut self) -> Result<()> {
593 self.write_bytes(b" = ")
594 }
595
596 /// Signals the end of an attribute to the formatter.
597 fn end_attribute(&mut self) -> Result<()> {
598 self.state = FormatState::AttributeEnd;
599 self.write_bytes(b"\n")
600 }
601
602 /// Signals the start of a block to the formatter.
603 fn begin_block(&mut self) -> Result<()> {
604 self.maybe_write_newline(FormatState::BlockStart)?;
605 self.write_indent(self.current_indent)
606 }
607
608 /// Signals the start of a block body to the formatter.
609 fn begin_block_body(&mut self) -> Result<()> {
610 self.current_indent += 1;
611 self.state = FormatState::BlockBodyStart;
612 self.write_bytes(b" {")
613 }
614
615 /// Signals the end of a block to the formatter.
616 fn end_block(&mut self) -> Result<()> {
617 self.state = FormatState::BlockEnd;
618 self.current_indent -= 1;
619 self.write_indent(self.current_indent)?;
620 self.write_bytes(b"}\n")
621 }
622
623 // Conditionally writes a newline character depending on the formatter configuration and the
624 // current and next state. Updates the state to `next_state`.
625 fn maybe_write_newline(&mut self, next_state: FormatState) -> Result<()> {
626 let newline = match &self.state {
627 FormatState::AttributeEnd if !self.config.dense => {
628 matches!(next_state, FormatState::BlockStart)
629 }
630 FormatState::BlockEnd if !self.config.dense => {
631 matches!(
632 next_state,
633 FormatState::BlockStart | FormatState::AttributeStart
634 )
635 }
636 other => matches!(other, FormatState::BlockBodyStart),
637 };
638
639 if newline {
640 self.write_bytes(b"\n")?;
641 }
642
643 self.state = next_state;
644 Ok(())
645 }
646
647 fn write_indent(&mut self, n: usize) -> Result<()> {
648 for _ in 0..n {
649 self.write_bytes(self.config.indent)?;
650 }
651
652 Ok(())
653 }
654
655 fn write_indented(&mut self, n: usize, s: &str) -> Result<()> {
656 for (i, line) in s.lines().enumerate() {
657 if i > 0 {
658 self.write_bytes(b"\n")?;
659 }
660
661 self.write_indent(n)?;
662 self.write_string_fragment(line)?;
663 }
664
665 if s.ends_with('\n') {
666 self.write_bytes(b"\n")?;
667 }
668
669 Ok(())
670 }
671
672 fn write_bytes(&mut self, buf: &[u8]) -> Result<()> {
673 self.writer.write_all(buf)?;
674 Ok(())
675 }
676
677 /// Enables compact mode, runs the closure and disables compact mode again unless it's enabled
678 /// via another call to `with_compact_mode`.
679 ///
680 /// This is mostly used for serializing array and object function arguments.
681 fn with_compact_mode<F>(&mut self, f: F) -> Result<()>
682 where
683 F: FnOnce(&mut Self) -> Result<()>,
684 {
685 self.compact_mode_level += 1;
686 let result = f(self);
687 self.compact_mode_level -= 1;
688 result
689 }
690
691 fn compact_arrays(&self) -> bool {
692 self.config.compact_arrays || self.in_compact_mode()
693 }
694
695 fn compact_objects(&self) -> bool {
696 self.config.compact_objects || self.in_compact_mode()
697 }
698
699 fn in_compact_mode(&self) -> bool {
700 self.compact_mode_level > 0
701 }
702}
703
704/// Format the given value as an HCL byte vector.
705///
706/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
707/// [`hcl::to_vec`](crate::to_vec) instead.
708///
709/// # Errors
710///
711/// Formatting a value as byte vector cannot fail.
712pub fn to_vec<T>(value: &T) -> Result<Vec<u8>>
713where
714 T: ?Sized + Format,
715{
716 let mut formatter = Formatter::default();
717 value.format_vec(&mut formatter)
718}
719
720/// Format the given value as an HCL string.
721///
722/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
723/// [`hcl::to_string`](crate::to_string) instead.
724///
725/// # Errors
726///
727/// Formatting a value as string cannot fail.
728pub fn to_string<T>(value: &T) -> Result<String>
729where
730 T: ?Sized + Format,
731{
732 let mut formatter = Formatter::default();
733 value.format_string(&mut formatter)
734}
735
736/// Format the given value as HCL into the IO stream.
737///
738/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
739/// [`hcl::to_writer`](crate::to_writer) instead.
740///
741/// # Errors
742///
743/// Formatting fails if any operation on the writer fails.
744pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
745where
746 W: io::Write,
747 T: ?Sized + Format,
748{
749 let mut formatter = Formatter::new(writer);
750 value.format(&mut formatter)
751}
752
753/// Format the given value as an interpolated HCL string.
754///
755/// It is the callers responsiblity to ensure that the value is not an HCL structure (i.e. `Body`,
756/// `Structure`, `Block` or `Attribute`). Otherwise this will produce invalid HCL.
757///
758/// # Errors
759///
760/// Formatting a value as string cannot fail.
761pub(crate) fn to_interpolated_string<T>(value: &T) -> Result<String>
762where
763 T: ?Sized + Format,
764{
765 let mut formatter = Formatter::builder().compact(true).build_vec();
766 formatter.writer.extend([b'$', b'{']);
767 let mut string = value.format_string(&mut formatter)?;
768 string.push('}');
769 Ok(string)
770}
771
772#[cfg(test)]
773mod tests {
774 use super::to_interpolated_string;
775 use crate::expr::{BinaryOp, BinaryOperator, FuncCall};
776 use pretty_assertions::assert_eq;
777
778 #[test]
779 fn format_interpolated_string() {
780 let binop = BinaryOp::new(1, BinaryOperator::Plus, 1);
781 assert_eq!(to_interpolated_string(&binop).unwrap(), "${1 + 1}");
782
783 let expr = FuncCall::builder("add").arg(1).arg(1).build();
784 assert_eq!(to_interpolated_string(&expr).unwrap(), "${add(1, 1)}");
785 }
786}