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 black_box(name);73 let value = value?;74 self.manifest_buf(value, buf)?;75 }76 }77 Val::Func(func_val) => {78 black_box(func_val);79 bail!("tried to manifest function")80 }81 }82 Ok(())83 }84}8586#[derive(PartialEq, Eq, Clone, Copy)]87enum JsonFormatting {88 89 Manifest,90 91 92 Std,93 94 ToString,95 96 Minify,97}9899pub struct JsonFormat<'s> {100 padding: Cow<'s, str>,101 mtype: JsonFormatting,102 newline: &'s str,103 key_val_sep: &'s str,104 #[cfg(feature = "exp-preserve-order")]105 preserve_order: bool,106 #[cfg(feature = "exp-bigint")]107 preserve_bigints: bool,108}109110impl<'s> JsonFormat<'s> {111 112 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {113 Self {114 padding: Cow::Borrowed(""),115 mtype: JsonFormatting::Minify,116 newline: "\n",117 key_val_sep: ":",118 #[cfg(feature = "exp-preserve-order")]119 preserve_order,120 #[cfg(feature = "exp-bigint")]121 preserve_bigints: false,122 }123 }124 125 126 const fn std_to_string_helper() -> Self {127 Self {128 padding: Cow::Borrowed(""),129 mtype: JsonFormatting::ToString,130 newline: "\n",131 key_val_sep: ": ",132 #[cfg(feature = "exp-preserve-order")]133 preserve_order: false,134 #[cfg(feature = "exp-bigint")]135 preserve_bigints: false,136 }137 }138 pub fn std_to_json(139 padding: String,140 newline: &'s str,141 key_val_sep: &'s str,142 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,143 ) -> Self {144 Self {145 padding: Cow::Owned(padding),146 mtype: JsonFormatting::Std,147 newline,148 key_val_sep,149 #[cfg(feature = "exp-preserve-order")]150 preserve_order,151 #[cfg(feature = "exp-bigint")]152 preserve_bigints: false,153 }154 }155 156 pub fn cli(157 padding: usize,158 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,159 ) -> Self {160 if padding == 0 {161 return Self::minify(162 #[cfg(feature = "exp-preserve-order")]163 preserve_order,164 );165 }166 Self {167 padding: Cow::Owned(" ".repeat(padding)),168 mtype: JsonFormatting::Manifest,169 newline: "\n",170 key_val_sep: ": ",171 #[cfg(feature = "exp-preserve-order")]172 preserve_order,173 #[cfg(feature = "exp-bigint")]174 preserve_bigints: false,175 }176 }177 178 pub fn debug() -> Self {179 Self {180 padding: Cow::Borrowed(" "),181 mtype: JsonFormatting::Manifest,182 newline: "\n",183 key_val_sep: ": ",184 #[cfg(feature = "exp-preserve-order")]185 preserve_order: true,186 #[cfg(feature = "exp-bigint")]187 preserve_bigints: true,188 }189 }190}191impl Default for JsonFormat<'static> {192 fn default() -> Self {193 Self {194 padding: Cow::Borrowed(" "),195 mtype: JsonFormatting::Manifest,196 newline: "\n",197 key_val_sep: ": ",198 #[cfg(feature = "exp-preserve-order")]199 preserve_order: false,200 #[cfg(feature = "exp-bigint")]201 preserve_bigints: false,202 }203 }204}205206pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {207 let mut out = String::new();208 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;209 Ok(out)210}211212#[allow(clippy::too_many_lines)]213fn manifest_json_ex_buf(214 val: &Val,215 buf: &mut String,216 cur_padding: &mut String,217 options: &JsonFormat<'_>,218) -> Result<()> {219 use JsonFormatting::*;220221 let mtype = options.mtype;222 match val {223 Val::Bool(v) => {224 if *v {225 buf.push_str("true");226 } else {227 buf.push_str("false");228 }229 }230 Val::Null => buf.push_str("null"),231 Val::Str(s) => {232 buf.reserve(2 + s.len());233 buf.push('"');234 s.chunks(&mut |c| {235 escape_string_json_buf_raw(c, buf);236 });237 buf.push('"');238 }239 Val::Num(n) => write!(buf, "{n}").unwrap(),240 #[cfg(feature = "exp-bigint")]241 Val::BigInt(n) => {242 if options.preserve_bigints {243 write!(buf, "{n}").unwrap();244 } else {245 write!(buf, "{:?}", n.to_string()).unwrap();246 }247 }248 Val::Arr(items) => {249 buf.push('[');250251 let old_len = cur_padding.len();252 cur_padding.push_str(&options.padding);253254 let mut had_items = false;255 for (i, item) in items.iter().enumerate() {256 had_items = true;257 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;258259 if i != 0 {260 buf.push(',');261 }262 match mtype {263 Manifest | Std => {264 buf.push_str(options.newline);265 buf.push_str(cur_padding);266 }267 ToString if i != 0 => buf.push(' '),268 Minify | ToString => {}269 }270271 in_description_frame(272 || format!("elem <{i}> manifestification"),273 || manifest_json_ex_buf(&item, buf, cur_padding, options),274 )?;275 }276277 cur_padding.truncate(old_len);278279 match mtype {280 Manifest | ToString if !had_items => {281 282 buf.push(' ');283 }284 Manifest => {285 buf.push_str(options.newline);286 buf.push_str(cur_padding);287 }288 Std => {289 if !had_items {290 291 buf.push_str(options.newline);292 }293 buf.push_str(options.newline);294 buf.push_str(cur_padding);295 }296 Minify | ToString => {}297 }298299 buf.push(']');300 }301 Val::Obj(obj) => {302 obj.run_assertions()?;303 buf.push('{');304305 let old_len = cur_padding.len();306 cur_padding.push_str(&options.padding);307308 let mut had_fields = false;309 for (i, (key, value)) in obj310 .iter(311 #[cfg(feature = "exp-preserve-order")]312 options.preserve_order,313 )314 .enumerate()315 {316 had_fields = true;317 let value = value.with_description(|| format!("field <{key}> evaluation"))?;318319 if i != 0 {320 buf.push(',');321 }322 match mtype {323 Manifest | Std => {324 buf.push_str(options.newline);325 buf.push_str(cur_padding);326 }327 ToString if i != 0 => buf.push(' '),328 Minify | ToString => {}329 }330331 escape_string_json_buf(&key, buf);332 buf.push_str(options.key_val_sep);333 in_description_frame(334 || format!("field <{key}> manifestification"),335 || manifest_json_ex_buf(&value, buf, cur_padding, options),336 )?;337 }338339 cur_padding.truncate(old_len);340341 match mtype {342 Manifest | ToString if !had_fields => {343 344 buf.push(' ');345 }346 Manifest => {347 buf.push_str(options.newline);348 buf.push_str(cur_padding);349 }350 Std => {351 if !had_fields {352 353 buf.push_str(options.newline);354 }355 buf.push_str(options.newline);356 buf.push_str(cur_padding);357 }358 Minify | ToString => {}359 }360361 buf.push('}');362 }363 Val::Func(_) => bail!("tried to manifest function"),364 }365 Ok(())366}367368impl ManifestFormat for JsonFormat<'_> {369 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {370 manifest_json_ex_buf(&val, buf, &mut String::new(), self)371 }372}373374375376pub struct ToStringFormat;377impl ManifestFormat for ToStringFormat {378 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {379 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();380 if let Some(str) = val.as_str() {381 out.push_str(&str);382 return Ok(());383 }384 #[cfg(feature = "exp-bigint")]385 if let Some(int) = val.as_bigint() {386 out.push_str(&int.to_str_radix(10));387 return Ok(());388 }389 JSON_TO_STRING.manifest_buf(val, out)390 }391 fn file_trailing_newline(&self) -> bool {392 false393 }394}395pub struct StringFormat;396impl ManifestFormat for StringFormat {397 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {398 let Val::Str(s) = val else {399 bail!(400 "output should be string for string manifest format, got {}",401 val.value_type()402 )403 };404 write!(out, "{s}").unwrap();405 Ok(())406 }407 fn file_trailing_newline(&self) -> bool {408 false409 }410}411412pub struct YamlStreamFormat<I> {413 inner: I,414 c_document_end: bool,415 end_newline: bool,416}417impl<I> YamlStreamFormat<I> {418 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {419 Self {420 inner,421 c_document_end,422 423 end_newline: true,424 }425 }426 pub fn cli(inner: I) -> Self {427 Self {428 inner,429 c_document_end: true,430 end_newline: false,431 }432 }433}434impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {435 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {436 let Val::Arr(arr) = val else {437 bail!(438 "output should be array for yaml stream format, got {}",439 val.value_type()440 )441 };442 for (i, v) in arr.iter().enumerate() {443 if i != 0 {444 out.push('\n');445 }446 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;447 out.push_str("---\n");448 in_description_frame(449 || format!("elem <{i}> manifestification"),450 || self.inner.manifest_buf(v, out),451 )?;452 }453 if self.c_document_end {454 out.push('\n');455 out.push_str("...");456 }457 if self.end_newline {458 out.push('\n');459 }460 Ok(())461 }462}463464pub fn escape_string_json(s: &str) -> String {465 let mut buf = String::new();466 escape_string_json_buf(s, &mut buf);467 buf468}469470471472const BB: u8 = b'b'; 473const TT: u8 = b't'; 474const NN: u8 = b'n'; 475const FF: u8 = b'f'; 476const RR: u8 = b'r'; 477const QU: u8 = b'"'; 478const BS: u8 = b'\\'; 479const UU: u8 = b'u'; 480const __: u8 = 0;481482483484static ESCAPE: [u8; 256] = [485 486 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 487 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 488 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 489 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 490 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 491 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 492 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 493 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 494 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 495 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 496 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 497 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 498 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 499 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 500 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 501 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 502];503504pub fn escape_string_json_buf(value: &str, buf: &mut String) {505 buf.reserve_exact(value.len() + 2);506 escape_string_json_buf_raw(value, buf);507}508509fn escape_string_json_buf_raw(value: &str, buf: &mut String) {510 511 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };512 let bytes = value.as_bytes();513514 let mut start = 0;515516 for (i, &byte) in bytes.iter().enumerate() {517 let escape = ESCAPE[byte as usize];518 if escape == __ {519 continue;520 }521522 if start < i {523 buf.extend_from_slice(&bytes[start..i]);524 }525 start = i + 1;526527 match escape {528 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {529 buf.extend_from_slice(&[b'\\', escape]);530 }531 self::UU => {532 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";533 let bytes = &[534 b'\\',535 b'u',536 b'0',537 b'0',538 HEX_DIGITS[(byte >> 4) as usize],539 HEX_DIGITS[(byte & 0xF) as usize],540 ];541 buf.extend_from_slice(bytes);542 }543 _ => unreachable!(),544 }545 }546547 if start == bytes.len() {548 return;549 }550551 buf.extend_from_slice(&bytes[start..]);552}