#[cfg(test)]
mod test_tree_writer;
use std::fmt::{self, Write};
use super::{Element, Field, Value};
const TAB: &str = " ";
const ARRAY_OPEN: char = '[';
const ARRAY_CLOSE: char = ']';
const OBJECT_OPEN: char = '{';
const OBJECT_CLOSE: char = '}';
const COMMA: char = ',';
const NEWLINE: char = '\n';
pub struct Pretty<'a, 'buf> {
elem: &'a Element<'buf>,
}
impl<'a, 'buf> Pretty<'a, 'buf> {
pub fn new(elem: &'a Element<'buf>) -> Self {
Pretty { elem }
}
}
impl fmt::Display for Pretty<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut stack = vec![State::Root(self.elem)];
let mut write_comma = WriteComma::Skip;
loop {
let elem = loop {
let depth = stack.len();
let Some(mut state) = stack.pop() else {
return Ok(());
};
let elem = match &mut state {
State::Root(elem) => {
write_elem(elem, f)?;
elem
}
State::Array(iter) => {
let Some(elem) = iter.next() else {
let Some(depth) = depth.checked_sub(1) else {
return Ok(());
};
write_nl_and_indent(depth, f)?;
f.write_char(ARRAY_CLOSE)?;
continue;
};
if let WriteComma::Write = write_comma {
f.write_char(COMMA)?;
}
write_nl_and_indent(depth, f)?;
write_comma = write_elem(elem, f)?;
elem
}
State::Object(iter) => {
let Some(field) = iter.next() else {
let Some(depth) = depth.checked_sub(1) else {
return Ok(());
};
write_nl_and_indent(depth, f)?;
f.write_char(OBJECT_CLOSE)?;
continue;
};
if let WriteComma::Write = write_comma {
f.write_char(COMMA)?;
}
write_nl_and_indent(depth, f)?;
write_comma = write_field(field, f)?;
field.element()
}
};
match &state {
State::Array(_) | State::Object(_) => stack.push(state),
State::Root(_) => (),
}
break elem;
};
match elem.value() {
Value::Array(elements) => stack.push(State::Array(elements.iter())),
Value::Object(fields) => stack.push(State::Object(fields.iter())),
_ => (),
}
}
}
}
#[derive(Debug)]
enum State<'a, 'buf> {
Root(&'a Element<'buf>),
Array(std::slice::Iter<'a, Element<'buf>>),
Object(std::slice::Iter<'a, Field<'buf>>),
}
fn write_nl_and_indent(depth: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_char(NEWLINE)?;
for _ in 0..depth {
f.write_str(TAB)?;
}
Ok(())
}
#[derive(Copy, Clone)]
enum WriteComma {
Write,
Skip,
}
fn write_elem(elem: &Element<'_>, f: &mut fmt::Formatter<'_>) -> Result<WriteComma, fmt::Error> {
match elem.value() {
Value::Null => {
f.write_str("null")?;
Ok(WriteComma::Write)
}
Value::True => {
f.write_str("true")?;
Ok(WriteComma::Write)
}
Value::False => {
f.write_str("false")?;
Ok(WriteComma::Write)
}
Value::String(s) => {
write!(f, "\"{s}\"")?;
Ok(WriteComma::Write)
}
Value::Number(n) => {
write!(f, "{n}")?;
Ok(WriteComma::Write)
}
Value::Array(_) => {
f.write_char(ARRAY_OPEN)?;
Ok(WriteComma::Skip)
}
Value::Object(_) => {
f.write_char(OBJECT_OPEN)?;
Ok(WriteComma::Skip)
}
}
}
fn write_field(field: &Field<'_>, f: &mut fmt::Formatter<'_>) -> Result<WriteComma, fmt::Error> {
write!(f, "\"{}\": ", field.key())?;
write_elem(field.element(), f)
}