#![cfg(feature="std")]
use {
std::io::Write,
crate::{Array, IoResult, Json, Map, NumberWriter, bytes},
};
pub (super) const DEFAULT_TAB_WIDTH: u8 = 4;
const WHITE_SPACE: &[u8] = &[b' '];
const MAX_TAB_WIDTH: u8 = 32;
const MAX_TAB_LEVEL: usize = 65_535;
const LINE_BREAK: &[u8] = &[b'\n'];
const QUOTATION_MARK: &[u8] = &[b'"'];
const TAB_32: [u8; 32] = [b' '; 32];
#[test]
fn tests() {
assert!(TAB_32.iter().all(|b| b == &b' '));
}
#[derive(Debug)]
pub (super) struct Formatter {
tab_width: Option<u8>,
tab_level: Option<usize>,
number_writer: NumberWriter,
}
impl Formatter {
pub fn new(tab_width: Option<u8>) -> Self {
let has_tab_width = tab_width.is_some();
Self {
tab_width: tab_width.map(|w| w.min(MAX_TAB_WIDTH)),
tab_level: if has_tab_width { Some(0) } else { None },
number_writer: NumberWriter::new(),
}
}
pub fn format<W>(&mut self, value: &Json, stream: &mut W) -> IoResult<()> where W: Write {
match value {
Json::String(s) => format_string(s, stream)?,
Json::Number(n) => self.number_writer.write(n, stream)?,
Json::Boolean(true) => stream.write_all(b"true")?,
Json::Boolean(false) => stream.write_all(b"false")?,
Json::Null => stream.write_all(b"null")?,
Json::Object(object) => {
stream.write_all(&[b'{'])?;
increment_tab_level(self.tab_level.as_mut());
self.format_object_content(object, stream)?;
decrement_tab_level(self.tab_level.as_mut());
if object.is_empty() == false {
write_pad(make_pad(self.tab_width.as_ref(), self.tab_level.as_ref()), stream)?
}
stream.write_all(&[b'}'])?;
},
Json::Array(array) => {
stream.write_all(&[b'['])?;
increment_tab_level(self.tab_level.as_mut());
self.format_array_content(array, stream)?;
decrement_tab_level(self.tab_level.as_mut());
if array.is_empty() == false {
write_pad(make_pad(self.tab_width.as_ref(), self.tab_level.as_ref()), stream)?;
}
stream.write_all(&[b']'])?;
},
};
if self.tab_width.is_some() && self.tab_level == Some(0) {
stream.write_all(LINE_BREAK)
} else {
Ok(())
}
}
fn format_object_content<W>(&mut self, map: &Map, stream: &mut W) -> IoResult<()> where W: Write {
let pad = make_pad(self.tab_width.as_ref(), self.tab_level.as_ref());
for (i, (k, v)) in map.iter().enumerate() {
match i {
0 => if pad.is_some() {
stream.write_all(LINE_BREAK)?;
},
_ => {
stream.write_all(&[b','])?;
if pad.is_some() {
stream.write_all(LINE_BREAK)?;
}
},
};
write_pad(pad, stream)?;
stream.write_all(QUOTATION_MARK)?;
stream.write_all(k.as_bytes())?;
stream.write_all(&[b'"', b':'])?;
if pad.is_some() {
stream.write_all(WHITE_SPACE)?;
}
self.format(v, stream)?;
}
if pad.is_some() && map.is_empty() == false {
stream.write_all(LINE_BREAK)
} else {
Ok(())
}
}
fn format_array_content<W>(&mut self, array: &Array, stream: &mut W) -> IoResult<()> where W: Write {
let pad = make_pad(self.tab_width.as_ref(), self.tab_level.as_ref());
for (i, v) in array.iter().enumerate() {
match i {
0 => if pad.is_some() {
stream.write_all(LINE_BREAK)?;
},
_ => {
stream.write_all(&[b','])?;
if pad.is_some() {
stream.write_all(LINE_BREAK)?;
}
},
};
write_pad(pad, stream)?;
self.format(v, stream)?;
}
if pad.is_some() && array.is_empty() == false {
stream.write_all(LINE_BREAK)
} else {
Ok(())
}
}
}
fn write_pad<W>(pad: Option<usize>, stream: &mut W) -> IoResult<()> where W: Write {
match pad {
Some(mut pad) => loop {
let tabs = match pad {
0 => return Ok(()),
1 => return stream.write_all(WHITE_SPACE),
2..=3 => &TAB_32[..2],
4..=5 => &TAB_32[..4],
6..=7 => &TAB_32[..6],
8..=11 => &TAB_32[..8],
12..=15 => &TAB_32[..12],
16..=19 => &TAB_32[..16],
20..=23 => &TAB_32[..20],
24..=27 => &TAB_32[..24],
28..=31 => &TAB_32[..28],
_ => &TAB_32,
};
stream.write_all(tabs)?;
pad -= tabs.len();
},
None => Ok(()),
}
}
fn format_string<W>(s: &str, stream: &mut W) -> IoResult<()> where W: Write {
stream.write_all(QUOTATION_MARK)?;
fn write<W>(s: &str, start: &mut Option<usize>, end: usize, stream: &mut W) -> IoResult<()> where W: Write {
if let Some(start_idx) = start {
stream.write_all(&s.as_bytes()[*start_idx..end])?;
*start = None;
}
Ok(())
}
let mut last_idx = None;
for (i, b) in s.as_bytes().iter().enumerate() {
match b {
b'"' | b'\\' => {
write(s, &mut last_idx, i, stream)?;
stream.write_all(&[b'\\', *b])?;
},
&bytes::BACKSPACE => {
write(s, &mut last_idx, i, stream)?;
stream.write_all(&[b'\\', b'b'])?;
},
&bytes::FORM_FEED => {
write(s, &mut last_idx, i, stream)?;
stream.write_all(&[b'\\', b'f'])?;
},
b'\n' => {
write(s, &mut last_idx, i, stream)?;
stream.write_all(&[b'\\', b'n'])?;
},
b'\r' => {
write(s, &mut last_idx, i, stream)?;
stream.write_all(&[b'\\', b'r'])?;
},
b'\t' => {
write(s, &mut last_idx, i, stream)?;
stream.write_all(&[b'\\', b't'])?;
},
_ => if last_idx.is_none() {
last_idx = Some(i);
},
};
}
if let Some(last_idx) = last_idx {
stream.write_all(&s.as_bytes()[last_idx..])?;
}
stream.write_all(QUOTATION_MARK)
}
fn make_pad(tab_width: Option<&u8>, tab_level: Option<&usize>) -> Option<usize> {
match (tab_width, tab_level) {
(Some(width), Some(level)) if width > &0 && level > &0 => Some(usize::from(*width).saturating_mul(*level)),
_ => None,
}
}
fn increment_tab_level(tab_level: Option<&mut usize>) {
tab_level.map(|l| *l = l.saturating_add(1).min(MAX_TAB_LEVEL));
}
fn decrement_tab_level(tab_level: Option<&mut usize>) {
tab_level.map(|l| *l = l.saturating_sub(1));
}
pub (super) fn estimate_format_size(value: &Json, tab: Option<u8>, level: Option<usize>) -> usize {
let mut level = match tab.as_ref() {
Some(tab) if tab > &0 => match level {
Some(_) => level,
None => Some(0),
},
_ => None,
};
let result = match value {
Json::String(s) => s.len().saturating_add(2),
Json::Number(n) => match u128::try_from(n) {
Ok(0) => 1,
Ok(n) => ((n as f64).log10().ceil() as usize).max(1),
Err(_) => 15,
},
Json::Boolean(_) => 5,
Json::Null => 4,
Json::Object(map) => {
let mut result: usize = 2;
increment_tab_level(level.as_mut());
let pad = make_pad(tab.as_ref(), level.as_ref());
for (k, v) in map.iter() {
result = result.saturating_add(4).saturating_add(k.len()).saturating_add(estimate_format_size(v, tab, level));
if let Some(pad) = pad.as_ref() {
result = result.saturating_add(2).saturating_add(*pad);
}
}
decrement_tab_level(level.as_mut());
if map.is_empty() == false {
if let Some(pad) = make_pad(tab.as_ref(), level.as_ref()) {
result = result.saturating_add(1).saturating_add(pad);
}
}
result
},
Json::Array(array) => {
let mut result: usize = 2;
increment_tab_level(level.as_mut());
let pad = make_pad(tab.as_ref(), level.as_ref());
for v in array {
result = result.saturating_add(1).saturating_add(estimate_format_size(v, tab, level));
if let Some(pad) = pad.as_ref() {
result = result.saturating_add(1).saturating_add(*pad);
}
}
decrement_tab_level(level.as_mut());
if array.is_empty() == false {
if let Some(pad) = make_pad(tab.as_ref(), level.as_ref()) {
result = result.saturating_add(1).saturating_add(pad);
}
}
result
},
};
if tab.is_some() {
result.saturating_add(LINE_BREAK.len())
} else {
result
}
}