use std::fmt;
use crate::model::value::{Map, Value};
use crate::text::write_string;
const ONE_LINE: usize = 72;
pub(super) const INDENT: &str = " ";
pub fn format(value: &Value, level: usize) -> String {
let mut out = String::new();
write_value(value, level, &mut out);
out
}
fn write_value(value: &Value, level: usize, out: &mut String) {
let start = out.len();
if write_inline(value, out) {
return;
}
out.truncate(start);
match value {
Value::List(items) => {
out.push_str("[\n");
for (i, v) in items.iter().enumerate() {
if i > 0 {
out.push_str(",\n");
}
push_indent(out, level + 1);
write_value(v, level + 1, out);
}
out.push('\n');
push_indent(out, level);
out.push(']');
}
Value::Map(map) => {
out.push_str("{\n");
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
out.push_str(",\n");
}
push_indent(out, level + 1);
write_string(k, out);
out.push_str(": ");
write_value(v, level + 1, out);
}
out.push('\n');
push_indent(out, level);
out.push('}');
}
other => {
write_scalar(other, out);
}
}
}
fn push_indent(out: &mut String, level: usize) {
for _ in 0..level {
out.push_str(INDENT);
}
}
pub(super) fn quoted(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
write_string(s, &mut out);
out
}
pub(super) fn number(n: f64) -> String {
crate::text::float_string(n)
}
fn write_scalar(v: &Value, out: &mut String) -> bool {
match v {
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Int(n) => {
fmt::Write::write_fmt(out, format_args!("{n}")).ok();
}
Value::Float(f) if f.is_finite() => out.push_str(&number(*f)),
Value::Null | Value::Float(_) => out.push_str("null"),
Value::List(_) | Value::Map(_) => return false,
Value::Str(s) => write_string(s, out),
other => out.push_str(&crate::text::to_string(other)),
}
true
}
fn write_inline(v: &Value, out: &mut String) -> bool {
if write_scalar(v, out) {
return true;
}
let start = out.len();
let written = match v {
Value::List(items) if items.is_empty() => {
out.push_str("[]");
return true;
}
Value::List(items) => write_numbers(items, out),
Value::Map(map) if map.is_empty() => {
out.push_str("{}");
return true;
}
Value::Map(map) => write_scalars(map, out),
_ => false,
};
written && out.len() - start <= ONE_LINE
}
fn write_numbers(items: &[Value], out: &mut String) -> bool {
if !items
.iter()
.all(|x| matches!(x, Value::Int(_) | Value::Float(_) | Value::Bool(_)))
{
return false;
}
out.push('[');
for (i, x) in items.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_scalar(x, out);
}
out.push(']');
true
}
fn write_scalars(map: &Map, out: &mut String) -> bool {
out.push_str("{ ");
for (i, (k, x)) in map.iter().enumerate() {
if matches!(x, Value::Map(_)) {
return false;
}
if i > 0 {
out.push_str(", ");
}
write_string(k, out);
out.push_str(": ");
if !write_inline(x, out) {
return false;
}
}
out.push_str(" }");
true
}
pub fn to_json(value: &Value) -> String {
let mut out = String::new();
json_into(value, &mut out);
out
}
fn json_into(value: &Value, out: &mut String) {
match value {
Value::List(items) => {
out.push('[');
for (i, v) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
json_into(v, out);
}
out.push(']');
}
Value::Map(map) => {
out.push('{');
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_string(k, out);
out.push(':');
json_into(v, out);
}
out.push('}');
}
Value::Str(s) => write_string(s, out),
Value::Float(f) if f.is_finite() => out.push_str(&f.to_string()),
Value::Float(_) => out.push_str("null"),
other => out.push_str(&crate::text::to_string(other)),
}
}
pub fn same(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Int(_) | Value::Float(_), Value::Int(_) | Value::Float(_)) => {
match (a.as_f64(), b.as_f64()) {
(Some(x), Some(y)) => x.to_bits() == y.to_bits(),
_ => false,
}
}
(Value::List(x), Value::List(y)) => {
x.len() == y.len() && x.iter().zip(y).all(|(p, q)| same(p, q))
}
(Value::Map(x), Value::Map(y)) => {
x.len() == y.len()
&& x.iter()
.zip(y.iter())
.all(|((ka, p), (kb, q))| ka == kb && same(p, q))
}
_ => a == b,
}
}