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 if i != 0 => buf.push(' '),241 Minify | ToString => {}242 };243244 State::push_description(245 || format!("elem <{i}> manifestification"),246 || manifest_json_ex_buf(&item, buf, cur_padding, options),247 )?;248 }249250 cur_padding.truncate(old_len);251252 match mtype {253 Manifest | ToString if !had_items => {254 255 buf.push(' ');256 }257 Manifest => {258 buf.push_str(options.newline);259 buf.push_str(cur_padding);260 }261 Std => {262 if !had_items {263 264 buf.push_str(options.newline);265 }266 buf.push_str(options.newline);267 buf.push_str(cur_padding);268 }269 Minify | ToString => {}270 }271272 buf.push(']');273 }274 Val::Obj(obj) => {275 obj.run_assertions()?;276 buf.push('{');277278 let old_len = cur_padding.len();279 cur_padding.push_str(&options.padding);280281 let mut had_fields = false;282 for (i, (key, value)) in obj283 .iter(284 #[cfg(feature = "exp-preserve-order")]285 options.preserve_order,286 )287 .enumerate()288 {289 had_fields = true;290 let value = value.with_description(|| format!("field <{key}> evaluation"))?;291292 if i != 0 {293 buf.push(',');294 }295 match mtype {296 Manifest | Std => {297 buf.push_str(options.newline);298 buf.push_str(cur_padding);299 }300 ToString if i != 0 => buf.push(' '),301 Minify | ToString => {}302 }303304 escape_string_json_buf(&key, buf);305 buf.push_str(options.key_val_sep);306 State::push_description(307 || format!("field <{key}> manifestification"),308 || manifest_json_ex_buf(&value, buf, cur_padding, options),309 )?;310 }311312 cur_padding.truncate(old_len);313314 match mtype {315 Manifest | ToString if !had_fields => {316 317 buf.push(' ');318 }319 Manifest => {320 buf.push_str(options.newline);321 buf.push_str(cur_padding);322 }323 Std => {324 if !had_fields {325 326 buf.push_str(options.newline);327 }328 buf.push_str(options.newline);329 buf.push_str(cur_padding);330 }331 Minify | ToString => {}332 }333334 buf.push('}');335 }336 Val::Func(_) => bail!("tried to manifest function"),337 };338 Ok(())339}340341impl ManifestFormat for JsonFormat<'_> {342 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {343 manifest_json_ex_buf(&val, buf, &mut String::new(), self)344 }345}346347pub struct ToStringFormat;348impl ManifestFormat for ToStringFormat {349 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {350 JsonFormat::std_to_string().manifest_buf(val, out)351 }352 fn file_trailing_newline(&self) -> bool {353 false354 }355}356pub struct StringFormat;357impl ManifestFormat for StringFormat {358 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {359 let Val::Str(s) = val else {360 bail!(361 "output should be string for string manifest format, got {}",362 val.value_type()363 )364 };365 write!(out, "{s}").unwrap();366 Ok(())367 }368 fn file_trailing_newline(&self) -> bool {369 false370 }371}372373pub struct YamlStreamFormat<I> {374 inner: I,375 c_document_end: bool,376 end_newline: bool,377}378impl<I> YamlStreamFormat<I> {379 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {380 Self {381 inner,382 c_document_end,383 384 end_newline: true,385 }386 }387 pub fn cli(inner: I) -> Self {388 Self {389 inner,390 c_document_end: true,391 end_newline: false,392 }393 }394}395impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {396 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {397 let Val::Arr(arr) = val else {398 bail!(399 "output should be array for yaml stream format, got {}",400 val.value_type()401 )402 };403 if !arr.is_empty() {404 for (i, v) in arr.iter().enumerate() {405 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;406 out.push_str("---\n");407 State::push_description(408 || format!("elem <{i}> manifestification"),409 || self.inner.manifest_buf(v, out),410 )?;411 out.push('\n');412 }413 }414 if self.c_document_end {415 out.push_str("...");416 }417 if self.end_newline {418 out.push('\n');419 }420 Ok(())421 }422}423424pub fn escape_string_json(s: &str) -> String {425 let mut buf = String::new();426 escape_string_json_buf(s, &mut buf);427 buf428}429430431432const BB: u8 = b'b'; 433const TT: u8 = b't'; 434const NN: u8 = b'n'; 435const FF: u8 = b'f'; 436const RR: u8 = b'r'; 437const QU: u8 = b'"'; 438const BS: u8 = b'\\'; 439const UU: u8 = b'u'; 440const __: u8 = 0;441442443444static ESCAPE: [u8; 256] = [445 446 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 447 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 448 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 449 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 450 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 451 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 452 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 453 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 454 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 455 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 456 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 457 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 458 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 459 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 460 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 461 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 462];463464pub fn escape_string_json_buf(value: &str, buf: &mut String) {465 466 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };467 let bytes = value.as_bytes();468469 470 buf.reserve(value.len() + 2);471472 buf.push(b'"');473474 let mut start = 0;475476 for (i, &byte) in bytes.iter().enumerate() {477 let escape = ESCAPE[byte as usize];478 if escape == __ {479 continue;480 }481482 if start < i {483 buf.extend_from_slice(&bytes[start..i]);484 }485 start = i + 1;486487 match escape {488 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {489 buf.extend_from_slice(&[b'\\', escape]);490 }491 self::UU => {492 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";493 let bytes = &[494 b'\\',495 b'u',496 b'0',497 b'0',498 HEX_DIGITS[(byte >> 4) as usize],499 HEX_DIGITS[(byte & 0xF) as usize],500 ];501 buf.extend_from_slice(bytes);502 }503 _ => unreachable!(),504 }505 }506507 if start == bytes.len() {508 buf.push(b'"');509 return;510 }511512 buf.extend_from_slice(&bytes[start..]);513 buf.push(b'"');514}