1use crate::{2 error::{Error::*, Result},3 throw, State, Val,4};56#[derive(PartialEq, Clone, Copy)]7pub enum ManifestType {8 9 Manifest,10 11 12 Std,13 14 ToString,15 16 Minify,17}1819pub struct ManifestJsonOptions<'s> {20 pub padding: &'s str,21 pub mtype: ManifestType,22 pub newline: &'s str,23 pub key_val_sep: &'s str,24 #[cfg(feature = "exp-preserve-order")]25 pub preserve_order: bool,26}2728pub fn manifest_json_ex(s: State, val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {29 let mut out = String::new();30 manifest_json_ex_buf(s, val, &mut out, &mut String::new(), options)?;31 Ok(out)32}33fn manifest_json_ex_buf(34 s: State,35 val: &Val,36 buf: &mut String,37 cur_padding: &mut String,38 options: &ManifestJsonOptions<'_>,39) -> Result<()> {40 use std::fmt::Write;41 let mtype = options.mtype;42 match val {43 Val::Bool(v) => {44 if *v {45 buf.push_str("true");46 } else {47 buf.push_str("false");48 }49 }50 Val::Null => buf.push_str("null"),51 Val::Str(s) => escape_string_json_buf(s, buf),52 Val::Num(n) => write!(buf, "{}", n).unwrap(),53 Val::Arr(items) => {54 buf.push('[');55 if !items.is_empty() {56 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {57 buf.push_str(options.newline);58 }5960 let old_len = cur_padding.len();61 cur_padding.push_str(options.padding);62 for (i, item) in items.iter(s.clone()).enumerate() {63 if i != 0 {64 buf.push(',');65 if mtype == ManifestType::ToString {66 buf.push(' ');67 } else if mtype != ManifestType::Minify {68 buf.push_str(options.newline);69 }70 }71 buf.push_str(cur_padding);72 manifest_json_ex_buf(s.clone(), &item?, buf, cur_padding, options)?;73 }74 cur_padding.truncate(old_len);7576 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {77 buf.push_str(options.newline);78 buf.push_str(cur_padding);79 }80 } else if mtype == ManifestType::Std {81 buf.push_str("\n\n");82 buf.push_str(cur_padding);83 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {84 buf.push(' ');85 }86 buf.push(']');87 }88 Val::Obj(obj) => {89 obj.run_assertions(s.clone())?;90 buf.push('{');91 let fields = obj.fields(92 #[cfg(feature = "exp-preserve-order")]93 options.preserve_order,94 );95 if !fields.is_empty() {96 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {97 buf.push_str(options.newline);98 }99100 let old_len = cur_padding.len();101 cur_padding.push_str(options.padding);102 for (i, field) in fields.into_iter().enumerate() {103 if i != 0 {104 buf.push(',');105 if mtype == ManifestType::ToString {106 buf.push(' ');107 } else if mtype != ManifestType::Minify {108 buf.push_str(options.newline);109 }110 }111 buf.push_str(cur_padding);112 escape_string_json_buf(&field, buf);113 buf.push_str(options.key_val_sep);114 s.push_description(115 || format!("field <{}> manifestification", field.clone()),116 || {117 let value = obj.get(s.clone(), field.clone())?.unwrap();118 manifest_json_ex_buf(s.clone(), &value, buf, cur_padding, options)?;119 Ok(Val::Null)120 },121 )?;122 }123 cur_padding.truncate(old_len);124125 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {126 buf.push_str(options.newline);127 buf.push_str(cur_padding);128 }129 } else if mtype == ManifestType::Std {130 buf.push_str("\n\n");131 buf.push_str(cur_padding);132 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {133 buf.push(' ');134 }135 buf.push('}');136 }137 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),138 };139 Ok(())140}141142pub fn escape_string_json(s: &str) -> String {143 let mut buf = String::new();144 escape_string_json_buf(s, &mut buf);145 buf146}147148fn escape_string_json_buf(s: &str, buf: &mut String) {149 use std::fmt::Write;150 buf.push('"');151 for c in s.chars() {152 match c {153 '"' => buf.push_str("\\\""),154 '\\' => buf.push_str("\\\\"),155 '\u{0008}' => buf.push_str("\\b"),156 '\u{000c}' => buf.push_str("\\f"),157 '\n' => buf.push_str("\\n"),158 '\r' => buf.push_str("\\r"),159 '\t' => buf.push_str("\\t"),160 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {161 write!(buf, "\\u{:04x}", c as u32).unwrap()162 }163 c => buf.push(c),164 }165 }166 buf.push('"');167}168169pub struct ManifestYamlOptions<'s> {170 171 172 173 174 175 176 pub padding: &'s str,177 178 179 180 181 182 183 pub arr_element_padding: &'s str,184 185 186 187 188 189 190 pub quote_keys: bool,191 192 193 #[cfg(feature = "exp-preserve-order")]194 pub preserve_order: bool,195}196197198199fn yaml_needs_quotes(string: &str) -> bool {200 fn need_quotes_spaces(string: &str) -> bool {201 string.starts_with(' ') || string.ends_with(' ')202 }203204 string.is_empty()205 || need_quotes_spaces(string)206 || string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))207 || string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))208 || [209 210 211 212 213 "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",214 "on", "On", "ON", "off", "Off", "OFF", 215 "null", "Null", "NULL", "~",216 ].contains(&string)217 || (string.chars().all(|c| matches!(c, '0'..='9' | '-'))218 && string.chars().filter(|c| *c == '-').count() == 2)219 || string.starts_with('.')220 || string.starts_with("0x")221 || string.parse::<i64>().is_ok()222 || string.parse::<f64>().is_ok()223}224225pub fn manifest_yaml_ex(s: State, val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {226 let mut out = String::new();227 manifest_yaml_ex_buf(s, val, &mut out, &mut String::new(), options)?;228 Ok(out)229}230fn manifest_yaml_ex_buf(231 s: State,232 val: &Val,233 buf: &mut String,234 cur_padding: &mut String,235 options: &ManifestYamlOptions<'_>,236) -> Result<()> {237 use std::fmt::Write;238 match val {239 Val::Bool(v) => {240 if *v {241 buf.push_str("true")242 } else {243 buf.push_str("false")244 }245 }246 Val::Null => buf.push_str("null"),247 Val::Str(s) => {248 if s.is_empty() {249 buf.push_str("\"\"");250 } else if let Some(s) = s.strip_suffix('\n') {251 buf.push('|');252 for line in s.split('\n') {253 buf.push('\n');254 buf.push_str(cur_padding);255 buf.push_str(options.padding);256 buf.push_str(line);257 }258 } else if !options.quote_keys && !yaml_needs_quotes(s) {259 buf.push_str(s);260 } else {261 escape_string_json_buf(s, buf);262 }263 }264 Val::Num(n) => write!(buf, "{}", *n).unwrap(),265 Val::Arr(a) => {266 if a.is_empty() {267 buf.push_str("[]");268 } else {269 for (i, item) in a.iter(s.clone()).enumerate() {270 if i != 0 {271 buf.push('\n');272 buf.push_str(cur_padding);273 }274 let item = item?;275 buf.push('-');276 match &item {277 Val::Arr(a) if !a.is_empty() => {278 buf.push('\n');279 buf.push_str(cur_padding);280 buf.push_str(options.padding);281 }282 _ => buf.push(' '),283 }284 let extra_padding = match &item {285 Val::Arr(a) => !a.is_empty(),286 Val::Obj(o) => !o.is_empty(),287 _ => false,288 };289 let prev_len = cur_padding.len();290 if extra_padding {291 cur_padding.push_str(options.padding);292 }293 manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;294 cur_padding.truncate(prev_len);295 }296 }297 }298 Val::Obj(o) => {299 if o.is_empty() {300 buf.push_str("{}");301 } else {302 for (i, key) in o303 .fields(304 #[cfg(feature = "exp-preserve-order")]305 options.preserve_order,306 )307 .iter()308 .enumerate()309 {310 if i != 0 {311 buf.push('\n');312 buf.push_str(cur_padding);313 }314 if !options.quote_keys && !yaml_needs_quotes(key) {315 buf.push_str(key);316 } else {317 escape_string_json_buf(key, buf);318 }319 buf.push(':');320 let prev_len = cur_padding.len();321 let item = o.get(s.clone(), key.clone())?.expect("field exists");322 match &item {323 Val::Arr(a) if !a.is_empty() => {324 buf.push('\n');325 buf.push_str(cur_padding);326 buf.push_str(options.arr_element_padding);327 cur_padding.push_str(options.arr_element_padding);328 }329 Val::Obj(o) if !o.is_empty() => {330 buf.push('\n');331 buf.push_str(cur_padding);332 buf.push_str(options.padding);333 cur_padding.push_str(options.padding);334 }335 _ => buf.push(' '),336 }337 manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;338 cur_padding.truncate(prev_len);339 }340 }341 }342 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),343 }344 Ok(())345}