1use std::borrow::Cow;23use jrsonnet_evaluator::{4 bail,5 manifest::{escape_string_json_buf, ManifestFormat},6 val::ArrValue,7 IStr, ObjValue, Result, Val,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) => {109 if a.is_empty() {110 buf.push_str("[]");111 return Ok(());112 }113 for (i, e) in a.iter().enumerate() {114 let e = e?;115 if i != 0 {116 buf.push(',');117 } else {118 buf.push('[');119 }120 if inline {121 buf.push(' ');122 } else {123 buf.push('\n');124 buf.push_str(cur_padding);125 buf.push_str(&options.padding);126 }127 manifest_value(&e, true, buf, "", options)?;128 }129 if inline {130 buf.push(' ');131 } else {132 buf.push('\n');133 buf.push_str(cur_padding);134 }135 buf.push(']');136 }137 Val::Obj(o) => {138 if o.is_empty() {139 buf.push_str("{}");140 }141 buf.push_str("{ ");142 for (i, (k, v)) in o143 .iter(144 #[cfg(feature = "exp-preserve-order")]145 options.preserve_order,146 )147 .enumerate()148 {149 let v = v?;150 if i != 0 {151 buf.push_str(", ");152 }153 escape_key_toml_buf(&k, buf);154 buf.push_str(" = ");155 manifest_value(&v, true, buf, "", options)?;156 }157 buf.push_str(" }");158 }159 Val::Null => {160 bail!("tried to manifest null")161 }162 Val::Func(_) => {163 bail!("tried to manifest function")164 }165 }166 Ok(())167}168169fn manifest_table_internal(170 obj: &ObjValue,171 path: &mut Vec<IStr>,172 buf: &mut String,173 cur_padding: &mut String,174 options: &TomlFormat<'_>,175) -> Result<()> {176 let mut sections = Vec::new();177 let mut first = true;178 for (key, value) in obj.iter(179 #[cfg(feature = "exp-preserve-order")]180 options.preserve_order,181 ) {182 let value = value?;183 if !is_section(&value)? {184 if !first {185 buf.push('\n');186 }187 first = false;188 buf.push_str(cur_padding);189 escape_key_toml_buf(&key, buf);190 buf.push_str(" = ");191 manifest_value(&value, false, buf, cur_padding, options)?;192 } else {193 sections.push((key, value));194 }195 }196 for (k, v) in sections {197 if !first {198 buf.push_str("\n\n");199 }200 first = false;201 path.push(k);202 match v {203 Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options)?,204 Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options)?,205 _ => unreachable!("iterating over sections"),206 }207 path.pop();208 }209 Ok(())210}211212fn manifest_table(213 obj: &ObjValue,214 path: &mut Vec<IStr>,215 buf: &mut String,216 cur_padding: &mut String,217 options: &TomlFormat<'_>,218) -> Result<()> {219 if options.skip_empty_sections220 && !obj.is_empty()221 && obj222 .iter(223 #[cfg(feature = "exp-preserve-order")]224 false,225 )226 .try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?227 {228 manifest_table_internal(obj, path, buf, cur_padding, options)?;229 return Ok(());230 }231 buf.push_str(cur_padding);232 buf.push('[');233 for (i, k) in path.iter().enumerate() {234 if i != 0 {235 buf.push('.');236 }237 escape_key_toml_buf(k, buf);238 }239 buf.push(']');240 if obj.is_empty() {241 return Ok(());242 }243 buf.push('\n');244 let prev_len = cur_padding.len();245 cur_padding.push_str(&options.padding);246 manifest_table_internal(obj, path, buf, cur_padding, options)?;247 cur_padding.truncate(prev_len);248 Ok(())249}250fn manifest_table_array(251 arr: &ArrValue,252 path: &mut Vec<IStr>,253 buf: &mut String,254 cur_padding: &mut String,255 options: &TomlFormat<'_>,256) -> Result<()> {257 let mut formatted_path = String::new();258 {259 formatted_path.push_str(cur_padding);260 formatted_path.push_str("[[");261 for (i, k) in path.iter().enumerate() {262 if i != 0 {263 formatted_path.push('.');264 }265 escape_key_toml_buf(k, &mut formatted_path);266 }267 formatted_path.push_str("]]");268 }269 let prev_len = cur_padding.len();270 cur_padding.push_str(&options.padding);271 for (i, e) in arr.iter().enumerate() {272 let obj = e.expect("already tested").as_obj().expect("already tested");273 if i != 0 {274 buf.push_str("\n\n");275 }276 buf.push_str(&formatted_path);277 if obj.is_empty() {278 continue;279 }280 buf.push('\n');281 manifest_table_internal(&obj, path, buf, cur_padding, options)?;282 }283 cur_padding.truncate(prev_len);284 Ok(())285}286287impl ManifestFormat for TomlFormat<'_> {288 fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {289 match val {290 Val::Obj(obj) => {291 manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)292 }293 _ => bail!("toml body should be object"),294 }295 }296}