1use crate::error::Error::*;2use crate::error::Result;3use crate::{throw, Val};45#[derive(PartialEq, Clone, Copy)]6pub enum ManifestType {7 8 Manifest,9 10 11 Std,12 13 ToString,14 15 Minify,16}1718pub struct ManifestJsonOptions<'s> {19 pub padding: &'s str,20 pub mtype: ManifestType,21 pub newline: &'s str,22 pub key_val_sep: &'s str,23}2425pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {26 let mut out = String::new();27 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;28 Ok(out)29}30fn manifest_json_ex_buf(31 val: &Val,32 buf: &mut String,33 cur_padding: &mut String,34 options: &ManifestJsonOptions<'_>,35) -> Result<()> {36 use std::fmt::Write;37 let mtype = options.mtype;38 match val {39 Val::Bool(v) => {40 if *v {41 buf.push_str("true");42 } else {43 buf.push_str("false");44 }45 }46 Val::Null => buf.push_str("null"),47 Val::Str(s) => escape_string_json_buf(s, buf),48 Val::Num(n) => write!(buf, "{}", n).unwrap(),49 Val::Arr(items) => {50 buf.push('[');51 if !items.is_empty() {52 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {53 buf.push_str(options.newline);54 }5556 let old_len = cur_padding.len();57 cur_padding.push_str(options.padding);58 for (i, item) in items.iter().enumerate() {59 if i != 0 {60 buf.push(',');61 if mtype == ManifestType::ToString {62 buf.push(' ');63 } else if mtype != ManifestType::Minify {64 buf.push_str(options.newline);65 }66 }67 buf.push_str(cur_padding);68 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;69 }70 cur_padding.truncate(old_len);7172 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {73 buf.push_str(options.newline);74 buf.push_str(cur_padding);75 }76 } else if mtype == ManifestType::Std {77 buf.push_str("\n\n");78 buf.push_str(cur_padding);79 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {80 buf.push(' ');81 }82 buf.push(']');83 }84 Val::Obj(obj) => {85 obj.run_assertions()?;86 buf.push('{');87 let fields = obj.fields();88 if !fields.is_empty() {89 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {90 buf.push_str(options.newline);91 }9293 let old_len = cur_padding.len();94 cur_padding.push_str(options.padding);95 for (i, field) in fields.into_iter().enumerate() {96 if i != 0 {97 buf.push(',');98 if mtype == ManifestType::ToString {99 buf.push(' ');100 } else if mtype != ManifestType::Minify {101 buf.push_str(options.newline);102 }103 }104 buf.push_str(cur_padding);105 escape_string_json_buf(&field, buf);106 buf.push_str(options.key_val_sep);107 crate::push(108 None,109 || format!("field <{}> manifestification", field.clone()),110 || {111 let value = obj.get(field.clone())?.unwrap();112 manifest_json_ex_buf(&value, buf, cur_padding, options)113 },114 )?;115 }116 cur_padding.truncate(old_len);117118 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {119 buf.push_str(options.newline);120 buf.push_str(cur_padding);121 }122 } else if mtype == ManifestType::Std {123 buf.push_str("\n\n");124 buf.push_str(cur_padding);125 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {126 buf.push(' ');127 }128 buf.push('}');129 }130 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),131 };132 Ok(())133}134135pub fn escape_string_json(s: &str) -> String {136 let mut buf = String::new();137 escape_string_json_buf(s, &mut buf);138 buf139}140141fn escape_string_json_buf(s: &str, buf: &mut String) {142 use std::fmt::Write;143 buf.push('"');144 for c in s.chars() {145 match c {146 '"' => buf.push_str("\\\""),147 '\\' => buf.push_str("\\\\"),148 '\u{0008}' => buf.push_str("\\b"),149 '\u{000c}' => buf.push_str("\\f"),150 '\n' => buf.push_str("\\n"),151 '\r' => buf.push_str("\\r"),152 '\t' => buf.push_str("\\t"),153 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {154 write!(buf, "\\u{:04x}", c as u32).unwrap()155 }156 c => buf.push(c),157 }158 }159 buf.push('"');160}161162pub struct ManifestYamlOptions<'s> {163 164 165 166 167 168 169 pub padding: &'s str,170 171 172 173 174 175 176 pub arr_element_padding: &'s str,177}178179pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {180 let mut out = String::new();181 manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;182 Ok(out)183}184fn manifest_yaml_ex_buf(185 val: &Val,186 buf: &mut String,187 cur_padding: &mut String,188 options: &ManifestYamlOptions<'_>,189) -> Result<()> {190 use std::fmt::Write;191 match val {192 Val::Bool(v) => {193 if *v {194 buf.push_str("true")195 } else {196 buf.push_str("false")197 }198 }199 Val::Null => buf.push_str("null"),200 Val::Str(s) => {201 if s.is_empty() {202 buf.push_str("\"\"");203 } else if let Some(s) = s.strip_suffix('\n') {204 buf.push('|');205 for line in s.split('\n') {206 buf.push('\n');207 buf.push_str(options.padding);208 buf.push_str(line);209 }210 } else {211 escape_string_json_buf(s, buf)212 }213 }214 Val::Num(n) => write!(buf, "{}", *n).unwrap(),215 Val::Arr(a) => {216 if a.is_empty() {217 buf.push_str("[]");218 } else {219 for (i, item) in a.iter().enumerate() {220 if i != 0 {221 buf.push('\n');222 buf.push_str(cur_padding);223 }224 let item = item?;225 buf.push('-');226 match &item {227 Val::Arr(a) if !a.is_empty() => {228 buf.push('\n');229 buf.push_str(cur_padding);230 buf.push_str(options.padding);231 }232 _ => buf.push(' '),233 }234 let extra_padding = match &item {235 Val::Arr(a) => !a.is_empty(),236 Val::Obj(o) => !o.is_empty(),237 _ => false,238 };239 let prev_len = cur_padding.len();240 if extra_padding {241 cur_padding.push_str(options.padding);242 }243 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;244 cur_padding.truncate(prev_len);245 }246 }247 }248 Val::Obj(o) => {249 if o.is_empty() {250 buf.push_str("{}");251 } else {252 for (i, key) in o.fields().iter().enumerate() {253 if i != 0 {254 buf.push('\n');255 buf.push_str(cur_padding);256 }257 escape_string_json_buf(key, buf);258 buf.push(':');259 let prev_len = cur_padding.len();260 let item = o.get(key.clone())?.expect("field exists");261 match &item {262 Val::Arr(a) if !a.is_empty() => {263 buf.push('\n');264 buf.push_str(cur_padding);265 buf.push_str(options.arr_element_padding);266 cur_padding.push_str(options.arr_element_padding);267 }268 Val::Obj(o) if !o.is_empty() => {269 buf.push('\n');270 buf.push_str(cur_padding);271 buf.push_str(options.padding);272 cur_padding.push_str(options.padding);273 }274 _ => buf.push(' '),275 }276 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;277 cur_padding.truncate(prev_len);278 }279 }280 }281 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),282 }283 Ok(())284}