1use crate::error::Error::*;2use crate::error::Result;3use crate::push_description_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_description_frame(107 || format!("field <{}> manifestification", field.clone()),108 || {109 let value = obj.get(field.clone())?.unwrap();110 manifest_json_ex_buf(&value, buf, cur_padding, options)?;111 Ok(Val::Null)112 },113 )?;114 }115 cur_padding.truncate(old_len);116117 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {118 buf.push('\n');119 buf.push_str(cur_padding);120 }121 } else if mtype == ManifestType::Std {122 buf.push_str("\n\n");123 buf.push_str(cur_padding);124 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {125 buf.push(' ');126 }127 buf.push('}');128 }129 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),130 };131 Ok(())132}133134pub fn escape_string_json(s: &str) -> String {135 let mut buf = String::new();136 escape_string_json_buf(s, &mut buf);137 buf138}139140fn escape_string_json_buf(s: &str, buf: &mut String) {141 use std::fmt::Write;142 buf.push('"');143 for c in s.chars() {144 match c {145 '"' => buf.push_str("\\\""),146 '\\' => buf.push_str("\\\\"),147 '\u{0008}' => buf.push_str("\\b"),148 '\u{000c}' => buf.push_str("\\f"),149 '\n' => buf.push_str("\\n"),150 '\r' => buf.push_str("\\r"),151 '\t' => buf.push_str("\\t"),152 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {153 write!(buf, "\\u{:04x}", c as u32).unwrap()154 }155 c => buf.push(c),156 }157 }158 buf.push('"');159}160161pub struct ManifestYamlOptions<'s> {162 163 164 165 166 167 168 pub padding: &'s str,169 170 171 172 173 174 175 pub arr_element_padding: &'s str,176}177178pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {179 let mut out = String::new();180 manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;181 Ok(out)182}183fn manifest_yaml_ex_buf(184 val: &Val,185 buf: &mut String,186 cur_padding: &mut String,187 options: &ManifestYamlOptions<'_>,188) -> Result<()> {189 use std::fmt::Write;190 match val {191 Val::Bool(v) => {192 if *v {193 buf.push_str("true")194 } else {195 buf.push_str("false")196 }197 }198 Val::Null => buf.push_str("null"),199 Val::Str(s) => {200 if s.is_empty() {201 buf.push_str("\"\"");202 } else if let Some(s) = s.strip_suffix('\n') {203 buf.push('|');204 for line in s.split('\n') {205 buf.push('\n');206 buf.push_str(options.padding);207 buf.push_str(line);208 }209 } else {210 escape_string_json_buf(s, buf)211 }212 }213 Val::Num(n) => write!(buf, "{}", *n).unwrap(),214 Val::Arr(a) => {215 if a.is_empty() {216 buf.push_str("[]");217 } else {218 for (i, item) in a.iter().enumerate() {219 if i != 0 {220 buf.push('\n');221 buf.push_str(cur_padding);222 }223 let item = item?;224 buf.push('-');225 match &item {226 Val::Arr(a) if !a.is_empty() => {227 buf.push('\n');228 buf.push_str(cur_padding);229 buf.push_str(options.padding);230 }231 _ => buf.push(' '),232 }233 let extra_padding = match &item {234 Val::Arr(a) => !a.is_empty(),235 Val::Obj(o) => !o.is_empty(),236 _ => false,237 };238 let prev_len = cur_padding.len();239 if extra_padding {240 cur_padding.push_str(options.padding);241 }242 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;243 cur_padding.truncate(prev_len);244 }245 }246 }247 Val::Obj(o) => {248 if o.is_empty() {249 buf.push_str("{}");250 } else {251 for (i, key) in o.fields().iter().enumerate() {252 if i != 0 {253 buf.push('\n');254 buf.push_str(cur_padding);255 }256 escape_string_json_buf(key, buf);257 buf.push(':');258 let prev_len = cur_padding.len();259 let item = o.get(key.clone())?.expect("field exists");260 match &item {261 Val::Arr(a) if !a.is_empty() => {262 buf.push('\n');263 buf.push_str(cur_padding);264 buf.push_str(options.arr_element_padding);265 cur_padding.push_str(options.arr_element_padding);266 }267 Val::Obj(o) if !o.is_empty() => {268 buf.push('\n');269 buf.push_str(cur_padding);270 buf.push_str(options.padding);271 cur_padding.push_str(options.padding);272 }273 _ => buf.push(' '),274 }275 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;276 cur_padding.truncate(prev_len);277 }278 }279 }280 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),281 }282 Ok(())283}