1use std::{borrow::Cow, fmt::Write};23use crate::{bail, Result, 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}178fn manifest_json_ex_buf(179 val: &Val,180 buf: &mut String,181 cur_padding: &mut String,182 options: &JsonFormat<'_>,183) -> Result<()> {184 let mtype = options.mtype;185 match val {186 Val::Bool(v) => {187 if *v {188 buf.push_str("true");189 } else {190 buf.push_str("false");191 }192 }193 Val::Null => buf.push_str("null"),194 Val::Str(s) => {195 let flat = s.clone().into_flat();196 if let Some(truncate) = options.debug_truncate_strings {197 if flat.len() > truncate {198 let (start, end) = flat.split_at(truncate / 2);199 let (_, end) = end.split_at(end.len() - truncate / 2);200 escape_string_json_buf(&format!("{start}..{end}"), buf);201 } else {202 escape_string_json_buf(&flat, buf);203 }204 } else {205 escape_string_json_buf(&flat, buf);206 }207 }208 Val::Num(n) => write!(buf, "{n}").unwrap(),209 #[cfg(feature = "exp-bigint")]210 Val::BigInt(n) => {211 if options.preserve_bigints {212 write!(buf, "{n}").unwrap()213 } else {214 write!(buf, "{:?}", n.to_string()).unwrap()215 }216 }217 Val::Arr(items) => {218 buf.push('[');219 if !items.is_empty() {220 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {221 buf.push_str(options.newline);222 }223224 let old_len = cur_padding.len();225 cur_padding.push_str(&options.padding);226 for (i, item) in items.iter().enumerate() {227 if i != 0 {228 buf.push(',');229 if mtype == JsonFormatting::ToString {230 buf.push(' ');231 } else if mtype != JsonFormatting::Minify {232 buf.push_str(options.newline);233 }234 }235 buf.push_str(cur_padding);236 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;237 }238 cur_padding.truncate(old_len);239240 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {241 buf.push_str(options.newline);242 buf.push_str(cur_padding);243 }244 } else if mtype == JsonFormatting::Std {245 buf.push_str(options.newline);246 buf.push_str(options.newline);247 buf.push_str(cur_padding);248 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {249 buf.push(' ');250 }251 buf.push(']');252 }253 Val::Obj(obj) => {254 obj.run_assertions()?;255 buf.push('{');256 let fields = obj.fields(257 #[cfg(feature = "exp-preserve-order")]258 options.preserve_order,259 );260 if !fields.is_empty() {261 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {262 buf.push_str(options.newline);263 }264265 let old_len = cur_padding.len();266 cur_padding.push_str(&options.padding);267 for (i, field) in fields.into_iter().enumerate() {268 if i != 0 {269 buf.push(',');270 if mtype == JsonFormatting::ToString {271 buf.push(' ');272 } else if mtype != JsonFormatting::Minify {273 buf.push_str(options.newline);274 }275 }276 buf.push_str(cur_padding);277 escape_string_json_buf(&field, buf);278 buf.push_str(options.key_val_sep);279 State::push_description(280 || format!("field <{}> manifestification", field.clone()),281 || {282 let value = obj.get(field.clone())?.unwrap();283 manifest_json_ex_buf(&value, buf, cur_padding, options)?;284 Ok(())285 },286 )?;287 }288 cur_padding.truncate(old_len);289290 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {291 buf.push_str(options.newline);292 buf.push_str(cur_padding);293 }294 } else if mtype == JsonFormatting::Std {295 buf.push_str(options.newline);296 buf.push_str(options.newline);297 buf.push_str(cur_padding);298 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {299 buf.push(' ');300 }301 buf.push('}');302 }303 Val::Func(_) => bail!("tried to manifest function"),304 };305 Ok(())306}307308impl ManifestFormat for JsonFormat<'_> {309 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {310 manifest_json_ex_buf(&val, buf, &mut String::new(), self)311 }312}313314pub struct ToStringFormat;315impl ManifestFormat for ToStringFormat {316 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {317 JsonFormat::std_to_string().manifest_buf(val, out)318 }319 fn file_trailing_newline(&self) -> bool {320 false321 }322}323pub struct StringFormat;324impl ManifestFormat for StringFormat {325 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {326 let Val::Str(s) = val else {327 bail!(328 "output should be string for string manifest format, got {}",329 val.value_type()330 )331 };332 write!(out, "{s}").unwrap();333 Ok(())334 }335 fn file_trailing_newline(&self) -> bool {336 false337 }338}339340pub struct YamlStreamFormat<I>(pub I);341impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {342 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {343 let Val::Arr(arr) = val else {344 bail!(345 "output should be array for yaml stream format, got {}",346 val.value_type()347 )348 };349 if !arr.is_empty() {350 for v in arr.iter() {351 let v = v?;352 out.push_str("---\n");353 self.0.manifest_buf(v, out)?;354 out.push('\n');355 }356 out.push_str("...");357 }358 Ok(())359 }360}361362pub fn escape_string_json(s: &str) -> String {363 let mut buf = String::new();364 escape_string_json_buf(s, &mut buf);365 buf366}367368369370const BB: u8 = b'b'; 371const TT: u8 = b't'; 372const NN: u8 = b'n'; 373const FF: u8 = b'f'; 374const RR: u8 = b'r'; 375const QU: u8 = b'"'; 376const BS: u8 = b'\\'; 377const UU: u8 = b'u'; 378const __: u8 = 0;379380381382static ESCAPE: [u8; 256] = [383 384 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 385 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 386 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 387 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 388 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 389 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 390 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 391 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 392 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 393 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 394 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 395 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 396 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 397 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 398 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 399 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 400];401402pub fn escape_string_json_buf(value: &str, buf: &mut String) {403 404 let buf: &mut Vec<u8> = unsafe { &mut *(buf as *mut String).cast::<Vec<u8>>() };405 let bytes = value.as_bytes();406407 408 buf.reserve(value.len() + 2);409410 buf.push(b'"');411412 let mut start = 0;413414 for (i, &byte) in bytes.iter().enumerate() {415 let escape = ESCAPE[byte as usize];416 if escape == __ {417 continue;418 }419420 if start < i {421 buf.extend_from_slice(&bytes[start..i]);422 }423 start = i + 1;424425 match escape {426 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {427 buf.extend_from_slice(&[b'\\', escape]);428 }429 self::UU => {430 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";431 let bytes = &[432 b'\\',433 b'u',434 b'0',435 b'0',436 HEX_DIGITS[(byte >> 4) as usize],437 HEX_DIGITS[(byte & 0xF) as usize],438 ];439 buf.extend_from_slice(bytes);440 }441 _ => unreachable!(),442 }443 }444445 if start == bytes.len() {446 buf.push(b'"');447 return;448 }449450 buf.extend_from_slice(&bytes[start..]);451 buf.push(b'"');452}