difftreelog
perf adopt string escape code from serde-json
in: master
1 file changed
crates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth1use std::{borrow::Cow, fmt::Write};23use crate::{4 error::{Error::*, Result},5 throw, ManifestFormat, State, Val,6};78#[derive(PartialEq, Eq, Clone, Copy)]9pub enum ManifestType {10 // Applied in manifestification11 Manifest,12 /// Used for std.manifestJson13 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest14 Std,15 /// No line breaks, used in `obj+''`16 ToString,17 /// Minified json18 Minify,19}2021pub struct JsonFormat<'s> {22 padding: Cow<'s, str>,23 mtype: ManifestType,24 newline: &'s str,25 key_val_sep: &'s str,26 #[cfg(feature = "exp-preserve-order")]27 preserve_order: bool,28}2930impl<'s> JsonFormat<'s> {31 // Minifying format32 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {33 Self {34 padding: Cow::Borrowed(""),35 mtype: ManifestType::Minify,36 newline: "\n",37 key_val_sep: ":",38 #[cfg(feature = "exp-preserve-order")]39 preserve_order,40 }41 }42 // Same format as std.toString43 pub fn std_to_string() -> Self {44 Self {45 padding: Cow::Borrowed(""),46 mtype: ManifestType::ToString,47 newline: "\n",48 key_val_sep: ": ",49 #[cfg(feature = "exp-preserve-order")]50 preserve_order: false,51 }52 }53 pub fn std_to_json(54 padding: String,55 newline: &'s str,56 key_val_sep: &'s str,57 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,58 ) -> Self {59 Self {60 padding: Cow::Owned(padding),61 mtype: ManifestType::Std,62 newline,63 key_val_sep,64 #[cfg(feature = "exp-preserve-order")]65 preserve_order,66 }67 }68 // Same format as CLI manifestification69 pub fn cli(70 padding: usize,71 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,72 ) -> Self {73 if padding == 0 {74 return Self::minify(75 #[cfg(feature = "exp-preserve-order")]76 preserve_order,77 );78 }79 Self {80 padding: Cow::Owned(" ".repeat(padding)),81 mtype: ManifestType::Manifest,82 newline: "\n",83 key_val_sep: ": ",84 #[cfg(feature = "exp-preserve-order")]85 preserve_order,86 }87 }88}89impl Default for JsonFormat<'static> {90 fn default() -> Self {91 Self {92 padding: Cow::Borrowed(" "),93 mtype: ManifestType::Manifest,94 newline: "\n",95 key_val_sep: ": ",96 #[cfg(feature = "exp-preserve-order")]97 preserve_order: false,98 }99 }100}101102pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {103 let mut out = String::new();104 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;105 Ok(out)106}107fn manifest_json_ex_buf(108 val: &Val,109 buf: &mut String,110 cur_padding: &mut String,111 options: &JsonFormat<'_>,112) -> Result<()> {113 let mtype = options.mtype;114 match val {115 Val::Bool(v) => {116 if *v {117 buf.push_str("true");118 } else {119 buf.push_str("false");120 }121 }122 Val::Null => buf.push_str("null"),123 Val::Str(s) => escape_string_json_buf(s, buf),124 Val::Num(n) => write!(buf, "{n}").unwrap(),125 Val::Arr(items) => {126 buf.push('[');127 if !items.is_empty() {128 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {129 buf.push_str(options.newline);130 }131132 let old_len = cur_padding.len();133 cur_padding.push_str(&options.padding);134 for (i, item) in items.iter().enumerate() {135 if i != 0 {136 buf.push(',');137 if mtype == ManifestType::ToString {138 buf.push(' ');139 } else if mtype != ManifestType::Minify {140 buf.push_str(options.newline);141 }142 }143 buf.push_str(cur_padding);144 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;145 }146 cur_padding.truncate(old_len);147148 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {149 buf.push_str(options.newline);150 buf.push_str(cur_padding);151 }152 } else if mtype == ManifestType::Std {153 buf.push_str("\n\n");154 buf.push_str(cur_padding);155 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {156 buf.push(' ');157 }158 buf.push(']');159 }160 Val::Obj(obj) => {161 obj.run_assertions()?;162 buf.push('{');163 let fields = obj.fields(164 #[cfg(feature = "exp-preserve-order")]165 options.preserve_order,166 );167 if !fields.is_empty() {168 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {169 buf.push_str(options.newline);170 }171172 let old_len = cur_padding.len();173 cur_padding.push_str(&options.padding);174 for (i, field) in fields.into_iter().enumerate() {175 if i != 0 {176 buf.push(',');177 if mtype == ManifestType::ToString {178 buf.push(' ');179 } else if mtype != ManifestType::Minify {180 buf.push_str(options.newline);181 }182 }183 buf.push_str(cur_padding);184 escape_string_json_buf(&field, buf);185 buf.push_str(options.key_val_sep);186 State::push_description(187 || format!("field <{}> manifestification", field.clone()),188 || {189 let value = obj.get(field.clone())?.unwrap();190 manifest_json_ex_buf(&value, buf, cur_padding, options)?;191 Ok(())192 },193 )?;194 }195 cur_padding.truncate(old_len);196197 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {198 buf.push_str(options.newline);199 buf.push_str(cur_padding);200 }201 } else if mtype == ManifestType::Std {202 buf.push_str("\n\n");203 buf.push_str(cur_padding);204 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {205 buf.push(' ');206 }207 buf.push('}');208 }209 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),210 };211 Ok(())212}213214impl ManifestFormat for JsonFormat<'_> {215 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {216 manifest_json_ex_buf(&val, buf, &mut String::new(), &self)217 }218}219220pub struct ToStringFormat;221impl ManifestFormat for ToStringFormat {222 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {223 JsonFormat::std_to_string().manifest_buf(val, out)224 }225}226pub struct StringFormat;227impl ManifestFormat for StringFormat {228 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {229 let Val::Str(s) = val else {230 throw!("output should be string for string manifest format, got {}", val.value_type())231 };232 out.write_str(&s).unwrap();233 Ok(())234 }235}236237pub struct YamlStreamFormat<I>(pub I);238impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {239 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {240 let Val::Arr(arr) = val else {241 throw!("output should be array for yaml stream format, got {}", val.value_type())242 };243 if !arr.is_empty() {244 for v in arr.iter() {245 let v = v?;246 out.push_str("---\n");247 self.0.manifest_buf(v, out)?;248 out.push('\n');249 }250 out.push_str("...");251 }252 Ok(())253 }254}255256pub fn escape_string_json(s: &str) -> String {257 let mut buf = String::new();258 escape_string_json_buf(s, &mut buf);259 buf260}261262fn escape_string_json_buf(s: &str, buf: &mut String) {263 buf.push('"');264 for c in s.chars() {265 match c {266 '"' => buf.push_str("\\\""),267 '\\' => buf.push_str("\\\\"),268 '\u{0008}' => buf.push_str("\\b"),269 '\u{000c}' => buf.push_str("\\f"),270 '\n' => buf.push_str("\\n"),271 '\r' => buf.push_str("\\r"),272 '\t' => buf.push_str("\\t"),273 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {274 write!(buf, "\\u{:04x}", c as u32).unwrap();275 }276 c => buf.push(c),277 }278 }279 buf.push('"');280}281282pub struct YamlFormat<'s> {283 /// Padding before fields, i.e284 /// ```yaml285 /// a:286 /// b:287 /// ## <- this288 /// ```289 padding: Cow<'s, str>,290 /// Padding before array elements in objects291 /// ```yaml292 /// a:293 /// - 1294 /// ## <- this295 /// ```296 arr_element_padding: Cow<'s, str>,297 /// Should yaml keys appear unescaped, when possible298 /// ```yaml299 /// "safe_key": 1300 /// # vs301 /// safe_key: 1302 /// ```303 quote_keys: bool,304 /// If true - then order of fields is preserved as written,305 /// instead of sorting alphabetically306 #[cfg(feature = "exp-preserve-order")]307 preserve_order: bool,308}309impl YamlFormat<'_> {310 pub fn cli(311 padding: usize,312 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,313 ) -> Self {314 let padding = " ".repeat(padding);315 Self {316 padding: Cow::Owned(padding.clone()),317 arr_element_padding: Cow::Owned(padding),318 quote_keys: false,319 #[cfg(feature = "exp-preserve-order")]320 preserve_order,321 }322 }323 pub fn std_to_yaml(324 indent_array_in_object: bool,325 quote_keys: bool,326 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,327 ) -> Self {328 Self {329 padding: Cow::Borrowed(" "),330 arr_element_padding: Cow::Borrowed(if indent_array_in_object { " " } else { "" }),331 quote_keys,332 #[cfg(feature = "exp-preserve-order")]333 preserve_order,334 }335 }336}337impl ManifestFormat for YamlFormat<'_> {338 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {339 manifest_yaml_ex_buf(&val, buf, &mut String::new(), self)340 }341}342343/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>344/// With added date check345fn yaml_needs_quotes(string: &str) -> bool {346 fn need_quotes_spaces(string: &str) -> bool {347 string.starts_with(' ') || string.ends_with(' ')348 }349350 string.is_empty()351 || need_quotes_spaces(string)352 || string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))353 || string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))354 || [355 // http://yaml.org/type/bool.html356 // Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse357 // them as string, not booleans, although it is violating the YAML 1.1 specification.358 // See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.359 "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",360 "on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html361 "null", "Null", "NULL", "~",362 ].contains(&string)363 || (string.chars().all(|c| matches!(c, '0'..='9' | '-'))364 && string.chars().filter(|c| *c == '-').count() == 2)365 || string.starts_with('.')366 || string.starts_with("0x")367 || string.parse::<i64>().is_ok()368 || string.parse::<f64>().is_ok()369}370371pub fn manifest_yaml_ex(val: &Val, options: &YamlFormat<'_>) -> Result<String> {372 let mut out = String::new();373 manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;374 Ok(out)375}376377#[allow(clippy::too_many_lines)]378fn manifest_yaml_ex_buf(379 val: &Val,380 buf: &mut String,381 cur_padding: &mut String,382 options: &YamlFormat<'_>,383) -> Result<()> {384 match val {385 Val::Bool(v) => {386 if *v {387 buf.push_str("true");388 } else {389 buf.push_str("false");390 }391 }392 Val::Null => buf.push_str("null"),393 Val::Str(s) => {394 if s.is_empty() {395 buf.push_str("\"\"");396 } else if let Some(s) = s.strip_suffix('\n') {397 buf.push('|');398 for line in s.split('\n') {399 buf.push('\n');400 buf.push_str(cur_padding);401 buf.push_str(&options.padding);402 buf.push_str(line);403 }404 } else if !options.quote_keys && !yaml_needs_quotes(s) {405 buf.push_str(s);406 } else {407 escape_string_json_buf(s, buf);408 }409 }410 Val::Num(n) => write!(buf, "{}", *n).unwrap(),411 Val::Arr(a) => {412 if a.is_empty() {413 buf.push_str("[]");414 } else {415 for (i, item) in a.iter().enumerate() {416 if i != 0 {417 buf.push('\n');418 buf.push_str(cur_padding);419 }420 let item = item?;421 buf.push('-');422 match &item {423 Val::Arr(a) if !a.is_empty() => {424 buf.push('\n');425 buf.push_str(cur_padding);426 buf.push_str(&options.padding);427 }428 _ => buf.push(' '),429 }430 let extra_padding = match &item {431 Val::Arr(a) => !a.is_empty(),432 Val::Obj(o) => !o.is_empty(),433 _ => false,434 };435 let prev_len = cur_padding.len();436 if extra_padding {437 cur_padding.push_str(&options.padding);438 }439 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;440 cur_padding.truncate(prev_len);441 }442 }443 }444 Val::Obj(o) => {445 if o.is_empty() {446 buf.push_str("{}");447 } else {448 for (i, key) in o449 .fields(450 #[cfg(feature = "exp-preserve-order")]451 options.preserve_order,452 )453 .iter()454 .enumerate()455 {456 if i != 0 {457 buf.push('\n');458 buf.push_str(cur_padding);459 }460 if !options.quote_keys && !yaml_needs_quotes(key) {461 buf.push_str(key);462 } else {463 escape_string_json_buf(key, buf);464 }465 buf.push(':');466 let prev_len = cur_padding.len();467 let item = o.get(key.clone())?.expect("field exists");468 match &item {469 Val::Arr(a) if !a.is_empty() => {470 buf.push('\n');471 buf.push_str(cur_padding);472 buf.push_str(&options.arr_element_padding);473 cur_padding.push_str(&options.arr_element_padding);474 }475 Val::Obj(o) if !o.is_empty() => {476 buf.push('\n');477 buf.push_str(cur_padding);478 buf.push_str(&options.padding);479 cur_padding.push_str(&options.padding);480 }481 _ => buf.push(' '),482 }483 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;484 cur_padding.truncate(prev_len);485 }486 }487 }488 Val::Func(_) => throw!("tried to manifest function"),489 }490 Ok(())491}1use std::{borrow::Cow, fmt::Write};23use crate::{4 error::{Error::*, Result},5 throw, ManifestFormat, State, Val,6};78#[derive(PartialEq, Eq, Clone, Copy)]9pub enum ManifestType {10 // Applied in manifestification11 Manifest,12 /// Used for std.manifestJson13 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest14 Std,15 /// No line breaks, used in `obj+''`16 ToString,17 /// Minified json18 Minify,19}2021pub struct JsonFormat<'s> {22 padding: Cow<'s, str>,23 mtype: ManifestType,24 newline: &'s str,25 key_val_sep: &'s str,26 #[cfg(feature = "exp-preserve-order")]27 preserve_order: bool,28}2930impl<'s> JsonFormat<'s> {31 // Minifying format32 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {33 Self {34 padding: Cow::Borrowed(""),35 mtype: ManifestType::Minify,36 newline: "\n",37 key_val_sep: ":",38 #[cfg(feature = "exp-preserve-order")]39 preserve_order,40 }41 }42 // Same format as std.toString43 pub fn std_to_string() -> Self {44 Self {45 padding: Cow::Borrowed(""),46 mtype: ManifestType::ToString,47 newline: "\n",48 key_val_sep: ": ",49 #[cfg(feature = "exp-preserve-order")]50 preserve_order: false,51 }52 }53 pub fn std_to_json(54 padding: String,55 newline: &'s str,56 key_val_sep: &'s str,57 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,58 ) -> Self {59 Self {60 padding: Cow::Owned(padding),61 mtype: ManifestType::Std,62 newline,63 key_val_sep,64 #[cfg(feature = "exp-preserve-order")]65 preserve_order,66 }67 }68 // Same format as CLI manifestification69 pub fn cli(70 padding: usize,71 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,72 ) -> Self {73 if padding == 0 {74 return Self::minify(75 #[cfg(feature = "exp-preserve-order")]76 preserve_order,77 );78 }79 Self {80 padding: Cow::Owned(" ".repeat(padding)),81 mtype: ManifestType::Manifest,82 newline: "\n",83 key_val_sep: ": ",84 #[cfg(feature = "exp-preserve-order")]85 preserve_order,86 }87 }88}89impl Default for JsonFormat<'static> {90 fn default() -> Self {91 Self {92 padding: Cow::Borrowed(" "),93 mtype: ManifestType::Manifest,94 newline: "\n",95 key_val_sep: ": ",96 #[cfg(feature = "exp-preserve-order")]97 preserve_order: false,98 }99 }100}101102pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {103 let mut out = String::new();104 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;105 Ok(out)106}107fn manifest_json_ex_buf(108 val: &Val,109 buf: &mut String,110 cur_padding: &mut String,111 options: &JsonFormat<'_>,112) -> Result<()> {113 let mtype = options.mtype;114 match val {115 Val::Bool(v) => {116 if *v {117 buf.push_str("true");118 } else {119 buf.push_str("false");120 }121 }122 Val::Null => buf.push_str("null"),123 Val::Str(s) => escape_string_json_buf(s, buf),124 Val::Num(n) => write!(buf, "{n}").unwrap(),125 Val::Arr(items) => {126 buf.push('[');127 if !items.is_empty() {128 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {129 buf.push_str(options.newline);130 }131132 let old_len = cur_padding.len();133 cur_padding.push_str(&options.padding);134 for (i, item) in items.iter().enumerate() {135 if i != 0 {136 buf.push(',');137 if mtype == ManifestType::ToString {138 buf.push(' ');139 } else if mtype != ManifestType::Minify {140 buf.push_str(options.newline);141 }142 }143 buf.push_str(cur_padding);144 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;145 }146 cur_padding.truncate(old_len);147148 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {149 buf.push_str(options.newline);150 buf.push_str(cur_padding);151 }152 } else if mtype == ManifestType::Std {153 buf.push_str("\n\n");154 buf.push_str(cur_padding);155 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {156 buf.push(' ');157 }158 buf.push(']');159 }160 Val::Obj(obj) => {161 obj.run_assertions()?;162 buf.push('{');163 let fields = obj.fields(164 #[cfg(feature = "exp-preserve-order")]165 options.preserve_order,166 );167 if !fields.is_empty() {168 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {169 buf.push_str(options.newline);170 }171172 let old_len = cur_padding.len();173 cur_padding.push_str(&options.padding);174 for (i, field) in fields.into_iter().enumerate() {175 if i != 0 {176 buf.push(',');177 if mtype == ManifestType::ToString {178 buf.push(' ');179 } else if mtype != ManifestType::Minify {180 buf.push_str(options.newline);181 }182 }183 buf.push_str(cur_padding);184 escape_string_json_buf(&field, buf);185 buf.push_str(options.key_val_sep);186 State::push_description(187 || format!("field <{}> manifestification", field.clone()),188 || {189 let value = obj.get(field.clone())?.unwrap();190 manifest_json_ex_buf(&value, buf, cur_padding, options)?;191 Ok(())192 },193 )?;194 }195 cur_padding.truncate(old_len);196197 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {198 buf.push_str(options.newline);199 buf.push_str(cur_padding);200 }201 } else if mtype == ManifestType::Std {202 buf.push_str("\n\n");203 buf.push_str(cur_padding);204 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {205 buf.push(' ');206 }207 buf.push('}');208 }209 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),210 };211 Ok(())212}213214impl ManifestFormat for JsonFormat<'_> {215 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {216 manifest_json_ex_buf(&val, buf, &mut String::new(), &self)217 }218}219220pub struct ToStringFormat;221impl ManifestFormat for ToStringFormat {222 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {223 JsonFormat::std_to_string().manifest_buf(val, out)224 }225}226pub struct StringFormat;227impl ManifestFormat for StringFormat {228 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {229 let Val::Str(s) = val else {230 throw!("output should be string for string manifest format, got {}", val.value_type())231 };232 out.write_str(&s).unwrap();233 Ok(())234 }235}236237pub struct YamlStreamFormat<I>(pub I);238impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {239 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {240 let Val::Arr(arr) = val else {241 throw!("output should be array for yaml stream format, got {}", val.value_type())242 };243 if !arr.is_empty() {244 for v in arr.iter() {245 let v = v?;246 out.push_str("---\n");247 self.0.manifest_buf(v, out)?;248 out.push('\n');249 }250 out.push_str("...");251 }252 Ok(())253 }254}255256pub fn escape_string_json(s: &str) -> String {257 let mut buf = String::new();258 escape_string_json_buf(s, &mut buf);259 buf260}261262// Json string encoding was borrowed from https://github.com/serde-rs/json263264const BB: u8 = b'b'; // \x08265const TT: u8 = b't'; // \x09266const NN: u8 = b'n'; // \x0A267const FF: u8 = b'f'; // \x0C268const RR: u8 = b'r'; // \x0D269const QU: u8 = b'"'; // \x22270const BS: u8 = b'\\'; // \x5C271const UU: u8 = b'u'; // \x00...\x1F except the ones above272const __: u8 = 0;273274// Lookup table of escape sequences. A value of b'x' at index i means that byte275// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.276static ESCAPE: [u8; 256] = [277 // 1 2 3 4 5 6 7 8 9 A B C D E F278 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0279 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1280 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2281 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3282 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4283 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5284 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6285 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7286 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8287 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9288 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A289 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B290 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C291 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D292 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E293 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F294];295296fn escape_string_json_buf(value: &str, buf: &mut String) {297 // Safety: we only write correct utf-8 in this function298 let mut buf: &mut Vec<u8> = unsafe { core::mem::transmute(buf) };299 let bytes = value.as_bytes();300301 // Perfect for ascii strings, removes any reallocations302 buf.reserve(value.len() + 2);303304 buf.push(b'"');305306 let mut start = 0;307308 for (i, &byte) in bytes.iter().enumerate() {309 let escape = ESCAPE[byte as usize];310 if escape == __ {311 continue;312 }313314 if start < i {315 buf.extend_from_slice(&bytes[start..i]);316 }317 start = i + 1;318319 match escape {320 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {321 buf.extend_from_slice(&[b'\\', escape])322 }323 self::UU => {324 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";325 let bytes = &[326 b'\\',327 b'u',328 b'0',329 b'0',330 HEX_DIGITS[(byte >> 4) as usize],331 HEX_DIGITS[(byte & 0xF) as usize],332 ];333 buf.extend_from_slice(bytes)334 }335 _ => unreachable!(),336 }337 }338339 if start == bytes.len() {340 buf.push(b'"');341 return;342 }343344 buf.extend_from_slice(&bytes[start..]);345 buf.push(b'"');346}347348pub struct YamlFormat<'s> {349 /// Padding before fields, i.e350 /// ```yaml351 /// a:352 /// b:353 /// ## <- this354 /// ```355 padding: Cow<'s, str>,356 /// Padding before array elements in objects357 /// ```yaml358 /// a:359 /// - 1360 /// ## <- this361 /// ```362 arr_element_padding: Cow<'s, str>,363 /// Should yaml keys appear unescaped, when possible364 /// ```yaml365 /// "safe_key": 1366 /// # vs367 /// safe_key: 1368 /// ```369 quote_keys: bool,370 /// If true - then order of fields is preserved as written,371 /// instead of sorting alphabetically372 #[cfg(feature = "exp-preserve-order")]373 preserve_order: bool,374}375impl YamlFormat<'_> {376 pub fn cli(377 padding: usize,378 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,379 ) -> Self {380 let padding = " ".repeat(padding);381 Self {382 padding: Cow::Owned(padding.clone()),383 arr_element_padding: Cow::Owned(padding),384 quote_keys: false,385 #[cfg(feature = "exp-preserve-order")]386 preserve_order,387 }388 }389 pub fn std_to_yaml(390 indent_array_in_object: bool,391 quote_keys: bool,392 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,393 ) -> Self {394 Self {395 padding: Cow::Borrowed(" "),396 arr_element_padding: Cow::Borrowed(if indent_array_in_object { " " } else { "" }),397 quote_keys,398 #[cfg(feature = "exp-preserve-order")]399 preserve_order,400 }401 }402}403impl ManifestFormat for YamlFormat<'_> {404 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {405 manifest_yaml_ex_buf(&val, buf, &mut String::new(), self)406 }407}408409/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>410/// With added date check411fn yaml_needs_quotes(string: &str) -> bool {412 fn need_quotes_spaces(string: &str) -> bool {413 string.starts_with(' ') || string.ends_with(' ')414 }415416 string.is_empty()417 || need_quotes_spaces(string)418 || string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))419 || string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))420 || [421 // http://yaml.org/type/bool.html422 // Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse423 // them as string, not booleans, although it is violating the YAML 1.1 specification.424 // See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.425 "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",426 "on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html427 "null", "Null", "NULL", "~",428 ].contains(&string)429 || (string.chars().all(|c| matches!(c, '0'..='9' | '-'))430 && string.chars().filter(|c| *c == '-').count() == 2)431 || string.starts_with('.')432 || string.starts_with("0x")433 || string.parse::<i64>().is_ok()434 || string.parse::<f64>().is_ok()435}436437pub fn manifest_yaml_ex(val: &Val, options: &YamlFormat<'_>) -> Result<String> {438 let mut out = String::new();439 manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;440 Ok(out)441}442443#[allow(clippy::too_many_lines)]444fn manifest_yaml_ex_buf(445 val: &Val,446 buf: &mut String,447 cur_padding: &mut String,448 options: &YamlFormat<'_>,449) -> Result<()> {450 match val {451 Val::Bool(v) => {452 if *v {453 buf.push_str("true");454 } else {455 buf.push_str("false");456 }457 }458 Val::Null => buf.push_str("null"),459 Val::Str(s) => {460 if s.is_empty() {461 buf.push_str("\"\"");462 } else if let Some(s) = s.strip_suffix('\n') {463 buf.push('|');464 for line in s.split('\n') {465 buf.push('\n');466 buf.push_str(cur_padding);467 buf.push_str(&options.padding);468 buf.push_str(line);469 }470 } else if !options.quote_keys && !yaml_needs_quotes(s) {471 buf.push_str(s);472 } else {473 escape_string_json_buf(s, buf);474 }475 }476 Val::Num(n) => write!(buf, "{}", *n).unwrap(),477 Val::Arr(a) => {478 if a.is_empty() {479 buf.push_str("[]");480 } else {481 for (i, item) in a.iter().enumerate() {482 if i != 0 {483 buf.push('\n');484 buf.push_str(cur_padding);485 }486 let item = item?;487 buf.push('-');488 match &item {489 Val::Arr(a) if !a.is_empty() => {490 buf.push('\n');491 buf.push_str(cur_padding);492 buf.push_str(&options.padding);493 }494 _ => buf.push(' '),495 }496 let extra_padding = match &item {497 Val::Arr(a) => !a.is_empty(),498 Val::Obj(o) => !o.is_empty(),499 _ => false,500 };501 let prev_len = cur_padding.len();502 if extra_padding {503 cur_padding.push_str(&options.padding);504 }505 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;506 cur_padding.truncate(prev_len);507 }508 }509 }510 Val::Obj(o) => {511 if o.is_empty() {512 buf.push_str("{}");513 } else {514 for (i, key) in o515 .fields(516 #[cfg(feature = "exp-preserve-order")]517 options.preserve_order,518 )519 .iter()520 .enumerate()521 {522 if i != 0 {523 buf.push('\n');524 buf.push_str(cur_padding);525 }526 if !options.quote_keys && !yaml_needs_quotes(key) {527 buf.push_str(key);528 } else {529 escape_string_json_buf(key, buf);530 }531 buf.push(':');532 let prev_len = cur_padding.len();533 let item = o.get(key.clone())?.expect("field exists");534 match &item {535 Val::Arr(a) if !a.is_empty() => {536 buf.push('\n');537 buf.push_str(cur_padding);538 buf.push_str(&options.arr_element_padding);539 cur_padding.push_str(&options.arr_element_padding);540 }541 Val::Obj(o) if !o.is_empty() => {542 buf.push('\n');543 buf.push_str(cur_padding);544 buf.push_str(&options.padding);545 cur_padding.push_str(&options.padding);546 }547 _ => buf.push(' '),548 }549 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;550 cur_padding.truncate(prev_len);551 }552 }553 }554 Val::Func(_) => throw!("tried to manifest function"),555 }556 Ok(())557}