1use crate::error::Error::*;2use crate::error::Result;3use crate::push_frame;4use crate::{throw, Val};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}2324pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {25 let mut out = String::new();26 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;27 Ok(out)28}29fn manifest_json_ex_buf(30 val: &Val,31 buf: &mut String,32 cur_padding: &mut String,33 options: &ManifestJsonOptions<'_>,34) -> Result<()> {35 use std::fmt::Write;36 let mtype = options.mtype;37 match val {38 Val::Bool(v) => {39 if *v {40 buf.push_str("true");41 } else {42 buf.push_str("false");43 }44 }45 Val::Null => buf.push_str("null"),46 Val::Str(s) => escape_string_json_buf(s, buf),47 Val::Num(n) => write!(buf, "{}", n).unwrap(),48 Val::Arr(items) => {49 buf.push('[');50 if !items.is_empty() {51 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {52 buf.push('\n');53 }5455 let old_len = cur_padding.len();56 cur_padding.push_str(options.padding);57 for (i, item) in items.iter().enumerate() {58 if i != 0 {59 buf.push(',');60 if mtype == ManifestType::ToString {61 buf.push(' ');62 } else if mtype != ManifestType::Minify {63 buf.push('\n');64 }65 }66 buf.push_str(cur_padding);67 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;68 }69 cur_padding.truncate(old_len);7071 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {72 buf.push('\n');73 buf.push_str(cur_padding);74 }75 } else if mtype == ManifestType::Std {76 buf.push_str("\n\n");77 buf.push_str(cur_padding);78 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {79 buf.push(' ');80 }81 buf.push(']');82 }83 Val::Obj(obj) => {84 obj.run_assertions()?;85 buf.push('{');86 let fields = obj.fields();87 if !fields.is_empty() {88 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {89 buf.push('\n');90 }9192 let old_len = cur_padding.len();93 cur_padding.push_str(options.padding);94 for (i, field) in fields.into_iter().enumerate() {95 if i != 0 {96 buf.push(',');97 if mtype == ManifestType::ToString {98 buf.push(' ');99 } else if mtype != ManifestType::Minify {100 buf.push('\n');101 }102 }103 buf.push_str(cur_padding);104 escape_string_json_buf(&field, buf);105 buf.push_str(": ");106 push_frame(107 None,108 || format!("field <{}> manifestification", field.clone()),109 || {110 let value = obj.get(field.clone())?.unwrap();111 manifest_json_ex_buf(&value, buf, cur_padding, options)?;112 Ok(Val::Null)113 },114 )?;115 }116 cur_padding.truncate(old_len);117118 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {119 buf.push('\n');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}