git.delta.rocks / jrsonnet / refs/commits / ddc0d176fb77

difftreelog

perf std.manifestTomlEx builtin

Yaroslav Bolyukin2022-12-03parent: #68bea05.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth
before · crates/jrsonnet-cli/src/manifest.rs
1use 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}
after · crates/jrsonnet-cli/src/manifest.rs
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}
modifiedcrates/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),
modifiedcrates/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),
+	))
+}
addedcrates/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"),
+		}
+	}
+}
modifiedcrates/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),