difftreelog
fix spacing in array toString
in: master
1 file changed
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth1use 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 /// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13 /// the meaning).14 ///15 /// Default implementation returns `true`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 // Applied in manifestification50 Manifest,51 /// Used for std.manifestJson52 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest53 Std,54 /// No line breaks, used in `obj+''`55 ToString,56 /// Minified json57 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 // Minifying format74 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 // Same format as std.toString88 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 // Same format as CLI manifestification120 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 // Same format as CLI manifestification143 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 => buf.push(' '),241 Minify => {}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 // Empty array as "[ ]"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 // Stdlib formats empty array as "[\n\n]"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 // Empty object as "{ }"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 // Stdlib formats empty object as "{\n\n}"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 // Stdlib format always inserts useless newline at the end384 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}429430// Json string encoding was borrowed from https://github.com/serde-rs/json431432const BB: u8 = b'b'; // \x08433const TT: u8 = b't'; // \x09434const NN: u8 = b'n'; // \x0A435const FF: u8 = b'f'; // \x0C436const RR: u8 = b'r'; // \x0D437const QU: u8 = b'"'; // \x22438const BS: u8 = b'\\'; // \x5C439const UU: u8 = b'u'; // \x00...\x1F except the ones above440const __: u8 = 0;441442// Lookup table of escape sequences. A value of b'x' at index i means that byte443// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.444static ESCAPE: [u8; 256] = [445 // 1 2 3 4 5 6 7 8 9 A B C D E F446 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0447 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1448 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2449 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3450 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4451 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5452 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6453 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7454 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8455 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9456 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A457 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B458 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C459 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D460 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E461 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F462];463464pub fn escape_string_json_buf(value: &str, buf: &mut String) {465 // Safety: we only write correct utf-8 in this function466 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };467 let bytes = value.as_bytes();468469 // Perfect for ascii strings, removes any reallocations470 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}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 /// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13 /// the meaning).14 ///15 /// Default implementation returns `true`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 // Applied in manifestification50 Manifest,51 /// Used for std.manifestJson52 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest53 Std,54 /// No line breaks, used in `obj+''`55 ToString,56 /// Minified json57 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 // Minifying format74 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 // Same format as std.toString88 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 // Same format as CLI manifestification120 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 // Same format as CLI manifestification143 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 // Empty array as "[ ]"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 // Stdlib formats empty array as "[\n\n]"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 // Empty object as "{ }"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 // Stdlib formats empty object as "{\n\n}"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 // Stdlib format always inserts useless newline at the end384 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}429430// Json string encoding was borrowed from https://github.com/serde-rs/json431432const BB: u8 = b'b'; // \x08433const TT: u8 = b't'; // \x09434const NN: u8 = b'n'; // \x0A435const FF: u8 = b'f'; // \x0C436const RR: u8 = b'r'; // \x0D437const QU: u8 = b'"'; // \x22438const BS: u8 = b'\\'; // \x5C439const UU: u8 = b'u'; // \x00...\x1F except the ones above440const __: u8 = 0;441442// Lookup table of escape sequences. A value of b'x' at index i means that byte443// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.444static ESCAPE: [u8; 256] = [445 // 1 2 3 4 5 6 7 8 9 A B C D E F446 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0447 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1448 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2449 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3450 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4451 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5452 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6453 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7454 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8455 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9456 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A457 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B458 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C459 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D460 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E461 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F462];463464pub fn escape_string_json_buf(value: &str, buf: &mut String) {465 // Safety: we only write correct utf-8 in this function466 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };467 let bytes = value.as_bytes();468469 // Perfect for ascii strings, removes any reallocations470 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}