1use std::{borrow::Cow, fmt::Write};23use crate::{bail, Result, State, Val};45pub trait ManifestFormat {6 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;7 fn manifest(&self, val: Val) -> Result<String> {8 let mut out = String::new();9 self.manifest_buf(val, &mut out)?;10 Ok(out)11 }12 13 14 15 16 fn file_trailing_newline(&self) -> bool {17 true18 }19}20impl<T> ManifestFormat for Box<T>21where22 T: ManifestFormat + ?Sized,23{24 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {25 let inner = &**self;26 inner.manifest_buf(val, buf)27 }28 fn file_trailing_newline(&self) -> bool {29 let inner = &**self;30 inner.file_trailing_newline()31 }32}33impl<T> ManifestFormat for &'_ T34where35 T: ManifestFormat + ?Sized,36{37 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38 let inner = &**self;39 inner.manifest_buf(val, buf)40 }41 fn file_trailing_newline(&self) -> bool {42 let inner = &**self;43 inner.file_trailing_newline()44 }45}4647#[derive(PartialEq, Eq, Clone, Copy)]48enum JsonFormatting {49 50 Manifest,51 52 53 Std,54 55 ToString,56 57 Minify,58}5960pub struct JsonFormat<'s> {61 padding: Cow<'s, str>,62 mtype: JsonFormatting,63 newline: &'s str,64 key_val_sep: &'s str,65 #[cfg(feature = "exp-preserve-order")]66 preserve_order: bool,67 #[cfg(feature = "exp-bigint")]68 preserve_bigints: bool,69}7071impl<'s> JsonFormat<'s> {72 73 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {74 Self {75 padding: Cow::Borrowed(""),76 mtype: JsonFormatting::Minify,77 newline: "\n",78 key_val_sep: ":",79 #[cfg(feature = "exp-preserve-order")]80 preserve_order,81 #[cfg(feature = "exp-bigint")]82 preserve_bigints: false,83 }84 }85 86 pub fn std_to_string() -> Self {87 Self {88 padding: Cow::Borrowed(""),89 mtype: JsonFormatting::ToString,90 newline: "\n",91 key_val_sep: ": ",92 #[cfg(feature = "exp-preserve-order")]93 preserve_order: false,94 #[cfg(feature = "exp-bigint")]95 preserve_bigints: false,96 }97 }98 pub fn std_to_json(99 padding: String,100 newline: &'s str,101 key_val_sep: &'s str,102 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,103 ) -> Self {104 Self {105 padding: Cow::Owned(padding),106 mtype: JsonFormatting::Std,107 newline,108 key_val_sep,109 #[cfg(feature = "exp-preserve-order")]110 preserve_order,111 #[cfg(feature = "exp-bigint")]112 preserve_bigints: false,113 }114 }115 116 pub fn cli(117 padding: usize,118 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,119 ) -> Self {120 if padding == 0 {121 return Self::minify(122 #[cfg(feature = "exp-preserve-order")]123 preserve_order,124 );125 }126 Self {127 padding: Cow::Owned(" ".repeat(padding)),128 mtype: JsonFormatting::Manifest,129 newline: "\n",130 key_val_sep: ": ",131 #[cfg(feature = "exp-preserve-order")]132 preserve_order,133 #[cfg(feature = "exp-bigint")]134 preserve_bigints: false,135 }136 }137}138impl Default for JsonFormat<'static> {139 fn default() -> Self {140 Self {141 padding: Cow::Borrowed(" "),142 mtype: JsonFormatting::Manifest,143 newline: "\n",144 key_val_sep: ": ",145 #[cfg(feature = "exp-preserve-order")]146 preserve_order: false,147 #[cfg(feature = "exp-bigint")]148 preserve_bigints: false,149 }150 }151}152153pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {154 let mut out = String::new();155 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;156 Ok(out)157}158fn manifest_json_ex_buf(159 val: &Val,160 buf: &mut String,161 cur_padding: &mut String,162 options: &JsonFormat<'_>,163) -> Result<()> {164 let mtype = options.mtype;165 match val {166 Val::Bool(v) => {167 if *v {168 buf.push_str("true");169 } else {170 buf.push_str("false");171 }172 }173 Val::Null => buf.push_str("null"),174 Val::Str(s) => escape_string_json_buf(&s.clone().into_flat(), buf),175 Val::Num(n) => write!(buf, "{n}").unwrap(),176 #[cfg(feature = "exp-bigint")]177 Val::BigInt(n) => if options.preserve_bigints {178 write!(buf, "{n}").unwrap()179 } else {180 write!(buf, "{:?}", n.to_string()).unwrap()181 },182 Val::Arr(items) => {183 buf.push('[');184 if !items.is_empty() {185 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {186 buf.push_str(options.newline);187 }188189 let old_len = cur_padding.len();190 cur_padding.push_str(&options.padding);191 for (i, item) in items.iter().enumerate() {192 if i != 0 {193 buf.push(',');194 if mtype == JsonFormatting::ToString {195 buf.push(' ');196 } else if mtype != JsonFormatting::Minify {197 buf.push_str(options.newline);198 }199 }200 buf.push_str(cur_padding);201 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;202 }203 cur_padding.truncate(old_len);204205 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {206 buf.push_str(options.newline);207 buf.push_str(cur_padding);208 }209 } else if mtype == JsonFormatting::Std {210 buf.push_str(options.newline);211 buf.push_str(options.newline);212 buf.push_str(cur_padding);213 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {214 buf.push(' ');215 }216 buf.push(']');217 }218 Val::Obj(obj) => {219 obj.run_assertions()?;220 buf.push('{');221 let fields = obj.fields(222 #[cfg(feature = "exp-preserve-order")]223 options.preserve_order,224 );225 if !fields.is_empty() {226 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {227 buf.push_str(options.newline);228 }229230 let old_len = cur_padding.len();231 cur_padding.push_str(&options.padding);232 for (i, field) in fields.into_iter().enumerate() {233 if i != 0 {234 buf.push(',');235 if mtype == JsonFormatting::ToString {236 buf.push(' ');237 } else if mtype != JsonFormatting::Minify {238 buf.push_str(options.newline);239 }240 }241 buf.push_str(cur_padding);242 escape_string_json_buf(&field, buf);243 buf.push_str(options.key_val_sep);244 State::push_description(245 || format!("field <{}> manifestification", field.clone()),246 || {247 let value = obj.get(field.clone())?.unwrap();248 manifest_json_ex_buf(&value, buf, cur_padding, options)?;249 Ok(())250 },251 )?;252 }253 cur_padding.truncate(old_len);254255 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {256 buf.push_str(options.newline);257 buf.push_str(cur_padding);258 }259 } else if mtype == JsonFormatting::Std {260 buf.push_str(options.newline);261 buf.push_str(options.newline);262 buf.push_str(cur_padding);263 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {264 buf.push(' ');265 }266 buf.push('}');267 }268 Val::Func(_) => bail!("tried to manifest function"),269 };270 Ok(())271}272273impl ManifestFormat for JsonFormat<'_> {274 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {275 manifest_json_ex_buf(&val, buf, &mut String::new(), self)276 }277}278279pub struct ToStringFormat;280impl ManifestFormat for ToStringFormat {281 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {282 JsonFormat::std_to_string().manifest_buf(val, out)283 }284 fn file_trailing_newline(&self) -> bool {285 false286 }287}288pub struct StringFormat;289impl ManifestFormat for StringFormat {290 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {291 let Val::Str(s) = val else {292 bail!(293 "output should be string for string manifest format, got {}",294 val.value_type()295 )296 };297 write!(out, "{s}").unwrap();298 Ok(())299 }300 fn file_trailing_newline(&self) -> bool {301 false302 }303}304305pub struct YamlStreamFormat<I>(pub I);306impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {307 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {308 let Val::Arr(arr) = val else {309 bail!(310 "output should be array for yaml stream format, got {}",311 val.value_type()312 )313 };314 if !arr.is_empty() {315 for v in arr.iter() {316 let v = v?;317 out.push_str("---\n");318 self.0.manifest_buf(v, out)?;319 out.push('\n');320 }321 out.push_str("...");322 }323 Ok(())324 }325}326327pub fn escape_string_json(s: &str) -> String {328 let mut buf = String::new();329 escape_string_json_buf(s, &mut buf);330 buf331}332333334335const BB: u8 = b'b'; 336const TT: u8 = b't'; 337const NN: u8 = b'n'; 338const FF: u8 = b'f'; 339const RR: u8 = b'r'; 340const QU: u8 = b'"'; 341const BS: u8 = b'\\'; 342const UU: u8 = b'u'; 343const __: u8 = 0;344345346347static ESCAPE: [u8; 256] = [348 349 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 350 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 351 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 352 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 353 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 354 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 355 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 356 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 357 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 358 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 359 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 360 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 361 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 362 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 363 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 364 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 365];366367pub fn escape_string_json_buf(value: &str, buf: &mut String) {368 369 let buf: &mut Vec<u8> = unsafe { &mut *(buf as *mut String).cast::<Vec<u8>>() };370 let bytes = value.as_bytes();371372 373 buf.reserve(value.len() + 2);374375 buf.push(b'"');376377 let mut start = 0;378379 for (i, &byte) in bytes.iter().enumerate() {380 let escape = ESCAPE[byte as usize];381 if escape == __ {382 continue;383 }384385 if start < i {386 buf.extend_from_slice(&bytes[start..i]);387 }388 start = i + 1;389390 match escape {391 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {392 buf.extend_from_slice(&[b'\\', escape]);393 }394 self::UU => {395 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";396 let bytes = &[397 b'\\',398 b'u',399 b'0',400 b'0',401 HEX_DIGITS[(byte >> 4) as usize],402 HEX_DIGITS[(byte & 0xF) as usize],403 ];404 buf.extend_from_slice(bytes);405 }406 _ => unreachable!(),407 }408 }409410 if start == bytes.len() {411 buf.push(b'"');412 return;413 }414415 buf.extend_from_slice(&bytes[start..]);416 buf.push(b'"');417}