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 89 const fn std_to_string_helper() -> Self {90 Self {91 padding: Cow::Borrowed(""),92 mtype: JsonFormatting::ToString,93 newline: "\n",94 key_val_sep: ": ",95 #[cfg(feature = "exp-preserve-order")]96 preserve_order: false,97 #[cfg(feature = "exp-bigint")]98 preserve_bigints: false,99 debug_truncate_strings: None,100 }101 }102 pub fn std_to_json(103 padding: String,104 newline: &'s str,105 key_val_sep: &'s str,106 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,107 ) -> Self {108 Self {109 padding: Cow::Owned(padding),110 mtype: JsonFormatting::Std,111 newline,112 key_val_sep,113 #[cfg(feature = "exp-preserve-order")]114 preserve_order,115 #[cfg(feature = "exp-bigint")]116 preserve_bigints: false,117 debug_truncate_strings: None,118 }119 }120 121 pub fn cli(122 padding: usize,123 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,124 ) -> Self {125 if padding == 0 {126 return Self::minify(127 #[cfg(feature = "exp-preserve-order")]128 preserve_order,129 );130 }131 Self {132 padding: Cow::Owned(" ".repeat(padding)),133 mtype: JsonFormatting::Manifest,134 newline: "\n",135 key_val_sep: ": ",136 #[cfg(feature = "exp-preserve-order")]137 preserve_order,138 #[cfg(feature = "exp-bigint")]139 preserve_bigints: false,140 debug_truncate_strings: None,141 }142 }143 144 pub fn debug() -> Self {145 Self {146 padding: Cow::Borrowed(" "),147 mtype: JsonFormatting::Manifest,148 newline: "\n",149 key_val_sep: ": ",150 #[cfg(feature = "exp-preserve-order")]151 preserve_order: true,152 #[cfg(feature = "exp-bigint")]153 preserve_bigints: true,154 debug_truncate_strings: Some(256),155 }156 }157}158impl Default for JsonFormat<'static> {159 fn default() -> Self {160 Self {161 padding: Cow::Borrowed(" "),162 mtype: JsonFormatting::Manifest,163 newline: "\n",164 key_val_sep: ": ",165 #[cfg(feature = "exp-preserve-order")]166 preserve_order: false,167 #[cfg(feature = "exp-bigint")]168 preserve_bigints: false,169 debug_truncate_strings: None,170 }171 }172}173174pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {175 let mut out = String::new();176 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;177 Ok(out)178}179180#[allow(clippy::too_many_lines)]181fn manifest_json_ex_buf(182 val: &Val,183 buf: &mut String,184 cur_padding: &mut String,185 options: &JsonFormat<'_>,186) -> Result<()> {187 use JsonFormatting::*;188189 let mtype = options.mtype;190 match val {191 Val::Bool(v) => {192 if *v {193 buf.push_str("true");194 } else {195 buf.push_str("false");196 }197 }198 Val::Null => buf.push_str("null"),199 Val::Str(s) => {200 let flat = s.clone().into_flat();201 if let Some(truncate) = options.debug_truncate_strings {202 if flat.len() > truncate {203 let (start, end) = flat.split_at(truncate / 2);204 let (_, end) = end.split_at(end.len() - truncate / 2);205 escape_string_json_buf(&format!("{start}..{end}"), buf);206 } else {207 escape_string_json_buf(&flat, buf);208 }209 } else {210 escape_string_json_buf(&flat, buf);211 }212 }213 Val::Num(n) => write!(buf, "{n}").unwrap(),214 #[cfg(feature = "exp-bigint")]215 Val::BigInt(n) => {216 if options.preserve_bigints {217 write!(buf, "{n}").unwrap();218 } else {219 write!(buf, "{:?}", n.to_string()).unwrap();220 }221 }222 Val::Arr(items) => {223 buf.push('[');224225 let old_len = cur_padding.len();226 cur_padding.push_str(&options.padding);227228 let mut had_items = false;229 for (i, item) in items.iter().enumerate() {230 had_items = true;231 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;232233 if i != 0 {234 buf.push(',');235 }236 match mtype {237 Manifest | Std => {238 buf.push_str(options.newline);239 buf.push_str(cur_padding);240 }241 ToString if i != 0 => buf.push(' '),242 Minify | ToString => {}243 };244245 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 if i != 0 => buf.push(' '),302 Minify | ToString => {}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}347348349350pub struct ToStringFormat;351impl ManifestFormat for ToStringFormat {352 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {353 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();354 if let Some(str) = val.as_str() {355 out.push_str(&str);356 return Ok(());357 }358 JSON_TO_STRING.manifest_buf(val, out)359 }360 fn file_trailing_newline(&self) -> bool {361 false362 }363}364pub struct StringFormat;365impl ManifestFormat for StringFormat {366 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {367 let Val::Str(s) = val else {368 bail!(369 "output should be string for string manifest format, got {}",370 val.value_type()371 )372 };373 write!(out, "{s}").unwrap();374 Ok(())375 }376 fn file_trailing_newline(&self) -> bool {377 false378 }379}380381pub struct YamlStreamFormat<I> {382 inner: I,383 c_document_end: bool,384 end_newline: bool,385}386impl<I> YamlStreamFormat<I> {387 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {388 Self {389 inner,390 c_document_end,391 392 end_newline: true,393 }394 }395 pub fn cli(inner: I) -> Self {396 Self {397 inner,398 c_document_end: true,399 end_newline: false,400 }401 }402}403impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {404 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {405 let Val::Arr(arr) = val else {406 bail!(407 "output should be array for yaml stream format, got {}",408 val.value_type()409 )410 };411 if !arr.is_empty() {412 for (i, v) in arr.iter().enumerate() {413 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;414 out.push_str("---\n");415 State::push_description(416 || format!("elem <{i}> manifestification"),417 || self.inner.manifest_buf(v, out),418 )?;419 out.push('\n');420 }421 }422 if self.c_document_end {423 out.push_str("...");424 }425 if self.end_newline {426 out.push('\n');427 }428 Ok(())429 }430}431432pub fn escape_string_json(s: &str) -> String {433 let mut buf = String::new();434 escape_string_json_buf(s, &mut buf);435 buf436}437438439440const BB: u8 = b'b'; 441const TT: u8 = b't'; 442const NN: u8 = b'n'; 443const FF: u8 = b'f'; 444const RR: u8 = b'r'; 445const QU: u8 = b'"'; 446const BS: u8 = b'\\'; 447const UU: u8 = b'u'; 448const __: u8 = 0;449450451452static ESCAPE: [u8; 256] = [453 454 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 455 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 456 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 457 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 458 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 459 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 460 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 461 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 462 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 463 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 464 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 465 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 466 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 467 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 468 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 469 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 470];471472pub fn escape_string_json_buf(value: &str, buf: &mut String) {473 474 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };475 let bytes = value.as_bytes();476477 478 buf.reserve(value.len() + 2);479480 buf.push(b'"');481482 let mut start = 0;483484 for (i, &byte) in bytes.iter().enumerate() {485 let escape = ESCAPE[byte as usize];486 if escape == __ {487 continue;488 }489490 if start < i {491 buf.extend_from_slice(&bytes[start..i]);492 }493 start = i + 1;494495 match escape {496 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {497 buf.extend_from_slice(&[b'\\', escape]);498 }499 self::UU => {500 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";501 let bytes = &[502 b'\\',503 b'u',504 b'0',505 b'0',506 HEX_DIGITS[(byte >> 4) as usize],507 HEX_DIGITS[(byte & 0xF) as usize],508 ];509 buf.extend_from_slice(bytes);510 }511 _ => unreachable!(),512 }513 }514515 if start == bytes.len() {516 buf.push(b'"');517 return;518 }519520 buf.extend_from_slice(&bytes[start..]);521 buf.push(b'"');522}