1use std::borrow::Cow;23use jrsonnet_evaluator::{4 Error, IStr, ObjValue, Result, ResultExt, Val, bail, ensure_sufficient_stack, in_description_frame, manifest::{ManifestFormat, escape_string_json_buf}, val::ArrValue5};67pub struct TomlFormat<'s> {8 9 10 11 12 13 14 padding: Cow<'s, str>,15 16 17 18 19 20 21 22 23 24 skip_empty_sections: bool,25 26 27 #[cfg(feature = "exp-preserve-order")]28 preserve_order: bool,29}30impl TomlFormat<'_> {31 pub fn cli(32 padding: usize,33 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,34 ) -> Self {35 let padding = " ".repeat(padding);36 Self {37 padding: Cow::Owned(padding),38 skip_empty_sections: true,39 #[cfg(feature = "exp-preserve-order")]40 preserve_order,41 }42 }43 pub fn std_to_toml(44 padding: String,45 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,46 ) -> Self {47 Self {48 padding: Cow::Owned(padding),49 skip_empty_sections: false,50 #[cfg(feature = "exp-preserve-order")]51 preserve_order,52 }53 }54}5556fn bare_allowed(s: &str) -> bool {57 s.bytes()58 .all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))59}6061fn escape_key_toml_buf(key: &str, buf: &mut String) {62 if bare_allowed(key) {63 buf.push_str(key);64 } else {65 escape_string_json_buf(key, buf);66 }67}6869fn is_section(val: &Val) -> Result<bool> {70 Ok(match val {71 Val::Arr(a) => {72 if a.is_empty() {73 return Ok(false);74 }75 for e in a.iter() {76 let e = e?;77 if !matches!(e, Val::Obj(_)) {78 return Ok(false);79 }80 }81 true82 }83 Val::Obj(_) => true,84 _ => false,85 })86}8788fn manifest_value(89 val: &Val,90 inline: bool,91 buf: &mut String,92 cur_padding: &str,93 options: &TomlFormat<'_>,94) -> Result<()> {95 use std::fmt::Write;96 match val {97 Val::Bool(true) => buf.push_str("true"),98 Val::Bool(false) => buf.push_str("false"),99 Val::Str(s) => {100 escape_string_json_buf(&s.clone().into_flat(), buf);101 }102 Val::Num(n) => write!(buf, "{n}").unwrap(),103 #[cfg(feature = "exp-bigint")]104 Val::BigInt(n) => write!(buf, "{n}").unwrap(),105 Val::Arr(a) => ensure_sufficient_stack(|| {106 buf.push('[');107108 let mut had_items = false;109 for (i, e) in a.iter().enumerate() {110 had_items = true;111 let e = e.with_description(|| format!("elem <{i}> evaluation"))?;112113 if i != 0 {114 buf.push(',');115 }116 if inline {117 buf.push(' ');118 } else {119 buf.push('\n');120 buf.push_str(cur_padding);121 buf.push_str(&options.padding);122 }123124 in_description_frame(125 || format!("elem <{i}> manifestification"),126 || manifest_value(&e, true, buf, "", options),127 )?;128 }129130 if !had_items {131 } else if inline {132 buf.push(' ');133 } else {134 buf.push('\n');135 buf.push_str(cur_padding);136 }137 buf.push(']');138 Ok::<_, Error>(())139 })?,140 Val::Obj(o) => ensure_sufficient_stack(|| {141 o.run_assertions()?;142 buf.push('{');143144 let mut had_fields = false;145 for (i, (k, v)) in o146 .iter(147 #[cfg(feature = "exp-preserve-order")]148 options.preserve_order,149 )150 .enumerate()151 {152 had_fields = true;153 let v = v.with_description(|| format!("field <{k}> evaluation"))?;154155 if i != 0 {156 buf.push(',');157 }158 buf.push(' ');159160 escape_key_toml_buf(&k, buf);161 buf.push_str(" = ");162 in_description_frame(163 || format!("field <{k}> manifestification"),164 || manifest_value(&v, true, buf, "", options),165 )?;166 }167168 if had_fields {169 buf.push(' ');170 }171172 buf.push('}');173 Ok::<_, Error>(())174 })?,175 Val::Null => {176 bail!("tried to manifest null")177 }178 Val::Func(_) => {179 bail!("tried to manifest function")180 }181 }182 Ok(())183}184185fn manifest_table_internal(186 obj: &ObjValue,187 path: &mut Vec<IStr>,188 buf: &mut String,189 cur_padding: &mut String,190 options: &TomlFormat<'_>,191) -> Result<()> {192 let mut sections = Vec::new();193 let mut first = true;194 for (key, value) in obj.iter(195 #[cfg(feature = "exp-preserve-order")]196 options.preserve_order,197 ) {198 let value = value.with_description(|| format!("field <{key}> evaluation"))?;199 if is_section(&value)? {200 sections.push((key, value));201 } else {202 if !first {203 buf.push('\n');204 }205 first = false;206 buf.push_str(cur_padding);207 escape_key_toml_buf(&key, buf);208 buf.push_str(" = ");209 in_description_frame(210 || format!("table <{key}> manifestification"),211 || manifest_value(&value, false, buf, cur_padding, options),212 )?;213 }214 }215 for (k, v) in sections {216 if !first {217 buf.push_str("\n\n");218 }219 first = false;220 path.push(k.clone());221 ensure_sufficient_stack(|| in_description_frame(222 || format!("section <{k}> manifestification"),223 || match v {224 Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options),225 Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options),226 _ => unreachable!("iterating over sections"),227 },228 ))?;229 path.pop();230 }231 Ok(())232}233234fn manifest_table(235 obj: &ObjValue,236 path: &mut Vec<IStr>,237 buf: &mut String,238 cur_padding: &mut String,239 options: &TomlFormat<'_>,240) -> Result<()> {241 if options.skip_empty_sections242 && !obj.is_empty()243 && obj244 .iter(245 #[cfg(feature = "exp-preserve-order")]246 false,247 )248 .try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?249 {250 manifest_table_internal(obj, path, buf, cur_padding, options)?;251 return Ok(());252 }253 buf.push_str(cur_padding);254 buf.push('[');255 for (i, k) in path.iter().enumerate() {256 if i != 0 {257 buf.push('.');258 }259 escape_key_toml_buf(k, buf);260 }261 buf.push(']');262 if obj.is_empty() {263 return Ok(());264 }265 buf.push('\n');266 let prev_len = cur_padding.len();267 cur_padding.push_str(&options.padding);268 manifest_table_internal(obj, path, buf, cur_padding, options)?;269 cur_padding.truncate(prev_len);270 Ok(())271}272fn manifest_table_array(273 arr: &ArrValue,274 path: &mut Vec<IStr>,275 buf: &mut String,276 cur_padding: &mut String,277 options: &TomlFormat<'_>,278) -> Result<()> {279 let mut formatted_path = String::new();280 {281 formatted_path.push_str(cur_padding);282 formatted_path.push_str("[[");283 for (i, k) in path.iter().enumerate() {284 if i != 0 {285 formatted_path.push('.');286 }287 escape_key_toml_buf(k, &mut formatted_path);288 }289 formatted_path.push_str("]]");290 }291 let prev_len = cur_padding.len();292 cur_padding.push_str(&options.padding);293 for (i, e) in arr.iter().enumerate() {294 let obj = e.expect("already tested").as_obj().expect("already tested");295 if i != 0 {296 buf.push_str("\n\n");297 }298 buf.push_str(&formatted_path);299 if obj.is_empty() {300 continue;301 }302 buf.push('\n');303 manifest_table_internal(&obj, path, buf, cur_padding, options)?;304 }305 cur_padding.truncate(prev_len);306 Ok(())307}308309impl ManifestFormat for TomlFormat<'_> {310 fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {311 match val {312 Val::Obj(obj) => {313 manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)314 }315 _ => bail!("toml body should be object"),316 }317 }318}