hcl-edit 0.4.3

Parse and modify HCL while preserving comments and whitespace
Documentation
use super::{
    encode_escaped, Encode, EncodeDecorated, EncodeState, BOTH_SPACE_DECOR, LEADING_SPACE_DECOR,
    TRAILING_SPACE_DECOR,
};
use crate::template::{
    Directive, Element, ElseTemplateExpr, EndforTemplateExpr, EndifTemplateExpr, ForDirective,
    ForTemplateExpr, HeredocTemplate, IfDirective, IfTemplateExpr, Interpolation, StringTemplate,
    Strip, Template,
};
use crate::util::indent_by;
use std::fmt::{self, Write};

const INTERPOLATION_START: &str = "${";
const DIRECTIVE_START: &str = "%{";

impl Encode for StringTemplate {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        buf.write_char('"')?;
        buf.escaped(|buf| {
            for element in self.iter() {
                element.encode(buf)?;
            }

            Ok(())
        })?;
        buf.write_char('"')
    }
}

impl Encode for HeredocTemplate {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        buf.write_str("<<")?;

        if self.indent().is_some() {
            buf.write_char('-')?;
        }

        writeln!(buf, "{}", self.delimiter.as_str())?;

        match self.indent() {
            Some(n) => {
                let mut indent_buf = String::new();
                let mut indent_state = EncodeState::new(&mut indent_buf);
                self.template.encode(&mut indent_state)?;
                let indented = indent_by(&indent_buf, n, false);
                buf.write_str(&indented)?;
            }
            None => self.template.encode(buf)?,
        }

        self.trailing().encode_with_default(buf, "")?;

        write!(buf, "{}", self.delimiter.as_str())
    }
}

impl Encode for Template {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        for element in self.iter() {
            element.encode(buf)?;
        }

        Ok(())
    }
}

impl Encode for Element {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        match self {
            Element::Literal(lit) => {
                if buf.escape() {
                    encode_escaped(buf, lit)
                } else {
                    buf.write_str(lit)
                }
            }
            Element::Interpolation(interp) => interp.encode(buf),
            Element::Directive(dir) => dir.encode(buf),
        }
    }
}

impl Encode for Interpolation {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        encode_strip(buf, INTERPOLATION_START, self.strip, |buf| {
            self.expr.encode_decorated(buf, BOTH_SPACE_DECOR)
        })
    }
}

impl Encode for Directive {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        match self {
            Directive::If(dir) => dir.encode(buf),
            Directive::For(dir) => dir.encode(buf),
        }
    }
}

impl Encode for IfDirective {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        self.if_expr.encode(buf)?;
        if let Some(else_expr) = &self.else_expr {
            else_expr.encode(buf)?;
        }
        self.endif_expr.encode(buf)
    }
}

impl Encode for IfTemplateExpr {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        encode_strip(buf, DIRECTIVE_START, self.strip, |buf| {
            self.preamble().encode_with_default(buf, " ")?;
            buf.write_str("if")?;
            self.cond_expr.encode_decorated(buf, TRAILING_SPACE_DECOR)
        })?;
        self.template.encode(buf)
    }
}

impl Encode for ElseTemplateExpr {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        encode_strip(buf, DIRECTIVE_START, self.strip, |buf| {
            self.preamble().encode_with_default(buf, " ")?;
            buf.write_str("else")?;
            self.trailing().encode_with_default(buf, " ")
        })?;
        self.template.encode(buf)
    }
}

impl Encode for EndifTemplateExpr {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        encode_strip(buf, DIRECTIVE_START, self.strip, |buf| {
            self.preamble().encode_with_default(buf, " ")?;
            buf.write_str("endif")?;
            self.trailing().encode_with_default(buf, " ")
        })
    }
}

impl Encode for ForDirective {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        self.for_expr.encode(buf)?;
        self.endfor_expr.encode(buf)
    }
}

impl Encode for ForTemplateExpr {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        encode_strip(buf, DIRECTIVE_START, self.strip, |buf| {
            self.preamble().encode_with_default(buf, " ")?;
            buf.write_str("for")?;

            if let Some(key_var) = &self.key_var {
                key_var.encode_decorated(buf, LEADING_SPACE_DECOR)?;
                buf.write_char(',')?;
            }

            self.value_var.encode_decorated(buf, BOTH_SPACE_DECOR)?;
            buf.write_str("in")?;
            self.collection_expr.encode_decorated(buf, BOTH_SPACE_DECOR)
        })?;
        self.template.encode(buf)
    }
}

impl Encode for EndforTemplateExpr {
    fn encode(&self, buf: &mut EncodeState) -> fmt::Result {
        encode_strip(buf, DIRECTIVE_START, self.strip, |buf| {
            self.preamble().encode_with_default(buf, " ")?;
            buf.write_str("endfor")?;
            self.trailing().encode_with_default(buf, " ")
        })
    }
}

fn encode_strip<F>(buf: &mut EncodeState, start_marker: &str, strip: Strip, f: F) -> fmt::Result
where
    F: FnOnce(&mut EncodeState) -> fmt::Result,
{
    buf.write_str(start_marker)?;
    if strip.strip_start() {
        buf.write_char('~')?;
    }

    f(buf)?;

    if strip.strip_end() {
        buf.write_char('~')?;
    }

    buf.write_char('}')
}