1use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};23use crate::{Result, ResultExt, Val, bail, in_description_frame};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}4647pub struct BlackBoxFormat;48impl ManifestFormat for BlackBoxFormat {49 #[allow(clippy::only_used_in_recursion)]50 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {51 match val {52 Val::Bool(v) => {53 black_box(v);54 }55 val @ Val::Null => {56 black_box(val);57 }58 Val::Str(str_value) => {59 black_box(format!("{str_value}"));60 }61 Val::Num(num_value) => {62 black_box(num_value);63 }64 Val::Arr(arr_value) => {65 for ele in arr_value.iter() {66 let ele = ele?;67 self.manifest_buf(ele, buf)?;68 }69 }70 Val::Obj(obj_value) => {71 for (name, value) in obj_value.iter(72 #[cfg(feature = "exp-preserve-order")]73 true,74 ) {75 black_box(name);76 let value = value?;77 self.manifest_buf(value, buf)?;78 }79 }80 Val::Func(func_val) => {81 black_box(func_val);82 bail!("tried to manifest function")83 }84 #[cfg(feature = "exp-bigint")]85 Val::BigInt(n) => {86 black_box(n);87 }88 }89 Ok(())90 }91}9293#[derive(PartialEq, Eq, Clone, Copy)]94enum JsonFormatting {95 96 Manifest,97 98 99 Std,100 101 ToString,102 103 Minify,104}105106pub struct JsonFormat<'s> {107 padding: Cow<'s, str>,108 mtype: JsonFormatting,109 newline: &'s str,110 key_val_sep: &'s str,111 #[cfg(feature = "exp-preserve-order")]112 preserve_order: bool,113 #[cfg(feature = "exp-bigint")]114 preserve_bigints: bool,115}116117impl<'s> JsonFormat<'s> {118 119 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {120 Self {121 padding: Cow::Borrowed(""),122 mtype: JsonFormatting::Minify,123 newline: "\n",124 key_val_sep: ":",125 #[cfg(feature = "exp-preserve-order")]126 preserve_order,127 #[cfg(feature = "exp-bigint")]128 preserve_bigints: false,129 }130 }131 132 133 const fn std_to_string_helper() -> Self {134 Self {135 padding: Cow::Borrowed(""),136 mtype: JsonFormatting::ToString,137 newline: "\n",138 key_val_sep: ": ",139 #[cfg(feature = "exp-preserve-order")]140 preserve_order: false,141 #[cfg(feature = "exp-bigint")]142 preserve_bigints: false,143 }144 }145 pub fn std_to_json(146 padding: String,147 newline: &'s str,148 key_val_sep: &'s str,149 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,150 ) -> Self {151 Self {152 padding: Cow::Owned(padding),153 mtype: JsonFormatting::Std,154 newline,155 key_val_sep,156 #[cfg(feature = "exp-preserve-order")]157 preserve_order,158 #[cfg(feature = "exp-bigint")]159 preserve_bigints: false,160 }161 }162 163 pub fn cli(164 padding: usize,165 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,166 ) -> Self {167 if padding == 0 {168 return Self::minify(169 #[cfg(feature = "exp-preserve-order")]170 preserve_order,171 );172 }173 Self {174 padding: Cow::Owned(" ".repeat(padding)),175 mtype: JsonFormatting::Manifest,176 newline: "\n",177 key_val_sep: ": ",178 #[cfg(feature = "exp-preserve-order")]179 preserve_order,180 #[cfg(feature = "exp-bigint")]181 preserve_bigints: false,182 }183 }184 185 pub fn debug() -> Self {186 Self {187 padding: Cow::Borrowed(" "),188 mtype: JsonFormatting::Manifest,189 newline: "\n",190 key_val_sep: ": ",191 #[cfg(feature = "exp-preserve-order")]192 preserve_order: true,193 #[cfg(feature = "exp-bigint")]194 preserve_bigints: true,195 }196 }197}198impl Default for JsonFormat<'static> {199 fn default() -> Self {200 Self {201 padding: Cow::Borrowed(" "),202 mtype: JsonFormatting::Manifest,203 newline: "\n",204 key_val_sep: ": ",205 #[cfg(feature = "exp-preserve-order")]206 preserve_order: false,207 #[cfg(feature = "exp-bigint")]208 preserve_bigints: false,209 }210 }211}212213pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {214 let mut out = String::new();215 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;216 Ok(out)217}218219#[allow(clippy::too_many_lines)]220fn manifest_json_ex_buf(221 val: &Val,222 buf: &mut String,223 cur_padding: &mut String,224 options: &JsonFormat<'_>,225) -> Result<()> {226 use JsonFormatting::*;227228 let mtype = options.mtype;229 match val {230 Val::Bool(v) => {231 if *v {232 buf.push_str("true");233 } else {234 buf.push_str("false");235 }236 }237 Val::Null => buf.push_str("null"),238 Val::Str(s) => {239 buf.reserve(2 + s.len());240 buf.push('"');241 s.chunks(&mut |c| {242 escape_string_json_buf_raw(c, buf);243 });244 buf.push('"');245 }246 Val::Num(n) => write!(buf, "{n}").unwrap(),247 #[cfg(feature = "exp-bigint")]248 Val::BigInt(n) => {249 if options.preserve_bigints {250 write!(buf, "{n}").unwrap();251 } else {252 write!(buf, "{:?}", n.to_string()).unwrap();253 }254 }255 Val::Arr(items) => {256 buf.push('[');257258 let old_len = cur_padding.len();259 cur_padding.push_str(&options.padding);260261 let mut had_items = false;262 for (i, item) in items.iter().enumerate() {263 had_items = true;264 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;265266 if i != 0 {267 buf.push(',');268 }269 match mtype {270 Manifest | Std => {271 buf.push_str(options.newline);272 buf.push_str(cur_padding);273 }274 ToString if i != 0 => buf.push(' '),275 Minify | ToString => {}276 }277278 in_description_frame(279 || format!("elem <{i}> manifestification"),280 || manifest_json_ex_buf(&item, buf, cur_padding, options),281 )?;282 }283284 cur_padding.truncate(old_len);285286 match mtype {287 Manifest | ToString if !had_items => {288 289 buf.push(' ');290 }291 Manifest => {292 buf.push_str(options.newline);293 buf.push_str(cur_padding);294 }295 Std => {296 if !had_items {297 298 buf.push_str(options.newline);299 }300 buf.push_str(options.newline);301 buf.push_str(cur_padding);302 }303 Minify | ToString => {}304 }305306 buf.push(']');307 }308 Val::Obj(obj) => {309 obj.run_assertions()?;310 buf.push('{');311312 let old_len = cur_padding.len();313 cur_padding.push_str(&options.padding);314315 let mut had_fields = false;316 for (i, (key, value)) in obj317 .iter(318 #[cfg(feature = "exp-preserve-order")]319 options.preserve_order,320 )321 .enumerate()322 {323 had_fields = true;324 let value = value.with_description(|| format!("field <{key}> evaluation"))?;325326 if i != 0 {327 buf.push(',');328 }329 match mtype {330 Manifest | Std => {331 buf.push_str(options.newline);332 buf.push_str(cur_padding);333 }334 ToString if i != 0 => buf.push(' '),335 Minify | ToString => {}336 }337338 escape_string_json_buf(&key, buf);339 buf.push_str(options.key_val_sep);340 in_description_frame(341 || format!("field <{key}> manifestification"),342 || manifest_json_ex_buf(&value, buf, cur_padding, options),343 )?;344 }345346 cur_padding.truncate(old_len);347348 match mtype {349 Manifest | ToString if !had_fields => {350 351 buf.push(' ');352 }353 Manifest => {354 buf.push_str(options.newline);355 buf.push_str(cur_padding);356 }357 Std => {358 if !had_fields {359 360 buf.push_str(options.newline);361 }362 buf.push_str(options.newline);363 buf.push_str(cur_padding);364 }365 Minify | ToString => {}366 }367368 buf.push('}');369 }370 Val::Func(_) => bail!("tried to manifest function"),371 }372 Ok(())373}374375impl ManifestFormat for JsonFormat<'_> {376 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {377 manifest_json_ex_buf(&val, buf, &mut String::new(), self)378 }379}380381382383pub struct ToStringFormat;384impl ManifestFormat for ToStringFormat {385 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {386 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();387 if let Some(str) = val.as_str() {388 out.push_str(&str);389 return Ok(());390 }391 #[cfg(feature = "exp-bigint")]392 if let Some(int) = val.as_bigint() {393 out.push_str(&int.to_str_radix(10));394 return Ok(());395 }396 JSON_TO_STRING.manifest_buf(val, out)397 }398 fn file_trailing_newline(&self) -> bool {399 false400 }401}402pub struct StringFormat;403impl ManifestFormat for StringFormat {404 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {405 let Val::Str(s) = val else {406 bail!(407 "output should be string for string manifest format, got {}",408 val.value_type()409 )410 };411 write!(out, "{s}").unwrap();412 Ok(())413 }414 fn file_trailing_newline(&self) -> bool {415 false416 }417}418419pub struct YamlStreamFormat<I> {420 inner: I,421 c_document_end: bool,422 end_newline: bool,423}424impl<I> YamlStreamFormat<I> {425 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {426 Self {427 inner,428 c_document_end,429 430 end_newline: true,431 }432 }433 pub fn cli(inner: I) -> Self {434 Self {435 inner,436 c_document_end: true,437 end_newline: false,438 }439 }440}441impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {442 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {443 let Val::Arr(arr) = val else {444 bail!(445 "output should be array for yaml stream format, got {}",446 val.value_type()447 )448 };449 for (i, v) in arr.iter().enumerate() {450 if i != 0 {451 out.push('\n');452 }453 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;454 out.push_str("---\n");455 in_description_frame(456 || format!("elem <{i}> manifestification"),457 || self.inner.manifest_buf(v, out),458 )?;459 }460 if self.c_document_end {461 out.push('\n');462 out.push_str("...");463 }464 if self.end_newline {465 out.push('\n');466 }467 Ok(())468 }469}470471pub fn escape_string_json(s: &str) -> String {472 let mut buf = String::new();473 escape_string_json_buf(s, &mut buf);474 buf475}476477478479const BB: u8 = b'b'; 480const TT: u8 = b't'; 481const NN: u8 = b'n'; 482const FF: u8 = b'f'; 483const RR: u8 = b'r'; 484const QU: u8 = b'"'; 485const BS: u8 = b'\\'; 486const UU: u8 = b'u'; 487const __: u8 = 0;488489490491static ESCAPE: [u8; 256] = [492 493 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 494 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 495 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 496 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 497 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 498 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 499 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 500 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 501 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 502 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 503 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 504 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 505 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 506 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 507 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 508 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 509];510511pub fn escape_string_json_buf(value: &str, buf: &mut String) {512 buf.reserve_exact(value.len() + 2);513 buf.push('"');514 escape_string_json_buf_raw(value, buf);515 buf.push('"');516}517518fn escape_string_json_buf_raw(value: &str, buf: &mut String) {519 520 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };521 let bytes = value.as_bytes();522523 let mut start = 0;524525 for (i, &byte) in bytes.iter().enumerate() {526 let escape = ESCAPE[byte as usize];527 if escape == __ {528 continue;529 }530531 if start < i {532 buf.extend_from_slice(&bytes[start..i]);533 }534 start = i + 1;535536 match escape {537 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {538 buf.extend_from_slice(&[b'\\', escape]);539 }540 self::UU => {541 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";542 let bytes = &[543 b'\\',544 b'u',545 b'0',546 b'0',547 HEX_DIGITS[(byte >> 4) as usize],548 HEX_DIGITS[(byte & 0xF) as usize],549 ];550 buf.extend_from_slice(bytes);551 }552 _ => unreachable!(),553 }554 }555556 if start == bytes.len() {557 return;558 }559560 buf.extend_from_slice(&bytes[start..]);561}