difftreelog
feat proper section descriptions for toml
in: master
1 file changed
crates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth1use std::borrow::Cow;23use jrsonnet_evaluator::{4 IStr, ObjValue, Result, ResultExt, Val, bail, in_description_frame,5 manifest::{ManifestFormat, escape_string_json_buf},6 val::ArrValue,7};89pub struct TomlFormat<'s> {10 /// Padding before fields, i.e11 /// ```toml12 /// [a]13 /// b = 114 /// ## <- this15 /// ```16 padding: Cow<'s, str>,17 /// Do not emit sections for objects, consisting only from sections:18 /// ```toml19 /// # false20 /// [a]21 /// [a.b]22 ///23 /// # true24 /// [a.b]25 /// ```26 skip_empty_sections: bool,27 /// If true - then order of fields is preserved as written,28 /// instead of sorting alphabetically29 #[cfg(feature = "exp-preserve-order")]30 preserve_order: bool,31}32impl TomlFormat<'_> {33 pub fn cli(34 padding: usize,35 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,36 ) -> Self {37 let padding = " ".repeat(padding);38 Self {39 padding: Cow::Owned(padding),40 skip_empty_sections: true,41 #[cfg(feature = "exp-preserve-order")]42 preserve_order,43 }44 }45 pub fn std_to_toml(46 padding: String,47 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,48 ) -> Self {49 Self {50 padding: Cow::Owned(padding),51 skip_empty_sections: false,52 #[cfg(feature = "exp-preserve-order")]53 preserve_order,54 }55 }56}5758fn bare_allowed(s: &str) -> bool {59 s.bytes()60 .all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))61}6263fn escape_key_toml_buf(key: &str, buf: &mut String) {64 if bare_allowed(key) {65 buf.push_str(key);66 } else {67 escape_string_json_buf(key, buf);68 }69}7071fn is_section(val: &Val) -> Result<bool> {72 Ok(match val {73 Val::Arr(a) => {74 if a.is_empty() {75 return Ok(false);76 }77 for e in a.iter() {78 let e = e?;79 if !matches!(e, Val::Obj(_)) {80 return Ok(false);81 }82 }83 true84 }85 Val::Obj(_) => true,86 _ => false,87 })88}8990fn manifest_value(91 val: &Val,92 inline: bool,93 buf: &mut String,94 cur_padding: &str,95 options: &TomlFormat<'_>,96) -> Result<()> {97 use std::fmt::Write;98 match val {99 Val::Bool(true) => buf.push_str("true"),100 Val::Bool(false) => buf.push_str("false"),101 Val::Str(s) => {102 escape_string_json_buf(&s.clone().into_flat(), buf);103 }104 Val::Num(n) => write!(buf, "{n}").unwrap(),105 #[cfg(feature = "exp-bigint")]106 Val::BigInt(n) => write!(buf, "{n}").unwrap(),107 Val::Arr(a) => {108 buf.push('[');109110 let mut had_items = false;111 for (i, e) in a.iter().enumerate() {112 had_items = true;113 let e = e.with_description(|| format!("elem <{i}> evaluation"))?;114115 if i != 0 {116 buf.push(',');117 }118 if inline {119 buf.push(' ');120 } else {121 buf.push('\n');122 buf.push_str(cur_padding);123 buf.push_str(&options.padding);124 }125126 in_description_frame(127 || format!("elem <{i}> manifestification"),128 || manifest_value(&e, true, buf, "", options),129 )?;130 }131132 if !had_items {133 } else if inline {134 buf.push(' ');135 } else {136 buf.push('\n');137 buf.push_str(cur_padding);138 }139 buf.push(']');140 }141 Val::Obj(o) => {142 o.run_assertions()?;143 buf.push('{');144145 let mut had_fields = false;146 for (i, (k, v)) in o147 .iter(148 #[cfg(feature = "exp-preserve-order")]149 options.preserve_order,150 )151 .enumerate()152 {153 had_fields = true;154 let v = v.with_description(|| format!("field <{k}> evaluation"))?;155156 if i != 0 {157 buf.push(',');158 }159 buf.push(' ');160161 escape_key_toml_buf(&k, buf);162 buf.push_str(" = ");163 in_description_frame(164 || format!("field <{k}> manifestification"),165 || manifest_value(&v, true, buf, "", options),166 )?;167 }168169 if had_fields {170 buf.push(' ');171 }172173 buf.push('}');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 manifest_value(&value, false, buf, cur_padding, options)?;210 }211 }212 for (k, v) in sections {213 if !first {214 buf.push_str("\n\n");215 }216 first = false;217 path.push(k);218 match v {219 Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options)?,220 Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options)?,221 _ => unreachable!("iterating over sections"),222 }223 path.pop();224 }225 Ok(())226}227228fn manifest_table(229 obj: &ObjValue,230 path: &mut Vec<IStr>,231 buf: &mut String,232 cur_padding: &mut String,233 options: &TomlFormat<'_>,234) -> Result<()> {235 if options.skip_empty_sections236 && !obj.is_empty()237 && obj238 .iter(239 #[cfg(feature = "exp-preserve-order")]240 false,241 )242 .try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?243 {244 manifest_table_internal(obj, path, buf, cur_padding, options)?;245 return Ok(());246 }247 buf.push_str(cur_padding);248 buf.push('[');249 for (i, k) in path.iter().enumerate() {250 if i != 0 {251 buf.push('.');252 }253 escape_key_toml_buf(k, buf);254 }255 buf.push(']');256 if obj.is_empty() {257 return Ok(());258 }259 buf.push('\n');260 let prev_len = cur_padding.len();261 cur_padding.push_str(&options.padding);262 manifest_table_internal(obj, path, buf, cur_padding, options)?;263 cur_padding.truncate(prev_len);264 Ok(())265}266fn manifest_table_array(267 arr: &ArrValue,268 path: &mut Vec<IStr>,269 buf: &mut String,270 cur_padding: &mut String,271 options: &TomlFormat<'_>,272) -> Result<()> {273 let mut formatted_path = String::new();274 {275 formatted_path.push_str(cur_padding);276 formatted_path.push_str("[[");277 for (i, k) in path.iter().enumerate() {278 if i != 0 {279 formatted_path.push('.');280 }281 escape_key_toml_buf(k, &mut formatted_path);282 }283 formatted_path.push_str("]]");284 }285 let prev_len = cur_padding.len();286 cur_padding.push_str(&options.padding);287 for (i, e) in arr.iter().enumerate() {288 let obj = e.expect("already tested").as_obj().expect("already tested");289 if i != 0 {290 buf.push_str("\n\n");291 }292 buf.push_str(&formatted_path);293 if obj.is_empty() {294 continue;295 }296 buf.push('\n');297 manifest_table_internal(&obj, path, buf, cur_padding, options)?;298 }299 cur_padding.truncate(prev_len);300 Ok(())301}302303impl ManifestFormat for TomlFormat<'_> {304 fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {305 match val {306 Val::Obj(obj) => {307 manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)308 }309 _ => bail!("toml body should be object"),310 }311 }312}