hcl-edit 0.4.3

Parse and modify HCL while preserving comments and whitespace
Documentation
mod expr;
mod structure;
mod template;

use crate::repr::{Decorate, Decorated, Formatted};
use crate::{Ident, Number};
use std::fmt::{self, Write};

pub(crate) const NO_DECOR: (&str, &str) = ("", "");
const LEADING_SPACE_DECOR: (&str, &str) = (" ", "");
const TRAILING_SPACE_DECOR: (&str, &str) = ("", " ");
const BOTH_SPACE_DECOR: (&str, &str) = (" ", " ");

pub(crate) trait EncodeDecorated {
    fn encode_decorated(&self, buf: &mut EncodeState, default_decor: (&str, &str)) -> fmt::Result;
}

pub(crate) trait Encode {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result;
}

pub(crate) struct EncodeState<'a> {
    buf: &'a mut dyn fmt::Write,
    escape: bool,
}

impl<'a> EncodeState<'a> {
    pub fn new(buf: &'a mut dyn fmt::Write) -> EncodeState<'a> {
        EncodeState { buf, escape: false }
    }

    pub fn escaped<F>(&mut self, f: F) -> fmt::Result
    where
        F: FnOnce(&mut EncodeState) -> fmt::Result,
    {
        self.escape = true;
        let result = f(self);
        self.escape = false;
        result
    }

    pub fn escape(&self) -> bool {
        self.escape
    }
}

impl<'a> fmt::Write for EncodeState<'a> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.buf.write_str(s)
    }
}

impl<T> EncodeDecorated for T
where
    T: Decorate + Encode,
{
    fn encode_decorated(&self, buf: &mut EncodeState, default_decor: (&str, &str)) -> fmt::Result {
        encode_decorated(self, buf, default_decor, |buf| self.encode(buf))
    }
}

impl<T> Encode for Decorated<T>
where
    T: Encode,
{
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        (**self).encode(buf)
    }
}

impl<T> Encode for Formatted<T>
where
    T: Encode + ToString,
{
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        if let Some(repr) = self.as_repr() {
            repr.encode_with_default(buf, "")
        } else {
            (**self).encode(buf)
        }
    }
}

impl Encode for bool {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        write!(buf, "{self}")
    }
}

impl Encode for u64 {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        write!(buf, "{self}")
    }
}

impl Encode for Number {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        write!(buf, "{self}")
    }
}

impl Encode for Ident {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        buf.write_str(self.as_str())
    }
}

fn encode_decorated<T, F>(
    item: &T,
    buf: &mut EncodeState,
    default_decor: (&str, &str),
    f: F,
) -> fmt::Result
where
    T: ?Sized + Decorate,
    F: FnOnce(&mut EncodeState) -> fmt::Result,
{
    let decor = item.decor();
    decor.encode_prefix(buf, default_decor.0)?;
    f(buf)?;
    decor.encode_suffix(buf, default_decor.1)
}

fn encode_quoted_string(buf: &mut dyn fmt::Write, value: &str) -> fmt::Result {
    buf.write_char('"')?;
    encode_escaped(buf, value)?;
    buf.write_char('"')
}

fn encode_escaped(buf: &mut dyn fmt::Write, value: &str) -> fmt::Result {
    for ch in value.chars() {
        match ch {
            '\u{8}' => buf.write_str("\\b")?,
            '\u{9}' => buf.write_str("\\t")?,
            '\u{a}' => buf.write_str("\\n")?,
            '\u{c}' => buf.write_str("\\f")?,
            '\u{d}' => buf.write_str("\\r")?,
            '\u{22}' => buf.write_str("\\\"")?,
            '\u{5c}' => buf.write_str("\\\\")?,
            c if c <= '\u{1f}' || c == '\u{7f}' => {
                write!(buf, "\\u{:04X}", ch as u32)?;
            }
            ch => buf.write_char(ch)?,
        }
    }

    Ok(())
}