use serde_json::Value; use crate::{Error, syntax}; const WIDTH: usize = 80; pub(crate) fn key(text: &str) -> String { let mut chars = text.chars(); if chars .next() .is_some_and(|ch| ch.is_ascii_alphabetic() || ch == '_' || ch == '$') && chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '$') { text.to_owned() } else { serde_json::to_string(text).unwrap() } } pub(crate) fn print( value: &Value, implicit: bool, options: &crate::Options, ) -> Result { check_depth(value, 0, options.max_depth)?; let mut output = String::new(); write(value, implicit, 0, &mut output); Ok(output) } fn check_depth(value: &Value, depth: usize, limit: usize) -> Result<(), Error> { if matches!(value, Value::Array(_) | Value::Object(_)) && depth > limit { return Err(syntax::nesting_limit(0..0, limit)); } match value { Value::Array(items) => { for value in items { check_depth(value, depth + 1, limit)?; } } Value::Object(map) => { for value in map.values() { check_depth(value, depth + 1, limit)?; } } _ => {} } Ok(()) } fn flat(value: &Value) -> Option { match value { Value::Object(map) if map.is_empty() => Some("{}".into()), Value::Array(items) if items.is_empty() => Some("[]".into()), Value::Object(_) | Value::Array(_) => None, _ => Some(serde_json::to_string(value).unwrap()), } } fn write(value: &Value, implicit: bool, indent: usize, output: &mut String) { if let Some(text) = flat(value) { output.push_str(&text); return; } match value { Value::Object(map) => { let child = if implicit { indent } else { indent + 2 }; if !implicit { output.push_str("{\n"); } for (index, (name, value)) in map.iter().enumerate() { if index > 0 { output.push('\n'); } output.push_str(&" ".repeat(child)); output.push_str(&key(name)); output.push_str(": "); write(value, false, child, output); } if !implicit { output.push('\n'); output.push_str(&" ".repeat(indent)); output.push('}'); } } Value::Array(items) => { let inline = items.iter().map(flat).collect::>>(); if let Some(inline) = inline { let text = format!("[{}]", inline.join(", ")); let column = output.rsplit('\n').next().unwrap().chars().count(); if column + text.chars().count() <= WIDTH { output.push_str(&text); return; } } output.push_str("[\n"); for (index, value) in items.iter().enumerate() { if index > 0 { output.push('\n'); } output.push_str(&" ".repeat(indent + 2)); write(value, false, indent + 2, output); } output.push('\n'); output.push_str(&" ".repeat(indent)); output.push(']'); } _ => unreachable!(), } }