1use std::borrow::Cow;23use jrsonnet_evaluator::{4 Error, IStr, ObjValue, Result, ResultExt, Val, bail, ensure_sufficient_stack,5 in_description_frame,6 manifest::{ManifestFormat, escape_string_json_buf},7 val::ArrValue,8};910pub struct TomlFormat<'s> {11 12 13 14 15 16 17 padding: Cow<'s, str>,18 19 20 21 22 23 24 25 26 27 skip_empty_sections: bool,28 29 30 #[cfg(feature = "exp-preserve-order")]31 preserve_order: bool,32}33impl TomlFormat<'_> {34 pub fn cli(35 padding: usize,36 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,37 ) -> Self {38 let padding = " ".repeat(padding);39 Self {40 padding: Cow::Owned(padding),41 skip_empty_sections: true,42 #[cfg(feature = "exp-preserve-order")]43 preserve_order,44 }45 }46 pub fn std_to_toml(47 padding: String,48 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,49 ) -> Self {50 Self {51 padding: Cow::Owned(padding),52 skip_empty_sections: false,53 #[cfg(feature = "exp-preserve-order")]54 preserve_order,55 }56 }57}5859fn bare_allowed(s: &str) -> bool {60 s.bytes()61 .all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))62}6364fn escape_key_toml_buf(key: &str, buf: &mut String) {65 if bare_allowed(key) {66 buf.push_str(key);67 } else {68 escape_string_json_buf(key, buf);69 }70}7172fn is_section(val: &Val) -> Result<bool> {73 Ok(match val {74 Val::Arr(a) => {75 if a.is_empty() {76 return Ok(false);77 }78 for e in a.iter() {79 let e = e?;80 if !matches!(e, Val::Obj(_)) {81 return Ok(false);82 }83 }84 true85 }86 Val::Obj(_) => true,87 _ => false,88 })89}9091fn manifest_value(92 val: &Val,93 inline: bool,94 buf: &mut String,95 cur_padding: &str,96 options: &TomlFormat<'_>,97) -> Result<()> {98 use std::fmt::Write;99 match val {100 Val::Bool(true) => buf.push_str("true"),101 Val::Bool(false) => buf.push_str("false"),102 Val::Str(s) => {103 escape_string_json_buf(&s.clone().into_flat(), buf);104 }105 Val::Num(n) => write!(buf, "{n}").unwrap(),106 #[cfg(feature = "exp-bigint")]107 Val::BigInt(n) => write!(buf, "{n}").unwrap(),108 Val::Arr(a) => ensure_sufficient_stack(|| {109 buf.push('[');110111 let mut had_items = false;112 for (i, e) in a.iter().enumerate() {113 had_items = true;114 let e = e.with_description(|| format!("elem <{i}> evaluation"))?;115116 if i != 0 {117 buf.push(',');118 }119 if inline {120 buf.push(' ');121 } else {122 buf.push('\n');123 buf.push_str(cur_padding);124 buf.push_str(&options.padding);125 }126127 in_description_frame(128 || format!("elem <{i}> manifestification"),129 || manifest_value(&e, true, buf, "", options),130 )?;131 }132133 if !had_items {134 } else if inline {135 buf.push(' ');136 } else {137 buf.push('\n');138 buf.push_str(cur_padding);139 }140 buf.push(']');141 Ok::<_, Error>(())142 })?,143 Val::Obj(o) => ensure_sufficient_stack(|| {144 o.run_assertions()?;145 buf.push('{');146147 let mut had_fields = false;148 for (i, (k, v)) in o149 .iter(150 #[cfg(feature = "exp-preserve-order")]151 options.preserve_order,152 )153 .enumerate()154 {155 had_fields = true;156 let v = v.with_description(|| format!("field <{k}> evaluation"))?;157158 if i != 0 {159 buf.push(',');160 }161 buf.push(' ');162163 escape_key_toml_buf(&k, buf);164 buf.push_str(" = ");165 in_description_frame(166 || format!("field <{k}> manifestification"),167 || manifest_value(&v, true, buf, "", options),168 )?;169 }170171 if had_fields {172 buf.push(' ');173 }174175 buf.push('}');176 Ok::<_, Error>(())177 })?,178 Val::Null => {179 bail!("tried to manifest null")180 }181 Val::Func(_) => {182 bail!("tried to manifest function")183 }184 }185 Ok(())186}187188fn manifest_table_internal(189 obj: &ObjValue,190 path: &mut Vec<IStr>,191 buf: &mut String,192 cur_padding: &mut String,193 options: &TomlFormat<'_>,194) -> Result<()> {195 let mut sections = Vec::new();196 let mut first = true;197 for (key, value) in obj.iter(198 #[cfg(feature = "exp-preserve-order")]199 options.preserve_order,200 ) {201 let value = value.with_description(|| format!("field <{key}> evaluation"))?;202 if is_section(&value)? {203 sections.push((key, value));204 } else {205 if !first {206 buf.push('\n');207 }208 first = false;209 buf.push_str(cur_padding);210 escape_key_toml_buf(&key, buf);211 buf.push_str(" = ");212 in_description_frame(213 || format!("table <{key}> manifestification"),214 || manifest_value(&value, false, buf, cur_padding, options),215 )?;216 }217 }218 for (k, v) in sections {219 if !first {220 buf.push_str("\n\n");221 }222 first = false;223 path.push(k.clone());224 ensure_sufficient_stack(|| {225 in_description_frame(226 || format!("section <{k}> manifestification"),227 || match v {228 Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options),229 Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options),230 _ => unreachable!("iterating over sections"),231 },232 )233 })?;234 path.pop();235 }236 Ok(())237}238239fn manifest_table(240 obj: &ObjValue,241 path: &mut Vec<IStr>,242 buf: &mut String,243 cur_padding: &mut String,244 options: &TomlFormat<'_>,245) -> Result<()> {246 if options.skip_empty_sections247 && !obj.is_empty()248 && obj249 .iter(250 #[cfg(feature = "exp-preserve-order")]251 false,252 )253 .try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?254 {255 manifest_table_internal(obj, path, buf, cur_padding, options)?;256 return Ok(());257 }258 buf.push_str(cur_padding);259 buf.push('[');260 for (i, k) in path.iter().enumerate() {261 if i != 0 {262 buf.push('.');263 }264 escape_key_toml_buf(k, buf);265 }266 buf.push(']');267 if obj.is_empty() {268 return Ok(());269 }270 buf.push('\n');271 let prev_len = cur_padding.len();272 cur_padding.push_str(&options.padding);273 manifest_table_internal(obj, path, buf, cur_padding, options)?;274 cur_padding.truncate(prev_len);275 Ok(())276}277fn manifest_table_array(278 arr: &ArrValue,279 path: &mut Vec<IStr>,280 buf: &mut String,281 cur_padding: &mut String,282 options: &TomlFormat<'_>,283) -> Result<()> {284 let mut formatted_path = String::new();285 {286 formatted_path.push_str(cur_padding);287 formatted_path.push_str("[[");288 for (i, k) in path.iter().enumerate() {289 if i != 0 {290 formatted_path.push('.');291 }292 escape_key_toml_buf(k, &mut formatted_path);293 }294 formatted_path.push_str("]]");295 }296 let prev_len = cur_padding.len();297 cur_padding.push_str(&options.padding);298 for (i, e) in arr.iter().enumerate() {299 let obj = e.expect("already tested").as_obj().expect("already tested");300 if i != 0 {301 buf.push_str("\n\n");302 }303 buf.push_str(&formatted_path);304 if obj.is_empty() {305 continue;306 }307 buf.push('\n');308 manifest_table_internal(&obj, path, buf, cur_padding, options)?;309 }310 cur_padding.truncate(prev_len);311 Ok(())312}313314impl ManifestFormat for TomlFormat<'_> {315 fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {316 match val {317 Val::Obj(obj) => {318 manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)319 }320 _ => bail!("toml body should be object"),321 }322 }323}