mod escape;
mod impls;
use self::escape::{CharEscape, ESCAPE};
use crate::Result;
use hcl_primitives::template::escape_markers;
use std::io;
mod private {
pub trait Sealed {}
}
pub trait Format: private::Sealed {
fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
where
W: io::Write;
fn format_vec<W>(&self, fmt: &mut Formatter<W>) -> Result<Vec<u8>>
where
W: io::Write + AsMut<Vec<u8>>,
{
self.format(fmt)?;
Ok(fmt.writer.as_mut().split_off(0))
}
fn format_string<W>(&self, fmt: &mut Formatter<W>) -> Result<String>
where
W: io::Write + AsMut<Vec<u8>>,
{
let bytes = self.format_vec(fmt)?;
Ok(unsafe { String::from_utf8_unchecked(bytes) })
}
}
#[derive(PartialEq)]
enum FormatState {
Initial,
AttributeStart,
AttributeEnd,
BlockStart,
BlockEnd,
BlockBodyStart,
}
struct FormatConfig<'a> {
indent: &'a [u8],
dense: bool,
compact_arrays: bool,
compact_objects: bool,
prefer_ident_keys: bool,
}
impl Default for FormatConfig<'_> {
fn default() -> Self {
FormatConfig {
indent: b" ",
dense: false,
compact_arrays: false,
compact_objects: false,
prefer_ident_keys: false,
}
}
}
pub struct Formatter<'a, W> {
writer: W,
config: FormatConfig<'a>,
state: FormatState,
first_element: bool,
current_indent: usize,
has_value: bool,
compact_mode_level: u64,
nesting_state: Vec<(bool, bool)>,
}
pub struct FormatterBuilder<'a> {
config: FormatConfig<'a>,
}
impl<'a> FormatterBuilder<'a> {
pub fn indent(mut self, indent: &'a [u8]) -> Self {
self.config.indent = indent;
self
}
pub fn dense(mut self, yes: bool) -> Self {
self.config.dense = yes;
self
}
pub fn compact(self, yes: bool) -> Self {
self.compact_arrays(yes).compact_objects(yes)
}
pub fn compact_arrays(mut self, yes: bool) -> Self {
self.config.compact_arrays = yes;
self
}
pub fn compact_objects(mut self, yes: bool) -> Self {
self.config.compact_objects = yes;
self
}
pub fn prefer_ident_keys(mut self, yes: bool) -> Self {
self.config.prefer_ident_keys = yes;
self
}
pub fn build<W>(self, writer: W) -> Formatter<'a, W>
where
W: io::Write,
{
Formatter {
writer,
config: self.config,
state: FormatState::Initial,
first_element: false,
current_indent: 0,
has_value: false,
compact_mode_level: 0,
nesting_state: Vec::new(),
}
}
pub fn build_vec(self) -> Formatter<'a, Vec<u8>> {
let vec = Vec::with_capacity(128);
self.build(vec)
}
}
impl Default for Formatter<'_, Vec<u8>> {
fn default() -> Self {
Formatter::builder().build_vec()
}
}
impl<'a> Formatter<'a, ()> {
pub fn builder() -> FormatterBuilder<'a> {
FormatterBuilder {
config: FormatConfig::default(),
}
}
}
impl<'a, W> Formatter<'a, W>
where
W: io::Write,
{
pub fn new(writer: W) -> Formatter<'a, W> {
Formatter::builder().build(writer)
}
pub fn into_inner(self) -> W {
self.writer
}
}
impl<W> Formatter<'_, W>
where
W: io::Write,
{
fn write_null(&mut self) -> Result<()> {
self.write_bytes(b"null")
}
fn write_bool(&mut self, value: bool) -> Result<()> {
let s = if value {
b"true" as &[u8]
} else {
b"false" as &[u8]
};
self.write_bytes(s)
}
fn write_int<T>(&mut self, value: T) -> Result<()>
where
T: itoa::Integer,
{
let mut buffer = itoa::Buffer::new();
let s = buffer.format(value);
self.write_bytes(s.as_bytes())
}
fn write_quoted_string(&mut self, s: &str) -> Result<()> {
self.write_bytes(b"\"")?;
self.write_string_fragment(s)?;
self.write_bytes(b"\"")
}
fn write_quoted_string_escaped(&mut self, s: &str) -> Result<()> {
self.write_bytes(b"\"")?;
self.write_escaped_string(s)?;
self.write_bytes(b"\"")
}
fn write_string_fragment(&mut self, s: &str) -> Result<()> {
self.write_bytes(s.as_bytes())
}
fn write_escaped_string(&mut self, value: &str) -> Result<()> {
let value = escape_markers(value);
let bytes = value.as_bytes();
let mut start = 0;
for (i, &byte) in bytes.iter().enumerate() {
let escape = ESCAPE[byte as usize];
if escape == 0 {
continue;
}
if start < i {
self.write_string_fragment(&value[start..i])?;
}
let char_escape = CharEscape::from_escape_table(escape, byte);
char_escape.write_escaped(&mut self.writer)?;
start = i + 1;
}
if start != bytes.len() {
self.write_string_fragment(&value[start..])?;
}
Ok(())
}
fn begin_array(&mut self) -> Result<()> {
self.nesting_state
.push((self.first_element, self.has_value));
if !self.compact_arrays() {
self.current_indent += 1;
}
self.has_value = false;
self.first_element = true;
self.write_bytes(b"[")
}
fn begin_array_value(&mut self) -> Result<()> {
if self.first_element {
self.first_element = false;
if !self.compact_arrays() {
self.write_bytes(b"\n")?;
self.write_indent(self.current_indent)?;
}
} else if self.compact_arrays() {
self.write_bytes(b", ")?;
} else {
self.write_bytes(b",\n")?;
self.write_indent(self.current_indent)?;
}
Ok(())
}
fn end_array_value(&mut self) -> Result<()> {
self.has_value = true;
Ok(())
}
fn end_array(&mut self) -> Result<()> {
if !self.compact_arrays() {
self.current_indent -= 1;
if self.has_value {
self.write_bytes(b"\n")?;
self.write_indent(self.current_indent)?;
}
}
let result = self.write_bytes(b"]");
if let Some((first_element, has_value)) = self.nesting_state.pop() {
self.first_element = first_element;
self.has_value = has_value;
}
result
}
fn begin_object(&mut self) -> Result<()> {
self.nesting_state
.push((self.first_element, self.has_value));
if !self.compact_objects() {
self.current_indent += 1;
}
self.has_value = false;
self.first_element = true;
self.write_bytes(b"{")
}
fn begin_object_key(&mut self) -> Result<()> {
if self.first_element {
self.first_element = false;
if self.compact_objects() {
self.write_bytes(b" ")?;
} else {
self.write_bytes(b"\n")?;
self.write_indent(self.current_indent)?;
}
} else if self.compact_objects() {
self.write_bytes(b", ")?;
} else {
self.write_bytes(b"\n")?;
self.write_indent(self.current_indent)?;
}
Ok(())
}
fn begin_object_value(&mut self) -> Result<()> {
self.write_bytes(b" = ")
}
fn end_object_value(&mut self) -> Result<()> {
self.end_array_value()
}
fn end_object(&mut self) -> Result<()> {
if self.compact_objects() {
if self.has_value {
self.write_bytes(b" ")?;
}
} else {
self.current_indent -= 1;
if self.has_value {
self.write_bytes(b"\n")?;
self.write_indent(self.current_indent)?;
}
}
let result = self.write_bytes(b"}");
if let Some((first_element, has_value)) = self.nesting_state.pop() {
self.first_element = first_element;
self.has_value = has_value;
}
result
}
fn begin_attribute(&mut self) -> Result<()> {
self.maybe_write_newline(FormatState::AttributeStart)?;
self.write_indent(self.current_indent)
}
fn begin_attribute_value(&mut self) -> Result<()> {
self.write_bytes(b" = ")
}
fn end_attribute(&mut self) -> Result<()> {
self.state = FormatState::AttributeEnd;
self.write_bytes(b"\n")
}
fn begin_block(&mut self) -> Result<()> {
self.maybe_write_newline(FormatState::BlockStart)?;
self.write_indent(self.current_indent)
}
fn begin_block_body(&mut self) -> Result<()> {
self.current_indent += 1;
self.state = FormatState::BlockBodyStart;
self.write_bytes(b" {")
}
fn end_block(&mut self) -> Result<()> {
self.state = FormatState::BlockEnd;
self.current_indent -= 1;
self.write_indent(self.current_indent)?;
self.write_bytes(b"}\n")
}
fn maybe_write_newline(&mut self, next_state: FormatState) -> Result<()> {
let newline = match &self.state {
FormatState::AttributeEnd if !self.config.dense => {
matches!(next_state, FormatState::BlockStart)
}
FormatState::BlockEnd if !self.config.dense => {
matches!(
next_state,
FormatState::BlockStart | FormatState::AttributeStart
)
}
other => matches!(other, FormatState::BlockBodyStart),
};
if newline {
self.write_bytes(b"\n")?;
}
self.state = next_state;
Ok(())
}
fn write_indent(&mut self, n: usize) -> Result<()> {
for _ in 0..n {
self.write_bytes(self.config.indent)?;
}
Ok(())
}
fn write_indented(&mut self, n: usize, s: &str) -> Result<()> {
for (i, line) in s.lines().enumerate() {
if i > 0 {
self.write_bytes(b"\n")?;
}
self.write_indent(n)?;
self.write_string_fragment(line)?;
}
if s.ends_with('\n') {
self.write_bytes(b"\n")?;
}
Ok(())
}
fn write_bytes(&mut self, buf: &[u8]) -> Result<()> {
self.writer.write_all(buf)?;
Ok(())
}
fn with_compact_mode<F>(&mut self, f: F) -> Result<()>
where
F: FnOnce(&mut Self) -> Result<()>,
{
self.compact_mode_level += 1;
let result = f(self);
self.compact_mode_level -= 1;
result
}
fn compact_arrays(&self) -> bool {
self.config.compact_arrays || self.in_compact_mode()
}
fn compact_objects(&self) -> bool {
self.config.compact_objects || self.in_compact_mode()
}
fn in_compact_mode(&self) -> bool {
self.compact_mode_level > 0
}
}
pub fn to_vec<T>(value: &T) -> Result<Vec<u8>>
where
T: ?Sized + Format,
{
let mut formatter = Formatter::default();
value.format_vec(&mut formatter)
}
pub fn to_string<T>(value: &T) -> Result<String>
where
T: ?Sized + Format,
{
let mut formatter = Formatter::default();
value.format_string(&mut formatter)
}
pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
where
W: io::Write,
T: ?Sized + Format,
{
let mut formatter = Formatter::new(writer);
value.format(&mut formatter)
}
pub(crate) fn to_interpolated_string<T>(value: &T) -> Result<String>
where
T: ?Sized + Format,
{
let mut formatter = Formatter::builder().compact(true).build_vec();
formatter.writer.extend([b'$', b'{']);
let mut string = value.format_string(&mut formatter)?;
string.push('}');
Ok(string)
}
#[cfg(test)]
mod tests {
use super::to_interpolated_string;
use crate::expr::{BinaryOp, BinaryOperator, FuncCall};
use pretty_assertions::assert_eq;
#[test]
fn format_interpolated_string() {
let binop = BinaryOp::new(1, BinaryOperator::Plus, 1);
assert_eq!(to_interpolated_string(&binop).unwrap(), "${1 + 1}");
let expr = FuncCall::builder("add").arg(1).arg(1).build();
assert_eq!(to_interpolated_string(&expr).unwrap(), "${add(1, 1)}");
}
}