use super::float::push_float;
use super::integer::push_int;
use super::open::Open;
use super::string::{push_string, write_body};
use crate::model::value::Value;
const INDENT: &[u8] = b" ";
#[derive(Debug)]
pub struct Writer {
out: Vec<u8>,
pretty: bool,
open: Vec<Open>,
}
impl Default for Writer {
fn default() -> Self {
Self::new()
}
}
impl Writer {
#[must_use]
pub fn new() -> Self {
Self::with_pretty(false)
}
#[must_use]
pub fn pretty() -> Self {
Self::with_pretty(true)
}
fn with_pretty(pretty: bool) -> Self {
Self {
out: Vec::with_capacity(256),
pretty,
open: Vec::new(),
}
}
pub fn null(&mut self) -> &mut Self {
self.begin();
self.out.extend_from_slice(b"null");
self.end()
}
pub fn bool(&mut self, b: bool) -> &mut Self {
self.begin();
self.out
.extend_from_slice(if b { b"true" } else { b"false" });
self.end()
}
pub fn int(&mut self, n: i64) -> &mut Self {
self.begin();
push_int(n, &mut self.out);
self.end()
}
pub fn float(&mut self, f: f64) -> &mut Self {
self.begin();
push_float(f, &mut self.out);
self.end()
}
pub fn str(&mut self, s: &str) -> &mut Self {
self.begin();
push_string(s, &mut self.out);
self.end()
}
pub fn reference(&mut self, kind: &str, id: &str) -> &mut Self {
self.begin();
self.out.extend_from_slice(b"\"@");
write_body(kind, &mut self.out);
self.out.push(b':');
write_body(id, &mut self.out);
self.out.push(b'"');
self.end()
}
pub fn bytes(&mut self, bytes: &[u8]) -> &mut Self {
use base64::Engine as _;
self.begin();
self.out.extend_from_slice(b"\"@");
self.out.extend_from_slice(crate::typed::BYTES.as_bytes());
self.out.push(b':');
let at = self.out.len();
self.out
.resize(at + base64::encoded_len(bytes.len(), true).unwrap_or(0), 0);
let n = base64::engine::general_purpose::STANDARD
.encode_slice(bytes, &mut self.out[at..])
.unwrap_or(0);
self.out.truncate(at + n);
self.out.push(b'"');
self.end()
}
pub fn list(&mut self, len: usize) -> &mut Self {
self.container(len, false)
}
pub fn map(&mut self, len: usize) -> &mut Self {
self.container(len, true)
}
pub fn key(&mut self, key: &str) -> &mut Self {
self.separate();
push_string(key, &mut self.out);
self.out.push(b':');
if self.pretty {
self.out.push(b' ');
}
self
}
pub fn value(&mut self, value: &Value) -> &mut Self {
match value {
Value::Null => self.null(),
Value::Bool(b) => self.bool(*b),
Value::Int(n) => self.int(*n),
Value::Float(f) => self.float(*f),
Value::Str(s) => self.str(s),
Value::List(items) => {
self.list(items.len());
for item in items {
self.value(item);
}
self
}
Value::Map(map) => {
self.map(map.len());
for (k, v) in map {
self.key(k).value(v);
}
self
}
Value::Ref(r) => self.reference(r.kind.as_str(), r.id.as_str()),
Value::Bytes(b) => self.bytes(b),
}
}
#[must_use]
pub fn finish(mut self) -> String {
if self.pretty {
self.out.push(b'\n');
}
String::from_utf8(self.out)
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned())
}
fn separate(&mut self) {
let Some(top) = self.open.last() else {
return;
};
if top.done > 0 {
self.out.push(b',');
}
self.line_break(self.open.len());
}
fn line_break(&mut self, level: usize) {
if self.pretty {
self.out.push(b'\n');
for _ in 0..level {
self.out.extend_from_slice(INDENT);
}
}
}
fn begin(&mut self) {
if matches!(self.open.last(), Some(top) if !top.map) {
self.separate();
}
}
fn end(&mut self) -> &mut Self {
while let Some(top) = self.open.last_mut() {
top.done += 1;
top.remaining -= 1;
if top.remaining > 0 {
break;
}
let closer = if top.map { b'}' } else { b']' };
self.open.pop();
self.line_break(self.open.len());
self.out.push(closer);
}
self
}
fn container(&mut self, len: usize, map: bool) -> &mut Self {
self.begin();
if len == 0 {
self.out.extend_from_slice(if map { b"{}" } else { b"[]" });
return self.end();
}
self.out.push(if map { b'{' } else { b'[' });
self.open.push(Open {
map,
remaining: len,
done: 0,
});
self
}
}