use std::borrow::Cow;
use memchr::{memchr2, memchr3};
use nt_syntax::{STEP, Value, push_indent, split_nt_line_li};
pub fn dumps(value: &Value<'_>) -> String {
let mut out = String::with_capacity(256);
write(value, 0, &mut out);
out
}
fn write(value: &Value<'_>, depth: usize, out: &mut String) {
match value {
Value::Str(s) => write_str_block(out, depth, s),
Value::List(item_li) => write_list(item_li, depth, out),
Value::Dict(pair_li) => write_dict(pair_li, depth, out),
}
}
pub(crate) fn write_str_block(out: &mut String, depth: usize, s: &str) {
for (i, line) in split_nt_line_li(s).enumerate() {
if i > 0 {
out.push('\n');
}
push_indent(out, depth);
out.push('>');
if !line.is_empty() {
out.push(' ');
out.push_str(line);
}
}
out.push('\n');
}
pub(crate) fn write_multiline_key(out: &mut String, depth: usize, key: &str) {
for (i, line) in split_nt_line_li(key).enumerate() {
if i > 0 {
out.push('\n');
}
push_indent(out, depth);
out.push(':');
if !line.is_empty() {
out.push(' ');
out.push_str(line);
}
}
out.push('\n');
}
fn write_list(item_li: &[Value<'_>], depth: usize, out: &mut String) {
if item_li.is_empty() {
push_indent(out, depth);
out.push_str("[]\n");
return;
}
for item in item_li {
push_indent(out, depth);
match item {
Value::Str(s) if s.is_empty() => out.push_str("-\n"),
Value::Str(s) if !multiline(s) => {
out.push_str("- ");
out.push_str(s);
out.push('\n');
}
_ => {
out.push_str("-\n");
write(item, depth + STEP, out);
}
}
}
}
fn write_dict(pair_li: &[(Cow<'_, str>, Value<'_>)], depth: usize, out: &mut String) {
if pair_li.is_empty() {
push_indent(out, depth);
out.push_str("{}\n");
return;
}
for (key, value) in pair_li {
if !is_inline_key(key) {
write_multiline_key(out, depth, key);
write(value, depth + STEP, out);
continue;
}
push_indent(out, depth);
out.push_str(key);
out.push(':');
match value {
Value::Str(s) if s.is_empty() => out.push('\n'),
Value::Str(s) if !multiline(s) => {
out.push(' ');
out.push_str(s);
out.push('\n');
}
_ => {
out.push('\n');
write(value, depth + STEP, out);
}
}
}
}
#[inline]
pub(crate) fn multiline(s: &str) -> bool {
let b = s.as_bytes();
if b.is_empty() {
return false;
}
let first = b[0];
let last = b[b.len() - 1];
if first.is_ascii_whitespace()
|| last.is_ascii_whitespace()
|| ((first >= 0x80 || last >= 0x80) && s != s.trim())
{
return true;
}
memchr2(b'\n', b'\r', b).is_some()
}
#[inline]
pub(crate) fn is_inline_key(key: &str) -> bool {
let b = key.as_bytes();
let Some(&first) = b.first() else {
return false;
};
let last = b[b.len() - 1];
if matches!(first, b'#' | b'[' | b'{')
|| matches!((first, b.get(1)), (b'-' | b'>' | b':', None | Some(b' ')))
|| last == b':'
|| first.is_ascii_whitespace()
|| last.is_ascii_whitespace()
|| ((first >= 0x80 || last >= 0x80) && key != key.trim())
{
return false;
}
let mut from = 0;
while let Some(rel) = memchr3(b'\n', b'\r', b':', unsafe { b.get_unchecked(from..) }) {
let pos = from + rel;
if unsafe { *b.get_unchecked(pos) } != b':' || b.get(pos + 1) == Some(&b' ') {
return false;
}
from = pos + 1;
}
true
}