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}7071impl<'s> JsonFormat<'s> {72 73 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {74 Self {75 padding: Cow::Borrowed(""),76 mtype: JsonFormatting::Minify,77 newline: "\n",78 key_val_sep: ":",79 #[cfg(feature = "exp-preserve-order")]80 preserve_order,81 #[cfg(feature = "exp-bigint")]82 preserve_bigints: false,83 }84 }85 86 pub fn std_to_string() -> Self {87 Self {88 padding: Cow::Borrowed(""),89 mtype: JsonFormatting::ToString,90 newline: "\n",91 key_val_sep: ": ",92 #[cfg(feature = "exp-preserve-order")]93 preserve_order: false,94 #[cfg(feature = "exp-bigint")]95 preserve_bigints: false,96 }97 }98 pub fn std_to_json(99 padding: String,100 newline: &'s str,101 key_val_sep: &'s str,102 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,103 ) -> Self {104 Self {105 padding: Cow::Owned(padding),106 mtype: JsonFormatting::Std,107 newline,108 key_val_sep,109 #[cfg(feature = "exp-preserve-order")]110 preserve_order,111 #[cfg(feature = "exp-bigint")]112 preserve_bigints: false,113 }114 }115 116 pub fn cli(117 padding: usize,118 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,119 ) -> Self {120 if padding == 0 {121 return Self::minify(122 #[cfg(feature = "exp-preserve-order")]123 preserve_order,124 );125 }126 Self {127 padding: Cow::Owned(" ".repeat(padding)),128 mtype: JsonFormatting::Manifest,129 newline: "\n",130 key_val_sep: ": ",131 #[cfg(feature = "exp-preserve-order")]132 preserve_order,133 #[cfg(feature = "exp-bigint")]134 preserve_bigints: false,135 }136 }137}138impl Default for JsonFormat<'static> {139 fn default() -> Self {140 Self {141 padding: Cow::Borrowed(" "),142 mtype: JsonFormatting::Manifest,143 newline: "\n",144 key_val_sep: ": ",145 #[cfg(feature = "exp-preserve-order")]146 preserve_order: false,147 #[cfg(feature = "exp-bigint")]148 preserve_bigints: false,149 }150 }151}152153pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {154 let mut out = String::new();155 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;156 Ok(out)157}158fn manifest_json_ex_buf(159 val: &Val,160 buf: &mut String,161 cur_padding: &mut String,162 options: &JsonFormat<'_>,163) -> Result<()> {164 let mtype = options.mtype;165 match val {166 Val::Bool(v) => {167 if *v {168 buf.push_str("true");169 } else {170 buf.push_str("false");171 }172 }173 Val::Null => buf.push_str("null"),174 Val::Str(s) => escape_string_json_buf(&s.clone().into_flat(), buf),175 Val::Num(n) => write!(buf, "{n}").unwrap(),176 #[cfg(feature = "exp-bigint")]177 Val::BigInt(n) => {178 if options.preserve_bigints {179 write!(buf, "{n}").unwrap()180 } else {181 write!(buf, "{:?}", n.to_string()).unwrap()182 }183 }184 Val::Arr(items) => {185 buf.push('[');186 if !items.is_empty() {187 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {188 buf.push_str(options.newline);189 }190191 let old_len = cur_padding.len();192 cur_padding.push_str(&options.padding);193 for (i, item) in items.iter().enumerate() {194 if i != 0 {195 buf.push(',');196 if mtype == JsonFormatting::ToString {197 buf.push(' ');198 } else if mtype != JsonFormatting::Minify {199 buf.push_str(options.newline);200 }201 }202 buf.push_str(cur_padding);203 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;204 }205 cur_padding.truncate(old_len);206207 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {208 buf.push_str(options.newline);209 buf.push_str(cur_padding);210 }211 } else if mtype == JsonFormatting::Std {212 buf.push_str(options.newline);213 buf.push_str(options.newline);214 buf.push_str(cur_padding);215 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {216 buf.push(' ');217 }218 buf.push(']');219 }220 Val::Obj(obj) => {221 obj.run_assertions()?;222 buf.push('{');223 let fields = obj.fields(224 #[cfg(feature = "exp-preserve-order")]225 options.preserve_order,226 );227 if !fields.is_empty() {228 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {229 buf.push_str(options.newline);230 }231232 let old_len = cur_padding.len();233 cur_padding.push_str(&options.padding);234 for (i, field) in fields.into_iter().enumerate() {235 if i != 0 {236 buf.push(',');237 if mtype == JsonFormatting::ToString {238 buf.push(' ');239 } else if mtype != JsonFormatting::Minify {240 buf.push_str(options.newline);241 }242 }243 buf.push_str(cur_padding);244 escape_string_json_buf(&field, buf);245 buf.push_str(options.key_val_sep);246 State::push_description(247 || format!("field <{}> manifestification", field.clone()),248 || {249 let value = obj.get(field.clone())?.unwrap();250 manifest_json_ex_buf(&value, buf, cur_padding, options)?;251 Ok(())252 },253 )?;254 }255 cur_padding.truncate(old_len);256257 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {258 buf.push_str(options.newline);259 buf.push_str(cur_padding);260 }261 } else if mtype == JsonFormatting::Std {262 buf.push_str(options.newline);263 buf.push_str(options.newline);264 buf.push_str(cur_padding);265 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {266 buf.push(' ');267 }268 buf.push('}');269 }270 Val::Func(_) => bail!("tried to manifest function"),271 };272 Ok(())273}274275impl ManifestFormat for JsonFormat<'_> {276 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {277 manifest_json_ex_buf(&val, buf, &mut String::new(), self)278 }279}280281pub struct ToStringFormat;282impl ManifestFormat for ToStringFormat {283 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {284 JsonFormat::std_to_string().manifest_buf(val, out)285 }286 fn file_trailing_newline(&self) -> bool {287 false288 }289}290pub struct StringFormat;291impl ManifestFormat for StringFormat {292 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {293 let Val::Str(s) = val else {294 bail!(295 "output should be string for string manifest format, got {}",296 val.value_type()297 )298 };299 write!(out, "{s}").unwrap();300 Ok(())301 }302 fn file_trailing_newline(&self) -> bool {303 false304 }305}306307pub struct YamlStreamFormat<I>(pub I);308impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {309 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {310 let Val::Arr(arr) = val else {311 bail!(312 "output should be array for yaml stream format, got {}",313 val.value_type()314 )315 };316 if !arr.is_empty() {317 for v in arr.iter() {318 let v = v?;319 out.push_str("---\n");320 self.0.manifest_buf(v, out)?;321 out.push('\n');322 }323 out.push_str("...");324 }325 Ok(())326 }327}328329pub fn escape_string_json(s: &str) -> String {330 let mut buf = String::new();331 escape_string_json_buf(s, &mut buf);332 buf333}334335336337const BB: u8 = b'b'; 338const TT: u8 = b't'; 339const NN: u8 = b'n'; 340const FF: u8 = b'f'; 341const RR: u8 = b'r'; 342const QU: u8 = b'"'; 343const BS: u8 = b'\\'; 344const UU: u8 = b'u'; 345const __: u8 = 0;346347348349static ESCAPE: [u8; 256] = [350 351 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 352 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 353 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 354 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 355 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 356 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 357 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 358 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 359 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 360 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 361 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 362 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 363 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 364 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 365 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 366 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 367];368369pub fn escape_string_json_buf(value: &str, buf: &mut String) {370 371 let buf: &mut Vec<u8> = unsafe { &mut *(buf as *mut String).cast::<Vec<u8>>() };372 let bytes = value.as_bytes();373374 375 buf.reserve(value.len() + 2);376377 buf.push(b'"');378379 let mut start = 0;380381 for (i, &byte) in bytes.iter().enumerate() {382 let escape = ESCAPE[byte as usize];383 if escape == __ {384 continue;385 }386387 if start < i {388 buf.extend_from_slice(&bytes[start..i]);389 }390 start = i + 1;391392 match escape {393 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {394 buf.extend_from_slice(&[b'\\', escape]);395 }396 self::UU => {397 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";398 let bytes = &[399 b'\\',400 b'u',401 b'0',402 b'0',403 HEX_DIGITS[(byte >> 4) as usize],404 HEX_DIGITS[(byte & 0xF) as usize],405 ];406 buf.extend_from_slice(bytes);407 }408 _ => unreachable!(),409 }410 }411412 if start == bytes.len() {413 buf.push(b'"');414 return;415 }416417 buf.extend_from_slice(&bytes[start..]);418 buf.push(b'"');419}