1use std::{borrow::Cow, fmt::Write};23use crate::{4 error::{ErrorKind::*, Result},5 throw, State, Val,6};78pub trait ManifestFormat {9 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;10 fn manifest(&self, val: Val) -> Result<String> {11 let mut out = String::new();12 self.manifest_buf(val, &mut out)?;13 Ok(out)14 }15 16 17 18 19 fn file_trailing_newline(&self) -> bool {20 true21 }22}23impl<T> ManifestFormat for Box<T>24where25 T: ManifestFormat + ?Sized,26{27 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {28 let inner = &**self;29 inner.manifest_buf(val, buf)30 }31 fn file_trailing_newline(&self) -> bool {32 let inner = &**self;33 inner.file_trailing_newline()34 }35}36impl<T> ManifestFormat for &'_ T37where38 T: ManifestFormat + ?Sized,39{40 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {41 let inner = &**self;42 inner.manifest_buf(val, buf)43 }44 fn file_trailing_newline(&self) -> bool {45 let inner = &**self;46 inner.file_trailing_newline()47 }48}4950#[derive(PartialEq, Eq, Clone, Copy)]51enum JsonFormatting {52 53 Manifest,54 55 56 Std,57 58 ToString,59 60 Minify,61}6263pub struct JsonFormat<'s> {64 padding: Cow<'s, str>,65 mtype: JsonFormatting,66 newline: &'s str,67 key_val_sep: &'s str,68 #[cfg(feature = "exp-preserve-order")]69 preserve_order: bool,70 #[cfg(feature = "exp-bigint")]71 preserve_bigints: bool,72}7374impl<'s> JsonFormat<'s> {75 76 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {77 Self {78 padding: Cow::Borrowed(""),79 mtype: JsonFormatting::Minify,80 newline: "\n",81 key_val_sep: ":",82 #[cfg(feature = "exp-preserve-order")]83 preserve_order,84 #[cfg(feature = "exp-bigint")]85 preserve_bigints: false,86 }87 }88 89 pub fn std_to_string() -> Self {90 Self {91 padding: Cow::Borrowed(""),92 mtype: JsonFormatting::ToString,93 newline: "\n",94 key_val_sep: ": ",95 #[cfg(feature = "exp-preserve-order")]96 preserve_order: false,97 #[cfg(feature = "exp-bigint")]98 preserve_bigints: false,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 }117 }118 119 pub fn cli(120 padding: usize,121 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,122 ) -> Self {123 if padding == 0 {124 return Self::minify(125 #[cfg(feature = "exp-preserve-order")]126 preserve_order,127 );128 }129 Self {130 padding: Cow::Owned(" ".repeat(padding)),131 mtype: JsonFormatting::Manifest,132 newline: "\n",133 key_val_sep: ": ",134 #[cfg(feature = "exp-preserve-order")]135 preserve_order,136 #[cfg(feature = "exp-bigint")]137 preserve_bigints: false,138 }139 }140}141impl Default for JsonFormat<'static> {142 fn default() -> Self {143 Self {144 padding: Cow::Borrowed(" "),145 mtype: JsonFormatting::Manifest,146 newline: "\n",147 key_val_sep: ": ",148 #[cfg(feature = "exp-preserve-order")]149 preserve_order: false,150 #[cfg(feature = "exp-bigint")]151 preserve_bigints: false,152 }153 }154}155156pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {157 let mut out = String::new();158 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;159 Ok(out)160}161fn manifest_json_ex_buf(162 val: &Val,163 buf: &mut String,164 cur_padding: &mut String,165 options: &JsonFormat<'_>,166) -> Result<()> {167 let mtype = options.mtype;168 match val {169 Val::Bool(v) => {170 if *v {171 buf.push_str("true");172 } else {173 buf.push_str("false");174 }175 }176 Val::Null => buf.push_str("null"),177 Val::Str(s) => escape_string_json_buf(&s.clone().into_flat(), buf),178 Val::Num(n) => write!(buf, "{n}").unwrap(),179 #[cfg(feature = "exp-bigint")]180 Val::BigInt(n) => if options.preserve_bigints {181 write!(buf, "{n}").unwrap()182 } else {183 write!(buf, "{:?}", n.to_string()).unwrap()184 },185 Val::Arr(items) => {186 buf.push('[');187 if !items.is_empty() {188 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {189 buf.push_str(options.newline);190 }191192 let old_len = cur_padding.len();193 cur_padding.push_str(&options.padding);194 for (i, item) in items.iter().enumerate() {195 if i != 0 {196 buf.push(',');197 if mtype == JsonFormatting::ToString {198 buf.push(' ');199 } else if mtype != JsonFormatting::Minify {200 buf.push_str(options.newline);201 }202 }203 buf.push_str(cur_padding);204 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;205 }206 cur_padding.truncate(old_len);207208 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {209 buf.push_str(options.newline);210 buf.push_str(cur_padding);211 }212 } else if mtype == JsonFormatting::Std {213 buf.push_str(options.newline);214 buf.push_str(options.newline);215 buf.push_str(cur_padding);216 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {217 buf.push(' ');218 }219 buf.push(']');220 }221 Val::Obj(obj) => {222 obj.run_assertions()?;223 buf.push('{');224 let fields = obj.fields(225 #[cfg(feature = "exp-preserve-order")]226 options.preserve_order,227 );228 if !fields.is_empty() {229 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {230 buf.push_str(options.newline);231 }232233 let old_len = cur_padding.len();234 cur_padding.push_str(&options.padding);235 for (i, field) in fields.into_iter().enumerate() {236 if i != 0 {237 buf.push(',');238 if mtype == JsonFormatting::ToString {239 buf.push(' ');240 } else if mtype != JsonFormatting::Minify {241 buf.push_str(options.newline);242 }243 }244 buf.push_str(cur_padding);245 escape_string_json_buf(&field, buf);246 buf.push_str(options.key_val_sep);247 State::push_description(248 || format!("field <{}> manifestification", field.clone()),249 || {250 let value = obj.get(field.clone())?.unwrap();251 manifest_json_ex_buf(&value, buf, cur_padding, options)?;252 Ok(())253 },254 )?;255 }256 cur_padding.truncate(old_len);257258 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {259 buf.push_str(options.newline);260 buf.push_str(cur_padding);261 }262 } else if mtype == JsonFormatting::Std {263 buf.push_str(options.newline);264 buf.push_str(options.newline);265 buf.push_str(cur_padding);266 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {267 buf.push(' ');268 }269 buf.push('}');270 }271 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),272 };273 Ok(())274}275276impl ManifestFormat for JsonFormat<'_> {277 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {278 manifest_json_ex_buf(&val, buf, &mut String::new(), self)279 }280}281282pub struct ToStringFormat;283impl ManifestFormat for ToStringFormat {284 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {285 JsonFormat::std_to_string().manifest_buf(val, out)286 }287 fn file_trailing_newline(&self) -> bool {288 false289 }290}291pub struct StringFormat;292impl ManifestFormat for StringFormat {293 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {294 let Val::Str(s) = val else {295 throw!(296 "output should be string for string manifest format, got {}",297 val.value_type()298 )299 };300 write!(out, "{s}").unwrap();301 Ok(())302 }303 fn file_trailing_newline(&self) -> bool {304 false305 }306}307308pub struct YamlStreamFormat<I>(pub I);309impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {310 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {311 let Val::Arr(arr) = val else {312 throw!(313 "output should be array for yaml stream format, got {}",314 val.value_type()315 )316 };317 if !arr.is_empty() {318 for v in arr.iter() {319 let v = v?;320 out.push_str("---\n");321 self.0.manifest_buf(v, out)?;322 out.push('\n');323 }324 out.push_str("...");325 }326 Ok(())327 }328}329330pub fn escape_string_json(s: &str) -> String {331 let mut buf = String::new();332 escape_string_json_buf(s, &mut buf);333 buf334}335336337338const BB: u8 = b'b'; 339const TT: u8 = b't'; 340const NN: u8 = b'n'; 341const FF: u8 = b'f'; 342const RR: u8 = b'r'; 343const QU: u8 = b'"'; 344const BS: u8 = b'\\'; 345const UU: u8 = b'u'; 346const __: u8 = 0;347348349350static ESCAPE: [u8; 256] = [351 352 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 353 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 354 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 355 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 356 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 357 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 358 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 359 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 360 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 361 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 362 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 363 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 364 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 365 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 366 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 367 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 368];369370pub fn escape_string_json_buf(value: &str, buf: &mut String) {371 372 let buf: &mut Vec<u8> = unsafe { &mut *(buf as *mut String).cast::<Vec<u8>>() };373 let bytes = value.as_bytes();374375 376 buf.reserve(value.len() + 2);377378 buf.push(b'"');379380 let mut start = 0;381382 for (i, &byte) in bytes.iter().enumerate() {383 let escape = ESCAPE[byte as usize];384 if escape == __ {385 continue;386 }387388 if start < i {389 buf.extend_from_slice(&bytes[start..i]);390 }391 start = i + 1;392393 match escape {394 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {395 buf.extend_from_slice(&[b'\\', escape]);396 }397 self::UU => {398 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";399 let bytes = &[400 b'\\',401 b'u',402 b'0',403 b'0',404 HEX_DIGITS[(byte >> 4) as usize],405 HEX_DIGITS[(byte & 0xF) as usize],406 ];407 buf.extend_from_slice(bytes);408 }409 _ => unreachable!(),410 }411 }412413 if start == bytes.len() {414 buf.push(b'"');415 return;416 }417418 buf.extend_from_slice(&bytes[start..]);419 buf.push(b'"');420}