difftreelog
perf std.manifestTomlEx builtin
in: master
5 files changed
crates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth1use std::{path::PathBuf, str::FromStr};23use clap::{Parser, ValueEnum};4use jrsonnet_evaluator::{5 error::Result,6 manifest::{JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat},7 State,8};9use jrsonnet_stdlib::YamlFormat;1011use crate::ConfigureState;1213#[derive(Clone, ValueEnum)]14pub enum ManifestFormatName {15 /// Expect string as output, and write them directly16 String,17 Json,18 Yaml,19}2021impl FromStr for ManifestFormatName {22 type Err = &'static str;23 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {24 Ok(match s {25 "string" => ManifestFormatName::String,26 "json" => ManifestFormatName::Json,27 "yaml" => ManifestFormatName::Yaml,28 _ => return Err("no such format"),29 })30 }31}3233#[derive(Parser)]34#[clap(next_help_heading = "MANIFESTIFICATION OUTPUT")]35pub struct ManifestOpts {36 /// Output format, wraps resulting value to corresponding std.manifest call.37 #[clap(long, short = 'f', default_value = "json")]38 format: ManifestFormatName,39 /// Expect plain string as output.40 /// Mutually exclusive with `--format`41 #[clap(long, short = 'S', conflicts_with = "format")]42 string: bool,43 /// Write output as YAML stream, can be used with --format json/yaml44 #[clap(long, short = 'y', conflicts_with = "string")]45 yaml_stream: bool,46 /// Number of spaces to pad output manifest with.47 /// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml]48 #[clap(long)]49 line_padding: Option<usize>,50 /// Preserve order in object manifestification51 #[cfg(feature = "exp-preserve-order")]52 #[clap(long)]53 pub preserve_order: bool,54}55impl ConfigureState for ManifestOpts {56 type Guards = Box<dyn ManifestFormat>;57 fn configure(&self, _s: &State) -> Result<Self::Guards> {58 let format: Box<dyn ManifestFormat> = if self.string {59 Box::new(StringFormat)60 } else {61 #[cfg(feature = "exp-preserve-order")]62 let preserve_order = self.preserve_order;63 match self.format {64 ManifestFormatName::String => Box::new(ToStringFormat),65 ManifestFormatName::Json => Box::new(JsonFormat::cli(66 self.line_padding.unwrap_or(3),67 #[cfg(feature = "exp-preserve-order")]68 preserve_order,69 )),70 ManifestFormatName::Yaml => Box::new(YamlFormat::cli(71 self.line_padding.unwrap_or(2),72 #[cfg(feature = "exp-preserve-order")]73 preserve_order,74 )),75 }76 };77 Ok(if self.yaml_stream {78 Box::new(YamlStreamFormat(format))79 } else {80 format81 })82 }83}8485#[derive(Parser)]86pub struct OutputOpts {87 /// Write to the output file rather than stdout88 #[clap(long, short = 'o')]89 pub output_file: Option<PathBuf>,90 /// Automatically creates all parent directories for files91 #[clap(long, short = 'c')]92 pub create_output_dirs: bool,93 /// Write multiple files to the directory, list files on stdout94 #[clap(long, short = 'm')]95 pub multi: Option<PathBuf>,96}1use std::path::PathBuf;23use clap::{Parser, ValueEnum};4use jrsonnet_evaluator::{5 error::Result,6 manifest::{JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat},7 State,8};9use jrsonnet_stdlib::{TomlFormat, YamlFormat};1011use crate::ConfigureState;1213#[derive(Clone, ValueEnum)]14pub enum ManifestFormatName {15 /// Expect string as output, and write them directly16 String,17 Json,18 Yaml,19 Toml,20}2122#[derive(Parser)]23#[clap(next_help_heading = "MANIFESTIFICATION OUTPUT")]24pub struct ManifestOpts {25 /// Output format, wraps resulting value to corresponding std.manifest call.26 #[clap(long, short = 'f', default_value = "json")]27 format: ManifestFormatName,28 /// Expect plain string as output.29 /// Mutually exclusive with `--format`30 #[clap(long, short = 'S', conflicts_with = "format")]31 string: bool,32 /// Write output as YAML stream, can be used with --format json/yaml33 #[clap(long, short = 'y', conflicts_with = "string")]34 yaml_stream: bool,35 /// Number of spaces to pad output manifest with.36 /// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml/toml]37 #[clap(long)]38 line_padding: Option<usize>,39 /// Preserve order in object manifestification40 #[cfg(feature = "exp-preserve-order")]41 #[clap(long)]42 pub preserve_order: bool,43}44impl ConfigureState for ManifestOpts {45 type Guards = Box<dyn ManifestFormat>;46 fn configure(&self, _s: &State) -> Result<Self::Guards> {47 let format: Box<dyn ManifestFormat> = if self.string {48 Box::new(StringFormat)49 } else {50 #[cfg(feature = "exp-preserve-order")]51 let preserve_order = self.preserve_order;52 match self.format {53 ManifestFormatName::String => Box::new(ToStringFormat),54 ManifestFormatName::Json => Box::new(JsonFormat::cli(55 self.line_padding.unwrap_or(3),56 #[cfg(feature = "exp-preserve-order")]57 preserve_order,58 )),59 ManifestFormatName::Yaml => Box::new(YamlFormat::cli(60 self.line_padding.unwrap_or(2),61 #[cfg(feature = "exp-preserve-order")]62 preserve_order,63 )),64 ManifestFormatName::Toml => Box::new(TomlFormat::cli(65 self.line_padding.unwrap_or(2),66 #[cfg(feature = "exp-preserve-order")]67 preserve_order,68 )),69 }70 };71 Ok(if self.yaml_stream {72 Box::new(YamlStreamFormat(format))73 } else {74 format75 })76 }77}7879#[derive(Parser)]80pub struct OutputOpts {81 /// Write to the output file rather than stdout82 #[clap(long, short = 'o')]83 pub output_file: Option<PathBuf>,84 /// Automatically creates all parent directories for files85 #[clap(long, short = 'c')]86 pub create_output_dirs: bool,87 /// Write multiple files to the directory, list files on stdout88 #[clap(long, short = 'm')]89 pub multi: Option<PathBuf>,90}crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -121,6 +121,7 @@
("escapeStringJson", builtin_escape_string_json::INST),
("manifestJsonEx", builtin_manifest_json_ex::INST),
("manifestYamlDoc", builtin_manifest_yaml_doc::INST),
+ ("manifestTomlEx", builtin_manifest_toml_ex::INST),
// Parsing
("parseJson", builtin_parse_json::INST),
("parseYaml", builtin_parse_yaml::INST),
crates/jrsonnet-stdlib/src/manifest/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/mod.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/mod.rs
@@ -1,3 +1,4 @@
+mod toml;
mod yaml;
use jrsonnet_evaluator::{
@@ -5,8 +6,9 @@
function::builtin,
manifest::{escape_string_json, JsonFormat},
typed::Any,
- IStr,
+ IStr, ObjValue, Val,
};
+pub use toml::TomlFormat;
pub use yaml::YamlFormat;
#[builtin]
@@ -47,3 +49,16 @@
preserve_order.unwrap_or(false),
))
}
+
+#[builtin]
+pub fn builtin_manifest_toml_ex(
+ value: ObjValue,
+ indent: IStr,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+ Val::Obj(value).manifest(TomlFormat::std_to_toml(
+ indent.to_string(),
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order.unwrap_or(false),
+ ))
+}
crates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest/toml.rs
@@ -0,0 +1,294 @@
+use std::borrow::Cow;
+
+use jrsonnet_evaluator::{
+ manifest::{escape_string_json_buf, ManifestFormat},
+ throw,
+ val::ArrValue,
+ IStr, ObjValue, Result, Val,
+};
+
+pub struct TomlFormat<'s> {
+ /// Padding before fields, i.e
+ /// ```toml
+ /// [a]
+ /// b = 1
+ /// ## <- this
+ /// ```
+ padding: Cow<'s, str>,
+ /// Do not emit sections for objects, consisting only from sections:
+ /// ```toml
+ /// # false
+ /// [a]
+ /// [a.b]
+ ///
+ /// # true
+ /// [a.b]
+ /// ```
+ skip_empty_sections: bool,
+ /// If true - then order of fields is preserved as written,
+ /// instead of sorting alphabetically
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+}
+impl TomlFormat<'_> {
+ pub fn cli(
+ padding: usize,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+ ) -> Self {
+ let padding = " ".repeat(padding);
+ Self {
+ padding: Cow::Owned(padding),
+ skip_empty_sections: true,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ }
+ }
+ pub fn std_to_toml(
+ padding: String,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+ ) -> Self {
+ Self {
+ padding: Cow::Owned(padding),
+ skip_empty_sections: false,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ }
+ }
+}
+
+fn bare_allowed(s: &str) -> bool {
+ s.bytes()
+ .all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))
+}
+
+fn escape_key_toml_buf(key: &str, buf: &mut String) {
+ if bare_allowed(key) {
+ buf.push_str(key);
+ } else {
+ escape_string_json_buf(key, buf);
+ }
+}
+
+fn is_section(val: &Val) -> Result<bool> {
+ Ok(match val {
+ Val::Arr(a) => {
+ if a.is_empty() {
+ return Ok(false);
+ }
+ for e in a.iter() {
+ let e = e?;
+ if !matches!(e, Val::Obj(_)) {
+ return Ok(false);
+ }
+ }
+ true
+ }
+ Val::Obj(_) => true,
+ _ => false,
+ })
+}
+
+fn manifest_value(
+ val: &Val,
+ inline: bool,
+ buf: &mut String,
+ cur_padding: &str,
+ options: &TomlFormat<'_>,
+) -> Result<()> {
+ use std::fmt::Write;
+ match val {
+ Val::Bool(true) => buf.push_str("true"),
+ Val::Bool(false) => buf.push_str("false"),
+ Val::Str(s) => {
+ escape_string_json_buf(&s.clone().into_flat(), buf);
+ }
+ Val::Num(n) => write!(buf, "{n}").unwrap(),
+ Val::Arr(a) => {
+ if a.is_empty() {
+ buf.push_str("[]");
+ return Ok(());
+ }
+ for (i, e) in a.iter().enumerate() {
+ let e = e?;
+ if i != 0 {
+ buf.push(',');
+ } else {
+ buf.push('[');
+ }
+ if inline {
+ buf.push(' ');
+ } else {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ buf.push_str(&options.padding);
+ }
+ manifest_value(&e, true, buf, "", options)?;
+ }
+ if inline {
+ buf.push(' ');
+ } else {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ }
+ buf.push(']');
+ }
+ Val::Obj(o) => {
+ if o.is_empty() {
+ buf.push_str("{}");
+ }
+ buf.push_str("{ ");
+ for (i, (k, v)) in o
+ .iter(
+ #[cfg(feature = "exp-preserve-order")]
+ options.preserve_order,
+ )
+ .enumerate()
+ {
+ let v = v?;
+ if i != 0 {
+ buf.push_str(", ");
+ }
+ escape_key_toml_buf(&k, buf);
+ buf.push_str(" = ");
+ manifest_value(&v, true, buf, "", options)?;
+ }
+ buf.push_str(" }");
+ }
+ Val::Null => {
+ throw!("tried to manifest null")
+ }
+ Val::Func(_) => {
+ throw!("tried to manifest function")
+ }
+ }
+ Ok(())
+}
+
+fn manifest_table_internal(
+ obj: &ObjValue,
+ path: &mut Vec<IStr>,
+ buf: &mut String,
+ cur_padding: &mut String,
+ options: &TomlFormat<'_>,
+) -> Result<()> {
+ let mut sections = Vec::new();
+ let mut first = true;
+ for (key, value) in obj.iter(
+ #[cfg(feature = "exp-preserve-order")]
+ options.preserve_order,
+ ) {
+ let value = value?;
+ if !is_section(&value)? {
+ if !first {
+ buf.push('\n');
+ }
+ first = false;
+ buf.push_str(cur_padding);
+ escape_key_toml_buf(&key, buf);
+ buf.push_str(" = ");
+ manifest_value(&value, false, buf, cur_padding, options)?;
+ } else {
+ sections.push((key, value));
+ }
+ }
+ for (k, v) in sections {
+ if !first {
+ buf.push_str("\n\n");
+ }
+ first = false;
+ path.push(k);
+ match v {
+ Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options)?,
+ Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options)?,
+ _ => unreachable!("iterating over sections"),
+ }
+ path.pop();
+ }
+ Ok(())
+}
+
+fn manifest_table(
+ obj: &ObjValue,
+ path: &mut Vec<IStr>,
+ buf: &mut String,
+ cur_padding: &mut String,
+ options: &TomlFormat<'_>,
+) -> Result<()> {
+ if options.skip_empty_sections
+ && !obj.is_empty()
+ && obj
+ .iter(
+ #[cfg(feature = "exp-preserve-order")]
+ false,
+ )
+ .try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?
+ {
+ manifest_table_internal(obj, path, buf, cur_padding, options)?;
+ return Ok(());
+ }
+ buf.push_str(cur_padding);
+ buf.push('[');
+ for (i, k) in path.iter().enumerate() {
+ if i != 0 {
+ buf.push('.');
+ }
+ escape_key_toml_buf(k, buf);
+ }
+ buf.push(']');
+ if obj.is_empty() {
+ return Ok(());
+ }
+ buf.push('\n');
+ let prev_len = cur_padding.len();
+ cur_padding.push_str(&options.padding);
+ manifest_table_internal(obj, path, buf, cur_padding, options)?;
+ cur_padding.truncate(prev_len);
+ Ok(())
+}
+fn manifest_table_array(
+ arr: &ArrValue,
+ path: &mut Vec<IStr>,
+ buf: &mut String,
+ cur_padding: &mut String,
+ options: &TomlFormat<'_>,
+) -> Result<()> {
+ let mut formatted_path = String::new();
+ {
+ formatted_path.push_str(cur_padding);
+ formatted_path.push_str("[[");
+ for (i, k) in path.iter().enumerate() {
+ if i != 0 {
+ formatted_path.push('.');
+ }
+ escape_key_toml_buf(k, &mut formatted_path);
+ }
+ formatted_path.push_str("]]");
+ }
+ let prev_len = cur_padding.len();
+ cur_padding.push_str(&options.padding);
+ for (i, e) in arr.iter().enumerate() {
+ let obj = e.expect("already tested").as_obj().expect("already tested");
+ if i != 0 {
+ buf.push_str("\n\n");
+ }
+ buf.push_str(&formatted_path);
+ if obj.is_empty() {
+ continue;
+ }
+ buf.push('\n');
+ manifest_table_internal(&obj, path, buf, cur_padding, options)?;
+ }
+ cur_padding.truncate(prev_len);
+ Ok(())
+}
+
+impl ManifestFormat for TomlFormat<'_> {
+ fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {
+ match val {
+ Val::Obj(obj) => {
+ manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)
+ }
+ _ => throw!("toml body should be object"),
+ }
+ }
+}
crates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -100,86 +100,6 @@
manifestToml(value):: std.manifestTomlEx(value, ' '),
- manifestTomlEx(value, indent)::
- local
- escapeStringToml = std.escapeStringJson,
- escapeKeyToml(key) =
- local bare_allowed = std.set(std.stringChars('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'));
- if std.setUnion(std.set(std.stringChars(key)), bare_allowed) == bare_allowed then key else escapeStringToml(key),
- isTableArray(v) = std.isArray(v) && std.length(v) > 0 && std.foldl(function(a, b) a && std.isObject(b), v, true),
- isSection(v) = std.isObject(v) || isTableArray(v),
- renderValue(v, indexedPath, inline, cindent) =
- if v == true then
- 'true'
- else if v == false then
- 'false'
- else if v == null then
- error 'Tried to manifest "null" at ' + indexedPath
- else if std.isNumber(v) then
- '' + v
- else if std.isString(v) then
- escapeStringToml(v)
- else if std.isFunction(v) then
- error 'Tried to manifest function at ' + indexedPath
- else if std.isArray(v) then
- if std.length(v) == 0 then
- '[]'
- else
- local range = std.range(0, std.length(v) - 1);
- local new_indent = if inline then '' else cindent + indent;
- local separator = if inline then ' ' else '\n';
- local lines = ['[' + separator]
- + std.join([',' + separator],
- [
- [new_indent + renderValue(v[i], indexedPath + [i], true, '')]
- for i in range
- ])
- + [separator + (if inline then '' else cindent) + ']'];
- std.join('', lines)
- else if std.isObject(v) then
- local lines = ['{ ']
- + std.join([', '],
- [
- [escapeKeyToml(k) + ' = ' + renderValue(v[k], indexedPath + [k], true, '')]
- for k in std.objectFields(v)
- ])
- + [' }'];
- std.join('', lines),
- renderTableInternal(v, path, indexedPath, cindent) =
- local kvp = std.flattenArrays([
- [cindent + escapeKeyToml(k) + ' = ' + renderValue(v[k], indexedPath + [k], false, cindent)]
- for k in std.objectFields(v)
- if !isSection(v[k])
- ]);
- local sections = [std.join('\n', kvp)] + [
- (
- if std.isObject(v[k]) then
- renderTable(v[k], path + [k], indexedPath + [k], cindent)
- else
- renderTableArray(v[k], path + [k], indexedPath + [k], cindent)
- )
- for k in std.objectFields(v)
- if isSection(v[k])
- ];
- std.join('\n\n', sections),
- renderTable(v, path, indexedPath, cindent) =
- cindent + '[' + std.join('.', std.map(escapeKeyToml, path)) + ']'
- + (if v == {} then '' else '\n')
- + renderTableInternal(v, path, indexedPath, cindent + indent),
- renderTableArray(v, path, indexedPath, cindent) =
- local range = std.range(0, std.length(v) - 1);
- local sections = [
- (cindent + '[[' + std.join('.', std.map(escapeKeyToml, path)) + ']]'
- + (if v[i] == {} then '' else '\n')
- + renderTableInternal(v[i], path, indexedPath + [i], cindent + indent))
- for i in range
- ];
- std.join('\n\n', sections);
- if std.isObject(value) then
- renderTableInternal(value, [], [], '')
- else
- error 'TOML body must be an object. Got ' + std.type(value),
-
escapeStringPython(str)::
std.escapeStringJson(str),