1use std::{borrow::Cow, fmt::Write, ptr};23use crate::{bail, Result, ResultExt, 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 debug_truncate_strings: Option<usize>,70}7172impl<'s> JsonFormat<'s> {73 74 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {75 Self {76 padding: Cow::Borrowed(""),77 mtype: JsonFormatting::Minify,78 newline: "\n",79 key_val_sep: ":",80 #[cfg(feature = "exp-preserve-order")]81 preserve_order,82 #[cfg(feature = "exp-bigint")]83 preserve_bigints: false,84 debug_truncate_strings: None,85 }86 }87 88 pub fn std_to_string() -> Self {89 Self {90 padding: Cow::Borrowed(""),91 mtype: JsonFormatting::ToString,92 newline: "\n",93 key_val_sep: ": ",94 #[cfg(feature = "exp-preserve-order")]95 preserve_order: false,96 #[cfg(feature = "exp-bigint")]97 preserve_bigints: false,98 debug_truncate_strings: None,99 }100 }101 pub fn std_to_json(102 padding: String,103 newline: &'s str,104 key_val_sep: &'s str,105 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,106 ) -> Self {107 Self {108 padding: Cow::Owned(padding),109 mtype: JsonFormatting::Std,110 newline,111 key_val_sep,112 #[cfg(feature = "exp-preserve-order")]113 preserve_order,114 #[cfg(feature = "exp-bigint")]115 preserve_bigints: false,116 debug_truncate_strings: None,117 }118 }119 120 pub fn cli(121 padding: usize,122 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,123 ) -> Self {124 if padding == 0 {125 return Self::minify(126 #[cfg(feature = "exp-preserve-order")]127 preserve_order,128 );129 }130 Self {131 padding: Cow::Owned(" ".repeat(padding)),132 mtype: JsonFormatting::Manifest,133 newline: "\n",134 key_val_sep: ": ",135 #[cfg(feature = "exp-preserve-order")]136 preserve_order,137 #[cfg(feature = "exp-bigint")]138 preserve_bigints: false,139 debug_truncate_strings: None,140 }141 }142 143 pub fn debug() -> Self {144 Self {145 padding: Cow::Borrowed(" "),146 mtype: JsonFormatting::Manifest,147 newline: "\n",148 key_val_sep: ": ",149 #[cfg(feature = "exp-preserve-order")]150 preserve_order: true,151 #[cfg(feature = "exp-bigint")]152 preserve_bigints: true,153 debug_truncate_strings: Some(256),154 }155 }156}157impl Default for JsonFormat<'static> {158 fn default() -> Self {159 Self {160 padding: Cow::Borrowed(" "),161 mtype: JsonFormatting::Manifest,162 newline: "\n",163 key_val_sep: ": ",164 #[cfg(feature = "exp-preserve-order")]165 preserve_order: false,166 #[cfg(feature = "exp-bigint")]167 preserve_bigints: false,168 debug_truncate_strings: None,169 }170 }171}172173pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {174 let mut out = String::new();175 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;176 Ok(out)177}178179#[allow(clippy::too_many_lines)]180fn manifest_json_ex_buf(181 val: &Val,182 buf: &mut String,183 cur_padding: &mut String,184 options: &JsonFormat<'_>,185) -> Result<()> {186 use JsonFormatting::*;187188 let mtype = options.mtype;189 match val {190 Val::Bool(v) => {191 if *v {192 buf.push_str("true");193 } else {194 buf.push_str("false");195 }196 }197 Val::Null => buf.push_str("null"),198 Val::Str(s) => {199 let flat = s.clone().into_flat();200 if let Some(truncate) = options.debug_truncate_strings {201 if flat.len() > truncate {202 let (start, end) = flat.split_at(truncate / 2);203 let (_, end) = end.split_at(end.len() - truncate / 2);204 escape_string_json_buf(&format!("{start}..{end}"), buf);205 } else {206 escape_string_json_buf(&flat, buf);207 }208 } else {209 escape_string_json_buf(&flat, buf);210 }211 }212 Val::Num(n) => write!(buf, "{n}").unwrap(),213 #[cfg(feature = "exp-bigint")]214 Val::BigInt(n) => {215 if options.preserve_bigints {216 write!(buf, "{n}").unwrap();217 } else {218 write!(buf, "{:?}", n.to_string()).unwrap();219 }220 }221 Val::Arr(items) => {222 buf.push('[');223224 let old_len = cur_padding.len();225 cur_padding.push_str(&options.padding);226227 let mut had_items = false;228 for (i, item) in items.iter().enumerate() {229 had_items = true;230 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;231232 if i != 0 {233 buf.push(',');234 }235 match mtype {236 Manifest | Std => {237 buf.push_str(options.newline);238 buf.push_str(cur_padding);239 }240 ToString => buf.push(' '),241 Minify => {}242 };243244 buf.push_str(cur_padding);245 State::push_description(246 || format!("elem <{i}> manifestification"),247 || manifest_json_ex_buf(&item, buf, cur_padding, options),248 )?;249 }250251 cur_padding.truncate(old_len);252253 match mtype {254 Manifest | ToString if !had_items => {255 256 buf.push(' ');257 }258 Manifest => {259 buf.push_str(options.newline);260 buf.push_str(cur_padding);261 }262 Std => {263 if !had_items {264 265 buf.push_str(options.newline);266 }267 buf.push_str(options.newline);268 buf.push_str(cur_padding);269 }270 Minify | ToString => {}271 }272273 buf.push(']');274 }275 Val::Obj(obj) => {276 obj.run_assertions()?;277 buf.push('{');278279 let old_len = cur_padding.len();280 cur_padding.push_str(&options.padding);281282 let mut had_fields = false;283 for (i, (key, value)) in obj284 .iter(285 #[cfg(feature = "exp-preserve-order")]286 options.preserve_order,287 )288 .enumerate()289 {290 had_fields = true;291 let value = value.with_description(|| format!("field <{key}> evaluation"))?;292293 if i != 0 {294 buf.push(',');295 }296 match mtype {297 Manifest | Std => {298 buf.push_str(options.newline);299 buf.push_str(cur_padding);300 }301 ToString => buf.push(' '),302 Minify => {}303 }304305 escape_string_json_buf(&key, buf);306 buf.push_str(options.key_val_sep);307 State::push_description(308 || format!("field <{key}> manifestification"),309 || manifest_json_ex_buf(&value, buf, cur_padding, options),310 )?;311 }312313 cur_padding.truncate(old_len);314315 match mtype {316 Manifest | ToString if !had_fields => {317 318 buf.push(' ');319 }320 Manifest => {321 buf.push_str(options.newline);322 buf.push_str(cur_padding);323 }324 Std => {325 if !had_fields {326 327 buf.push_str(options.newline);328 }329 buf.push_str(options.newline);330 buf.push_str(cur_padding);331 }332 Minify | ToString => {}333 }334335 buf.push('}');336 }337 Val::Func(_) => bail!("tried to manifest function"),338 };339 Ok(())340}341342impl ManifestFormat for JsonFormat<'_> {343 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {344 manifest_json_ex_buf(&val, buf, &mut String::new(), self)345 }346}347348pub struct ToStringFormat;349impl ManifestFormat for ToStringFormat {350 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {351 JsonFormat::std_to_string().manifest_buf(val, out)352 }353 fn file_trailing_newline(&self) -> bool {354 false355 }356}357pub struct StringFormat;358impl ManifestFormat for StringFormat {359 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {360 let Val::Str(s) = val else {361 bail!(362 "output should be string for string manifest format, got {}",363 val.value_type()364 )365 };366 write!(out, "{s}").unwrap();367 Ok(())368 }369 fn file_trailing_newline(&self) -> bool {370 false371 }372}373374pub struct YamlStreamFormat<I> {375 inner: I,376 c_document_end: bool,377 end_newline: bool,378}379impl<I> YamlStreamFormat<I> {380 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {381 Self {382 inner,383 c_document_end,384 385 end_newline: true,386 }387 }388 pub fn cli(inner: I) -> Self {389 Self {390 inner,391 c_document_end: true,392 end_newline: false,393 }394 }395}396impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {397 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {398 let Val::Arr(arr) = val else {399 bail!(400 "output should be array for yaml stream format, got {}",401 val.value_type()402 )403 };404 if !arr.is_empty() {405 for (i, v) in arr.iter().enumerate() {406 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;407 out.push_str("---\n");408 State::push_description(409 || format!("elem <{i}> manifestification"),410 || self.inner.manifest_buf(v, out),411 )?;412 out.push('\n');413 }414 }415 if self.c_document_end {416 out.push_str("...");417 }418 if self.end_newline {419 out.push('\n');420 }421 Ok(())422 }423}424425pub fn escape_string_json(s: &str) -> String {426 let mut buf = String::new();427 escape_string_json_buf(s, &mut buf);428 buf429}430431432433const BB: u8 = b'b'; 434const TT: u8 = b't'; 435const NN: u8 = b'n'; 436const FF: u8 = b'f'; 437const RR: u8 = b'r'; 438const QU: u8 = b'"'; 439const BS: u8 = b'\\'; 440const UU: u8 = b'u'; 441const __: u8 = 0;442443444445static ESCAPE: [u8; 256] = [446 447 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 448 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 449 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 450 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 451 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 452 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 453 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 454 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 455 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 456 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 457 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 458 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 459 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 460 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 461 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 462 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 463];464465pub fn escape_string_json_buf(value: &str, buf: &mut String) {466 467 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };468 let bytes = value.as_bytes();469470 471 buf.reserve(value.len() + 2);472473 buf.push(b'"');474475 let mut start = 0;476477 for (i, &byte) in bytes.iter().enumerate() {478 let escape = ESCAPE[byte as usize];479 if escape == __ {480 continue;481 }482483 if start < i {484 buf.extend_from_slice(&bytes[start..i]);485 }486 start = i + 1;487488 match escape {489 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {490 buf.extend_from_slice(&[b'\\', escape]);491 }492 self::UU => {493 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";494 let bytes = &[495 b'\\',496 b'u',497 b'0',498 b'0',499 HEX_DIGITS[(byte >> 4) as usize],500 HEX_DIGITS[(byte & 0xF) as usize],501 ];502 buf.extend_from_slice(bytes);503 }504 _ => unreachable!(),505 }506 }507508 if start == bytes.len() {509 buf.push(b'"');510 return;511 }512513 buf.extend_from_slice(&bytes[start..]);514 buf.push(b'"');515}