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
--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -1,4 +1,4 @@
-use std::{path::PathBuf, str::FromStr};
+use std::path::PathBuf;
 
 use clap::{Parser, ValueEnum};
 use jrsonnet_evaluator::{
@@ -6,7 +6,7 @@
 	manifest::{JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat},
 	State,
 };
-use jrsonnet_stdlib::YamlFormat;
+use jrsonnet_stdlib::{TomlFormat, YamlFormat};
 
 use crate::ConfigureState;
 
@@ -16,18 +16,7 @@
 	String,
 	Json,
 	Yaml,
-}
-
-impl FromStr for ManifestFormatName {
-	type Err = &'static str;
-	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
-		Ok(match s {
-			"string" => ManifestFormatName::String,
-			"json" => ManifestFormatName::Json,
-			"yaml" => ManifestFormatName::Yaml,
-			_ => return Err("no such format"),
-		})
-	}
+	Toml,
 }
 
 #[derive(Parser)]
@@ -44,7 +33,7 @@
 	#[clap(long, short = 'y', conflicts_with = "string")]
 	yaml_stream: bool,
 	/// Number of spaces to pad output manifest with.
-	/// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml]
+	/// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml/toml]
 	#[clap(long)]
 	line_padding: Option<usize>,
 	/// Preserve order in object manifestification
@@ -72,6 +61,11 @@
 					#[cfg(feature = "exp-preserve-order")]
 					preserve_order,
 				)),
+				ManifestFormatName::Toml => Box::new(TomlFormat::cli(
+					self.line_padding.unwrap_or(2),
+					#[cfg(feature = "exp-preserve-order")]
+					preserve_order,
+				)),
 			}
 		};
 		Ok(if self.yaml_stream {
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
before · crates/jrsonnet-stdlib/src/std.jsonnet
1{2  local std = self,3  local id = std.id,45  thisFile:: error 'std.thisFile is deprecated, to enable its support in jrsonnet - recompile it with "legacy-this-file" support.\nThis will slow down stdlib caching a bit, though',67  toString(a):: '' + a,89  lstripChars(str, chars)::10    if std.length(str) > 0 && std.member(chars, str[0]) then11      std.lstripChars(str[1:], chars)12    else13      str,1415  rstripChars(str, chars)::16    local len = std.length(str);17    if len > 0 && std.member(chars, str[len - 1]) then18      std.rstripChars(str[:len - 1], chars)19    else20      str,2122  stripChars(str, chars)::23    std.lstripChars(std.rstripChars(str, chars), chars),2425  stringChars(str)::26    std.makeArray(std.length(str), function(i) str[i]),2728  split(str, c):: std.splitLimit(str, c, -1),2930  mapWithIndex(func, arr)::31    if !std.isFunction(func) then32      error ('std.mapWithIndex first param must be function, got ' + std.type(func))33    else if !std.isArray(arr) && !std.isString(arr) then34      error ('std.mapWithIndex second param must be array, got ' + std.type(arr))35    else36      std.makeArray(std.length(arr), function(i) func(i, arr[i])),3738  mapWithKey(func, obj)::39    if !std.isFunction(func) then40      error ('std.mapWithKey first param must be function, got ' + std.type(func))41    else if !std.isObject(obj) then42      error ('std.mapWithKey second param must be object, got ' + std.type(obj))43    else44      { [k]: func(k, obj[k]) for k in std.objectFields(obj) },4546  lines(arr)::47    std.join('\n', arr + ['']),4849  deepJoin(arr)::50    if std.isString(arr) then51      arr52    else if std.isArray(arr) then53      std.join('', [std.deepJoin(x) for x in arr])54    else55      error 'Expected string or array, got %s' % std.type(arr),5657  filterMap(filter_func, map_func, arr)::58    if !std.isFunction(filter_func) then59      error ('std.filterMap first param must be function, got ' + std.type(filter_func))60    else if !std.isFunction(map_func) then61      error ('std.filterMap second param must be function, got ' + std.type(map_func))62    else if !std.isArray(arr) then63      error ('std.filterMap third param must be array, got ' + std.type(arr))64    else65      std.map(map_func, std.filter(filter_func, arr)),6667  assertEqual(a, b)::68    if a == b then69      true70    else71      error 'Assertion failed. ' + a + ' != ' + b,7273  clamp(x, minVal, maxVal)::74    if x < minVal then minVal75    else if x > maxVal then maxVal76    else x,7778  flattenArrays(arrs)::79    std.foldl(function(a, b) a + b, arrs, []),8081  manifestIni(ini)::82    local body_lines(body) =83      std.join([], [84        local value_or_values = body[k];85        if std.isArray(value_or_values) then86          ['%s = %s' % [k, value] for value in value_or_values]87        else88          ['%s = %s' % [k, value_or_values]]8990        for k in std.objectFields(body)91      ]);9293    local section_lines(sname, sbody) = ['[%s]' % [sname]] + body_lines(sbody),94          main_body = if std.objectHas(ini, 'main') then body_lines(ini.main) else [],95          all_sections = [96      section_lines(k, ini.sections[k])97      for k in std.objectFields(ini.sections)98    ];99    std.join('\n', main_body + std.flattenArrays(all_sections) + ['']),100101  manifestToml(value):: std.manifestTomlEx(value, '  '),102103  manifestTomlEx(value, indent)::104    local105      escapeStringToml = std.escapeStringJson,106      escapeKeyToml(key) =107        local bare_allowed = std.set(std.stringChars('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'));108        if std.setUnion(std.set(std.stringChars(key)), bare_allowed) == bare_allowed then key else escapeStringToml(key),109      isTableArray(v) = std.isArray(v) && std.length(v) > 0 && std.foldl(function(a, b) a && std.isObject(b), v, true),110      isSection(v) = std.isObject(v) || isTableArray(v),111      renderValue(v, indexedPath, inline, cindent) =112        if v == true then113          'true'114        else if v == false then115          'false'116        else if v == null then117          error 'Tried to manifest "null" at ' + indexedPath118        else if std.isNumber(v) then119          '' + v120        else if std.isString(v) then121          escapeStringToml(v)122        else if std.isFunction(v) then123          error 'Tried to manifest function at ' + indexedPath124        else if std.isArray(v) then125          if std.length(v) == 0 then126            '[]'127          else128            local range = std.range(0, std.length(v) - 1);129            local new_indent = if inline then '' else cindent + indent;130            local separator = if inline then ' ' else '\n';131            local lines = ['[' + separator]132                          + std.join([',' + separator],133                                     [134                                       [new_indent + renderValue(v[i], indexedPath + [i], true, '')]135                                       for i in range136                                     ])137                          + [separator + (if inline then '' else cindent) + ']'];138            std.join('', lines)139        else if std.isObject(v) then140          local lines = ['{ ']141                        + std.join([', '],142                                   [143                                     [escapeKeyToml(k) + ' = ' + renderValue(v[k], indexedPath + [k], true, '')]144                                     for k in std.objectFields(v)145                                   ])146                        + [' }'];147          std.join('', lines),148      renderTableInternal(v, path, indexedPath, cindent) =149        local kvp = std.flattenArrays([150          [cindent + escapeKeyToml(k) + ' = ' + renderValue(v[k], indexedPath + [k], false, cindent)]151          for k in std.objectFields(v)152          if !isSection(v[k])153        ]);154        local sections = [std.join('\n', kvp)] + [155          (156            if std.isObject(v[k]) then157              renderTable(v[k], path + [k], indexedPath + [k], cindent)158            else159              renderTableArray(v[k], path + [k], indexedPath + [k], cindent)160          )161          for k in std.objectFields(v)162          if isSection(v[k])163        ];164        std.join('\n\n', sections),165      renderTable(v, path, indexedPath, cindent) =166        cindent + '[' + std.join('.', std.map(escapeKeyToml, path)) + ']'167        + (if v == {} then '' else '\n')168        + renderTableInternal(v, path, indexedPath, cindent + indent),169      renderTableArray(v, path, indexedPath, cindent) =170        local range = std.range(0, std.length(v) - 1);171        local sections = [172          (cindent + '[[' + std.join('.', std.map(escapeKeyToml, path)) + ']]'173           + (if v[i] == {} then '' else '\n')174           + renderTableInternal(v[i], path, indexedPath + [i], cindent + indent))175          for i in range176        ];177        std.join('\n\n', sections);178    if std.isObject(value) then179      renderTableInternal(value, [], [], '')180    else181      error 'TOML body must be an object. Got ' + std.type(value),182183  escapeStringPython(str)::184    std.escapeStringJson(str),185186  escapeStringBash(str_)::187    local str = std.toString(str_);188    local trans(ch) =189      if ch == "'" then190        "'\"'\"'"191      else192        ch;193    "'%s'" % std.join('', [trans(ch) for ch in std.stringChars(str)]),194195  escapeStringDollars(str_)::196    local str = std.toString(str_);197    local trans(ch) =198      if ch == '$' then199        '$$'200      else201        ch;202    std.foldl(function(a, b) a + trans(b), std.stringChars(str), ''),203204  manifestJson(value):: std.manifestJsonEx(value, '    ') tailstrict,205206  manifestJsonMinified(value):: std.manifestJsonEx(value, '', '', ':'),207208  manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::209    if !std.isArray(value) then210      error 'manifestYamlStream only takes arrays, got ' + std.type(value)211    else212      '---\n' + std.join(213        '\n---\n', [std.manifestYamlDoc(e, indent_array_in_object) for e in value]214      ) + if c_document_end then '\n...\n' else '\n',215216217  manifestPython(v)::218    if std.isObject(v) then219      local fields = [220        '%s: %s' % [std.escapeStringPython(k), std.manifestPython(v[k])]221        for k in std.objectFields(v)222      ];223      '{%s}' % [std.join(', ', fields)]224    else if std.isArray(v) then225      '[%s]' % [std.join(', ', [std.manifestPython(v2) for v2 in v])]226    else if std.isString(v) then227      '%s' % [std.escapeStringPython(v)]228    else if std.isFunction(v) then229      error 'cannot manifest function'230    else if std.isNumber(v) then231      std.toString(v)232    else if v == true then233      'True'234    else if v == false then235      'False'236    else if v == null then237      'None',238239  manifestPythonVars(conf)::240    local vars = ['%s = %s' % [k, std.manifestPython(conf[k])] for k in std.objectFields(conf)];241    std.join('\n', vars + ['']),242243  manifestXmlJsonml(value)::244    if !std.isArray(value) then245      error 'Expected a JSONML value (an array), got %s' % std.type(value)246    else247      local aux(v) =248        if std.isString(v) then249          v250        else251          local tag = v[0];252          local has_attrs = std.length(v) > 1 && std.isObject(v[1]);253          local attrs = if has_attrs then v[1] else {};254          local children = if has_attrs then v[2:] else v[1:];255          local attrs_str =256            std.join('', [' %s="%s"' % [k, attrs[k]] for k in std.objectFields(attrs)]);257          std.deepJoin(['<', tag, attrs_str, '>', [aux(x) for x in children], '</', tag, '>']);258259      aux(value),260261  uniq(arr, keyF=id)::262    local f(a, b) =263      if std.length(a) == 0 then264        [b]265      else if keyF(a[std.length(a) - 1]) == keyF(b) then266        a267      else268        a + [b];269    std.foldl(f, arr, []),270271  set(arr, keyF=id)::272    std.uniq(std.sort(arr, keyF), keyF),273274  setMember(x, arr, keyF=id)::275    // TODO(dcunnin): Binary chop for O(log n) complexity276    std.length(std.setInter([x], arr, keyF)) > 0,277278  setUnion(a, b, keyF=id)::279    // NOTE: order matters, values in `a` win280    local aux(a, b, i, j, acc) =281      if i >= std.length(a) then282        acc + b[j:]283      else if j >= std.length(b) then284        acc + a[i:]285      else286        local ak = keyF(a[i]);287        local bk = keyF(b[j]);288        if ak == bk then289          aux(a, b, i + 1, j + 1, acc + [a[i]]) tailstrict290        else if ak < bk then291          aux(a, b, i + 1, j, acc + [a[i]]) tailstrict292        else293          aux(a, b, i, j + 1, acc + [b[j]]) tailstrict;294    aux(a, b, 0, 0, []),295296  setInter(a, b, keyF=id)::297    local aux(a, b, i, j, acc) =298      if i >= std.length(a) || j >= std.length(b) then299        acc300      else301        if keyF(a[i]) == keyF(b[j]) then302          aux(a, b, i + 1, j + 1, acc + [a[i]]) tailstrict303        else if keyF(a[i]) < keyF(b[j]) then304          aux(a, b, i + 1, j, acc) tailstrict305        else306          aux(a, b, i, j + 1, acc) tailstrict;307    aux(a, b, 0, 0, []) tailstrict,308309  setDiff(a, b, keyF=id)::310    local aux(a, b, i, j, acc) =311      if i >= std.length(a) then312        acc313      else if j >= std.length(b) then314        acc + a[i:]315      else316        if keyF(a[i]) == keyF(b[j]) then317          aux(a, b, i + 1, j + 1, acc) tailstrict318        else if keyF(a[i]) < keyF(b[j]) then319          aux(a, b, i + 1, j, acc + [a[i]]) tailstrict320        else321          aux(a, b, i, j + 1, acc) tailstrict;322    aux(a, b, 0, 0, []) tailstrict,323324  mergePatch(target, patch)::325    if std.isObject(patch) then326      local target_object =327        if std.isObject(target) then target else {};328329      local target_fields =330        if std.isObject(target_object) then std.objectFields(target_object) else [];331332      local null_fields = [k for k in std.objectFields(patch) if patch[k] == null];333      local both_fields = std.setUnion(target_fields, std.objectFields(patch));334335      {336        [k]:337          if !std.objectHas(patch, k) then338            target_object[k]339          else if !std.objectHas(target_object, k) then340            std.mergePatch(null, patch[k]) tailstrict341          else342            std.mergePatch(target_object[k], patch[k]) tailstrict343        for k in std.setDiff(both_fields, null_fields)344      }345    else346      patch,347348  get(o, f, default=null, inc_hidden=true)::349    if std.objectHasEx(o, f, inc_hidden) then o[f] else default,350351  objectFields(o)::352    std.objectFieldsEx(o, false),353354  objectFieldsAll(o)::355    std.objectFieldsEx(o, true),356357  objectHas(o, f)::358    std.objectHasEx(o, f, false),359360  objectHasAll(o, f)::361    std.objectHasEx(o, f, true),362363  objectValues(o)::364    [o[k] for k in std.objectFields(o)],365366  objectValuesAll(o)::367    [o[k] for k in std.objectFieldsAll(o)],368369  resolvePath(f, r)::370    local arr = std.split(f, '/');371    std.join('/', std.makeArray(std.length(arr) - 1, function(i) arr[i]) + [r]),372373  prune(a)::374    local isContent(b) =375      if b == null then376        false377      else if std.isArray(b) then378        std.length(b) > 0379      else if std.isObject(b) then380        std.length(b) > 0381      else382        true;383    if std.isArray(a) then384      [std.prune(x) for x in a if isContent($.prune(x))]385    else if std.isObject(a) then {386      [x]: $.prune(a[x])387      for x in std.objectFields(a)388      if isContent(std.prune(a[x]))389    } else390      a,391392  find(value, arr)::393    if !std.isArray(arr) then394      error 'find second parameter should be an array, got ' + std.type(arr)395    else396      std.filter(function(i) arr[i] == value, std.range(0, std.length(arr) - 1)),397}
after · crates/jrsonnet-stdlib/src/std.jsonnet
1{2  local std = self,3  local id = std.id,45  thisFile:: error 'std.thisFile is deprecated, to enable its support in jrsonnet - recompile it with "legacy-this-file" support.\nThis will slow down stdlib caching a bit, though',67  toString(a):: '' + a,89  lstripChars(str, chars)::10    if std.length(str) > 0 && std.member(chars, str[0]) then11      std.lstripChars(str[1:], chars)12    else13      str,1415  rstripChars(str, chars)::16    local len = std.length(str);17    if len > 0 && std.member(chars, str[len - 1]) then18      std.rstripChars(str[:len - 1], chars)19    else20      str,2122  stripChars(str, chars)::23    std.lstripChars(std.rstripChars(str, chars), chars),2425  stringChars(str)::26    std.makeArray(std.length(str), function(i) str[i]),2728  split(str, c):: std.splitLimit(str, c, -1),2930  mapWithIndex(func, arr)::31    if !std.isFunction(func) then32      error ('std.mapWithIndex first param must be function, got ' + std.type(func))33    else if !std.isArray(arr) && !std.isString(arr) then34      error ('std.mapWithIndex second param must be array, got ' + std.type(arr))35    else36      std.makeArray(std.length(arr), function(i) func(i, arr[i])),3738  mapWithKey(func, obj)::39    if !std.isFunction(func) then40      error ('std.mapWithKey first param must be function, got ' + std.type(func))41    else if !std.isObject(obj) then42      error ('std.mapWithKey second param must be object, got ' + std.type(obj))43    else44      { [k]: func(k, obj[k]) for k in std.objectFields(obj) },4546  lines(arr)::47    std.join('\n', arr + ['']),4849  deepJoin(arr)::50    if std.isString(arr) then51      arr52    else if std.isArray(arr) then53      std.join('', [std.deepJoin(x) for x in arr])54    else55      error 'Expected string or array, got %s' % std.type(arr),5657  filterMap(filter_func, map_func, arr)::58    if !std.isFunction(filter_func) then59      error ('std.filterMap first param must be function, got ' + std.type(filter_func))60    else if !std.isFunction(map_func) then61      error ('std.filterMap second param must be function, got ' + std.type(map_func))62    else if !std.isArray(arr) then63      error ('std.filterMap third param must be array, got ' + std.type(arr))64    else65      std.map(map_func, std.filter(filter_func, arr)),6667  assertEqual(a, b)::68    if a == b then69      true70    else71      error 'Assertion failed. ' + a + ' != ' + b,7273  clamp(x, minVal, maxVal)::74    if x < minVal then minVal75    else if x > maxVal then maxVal76    else x,7778  flattenArrays(arrs)::79    std.foldl(function(a, b) a + b, arrs, []),8081  manifestIni(ini)::82    local body_lines(body) =83      std.join([], [84        local value_or_values = body[k];85        if std.isArray(value_or_values) then86          ['%s = %s' % [k, value] for value in value_or_values]87        else88          ['%s = %s' % [k, value_or_values]]8990        for k in std.objectFields(body)91      ]);9293    local section_lines(sname, sbody) = ['[%s]' % [sname]] + body_lines(sbody),94          main_body = if std.objectHas(ini, 'main') then body_lines(ini.main) else [],95          all_sections = [96      section_lines(k, ini.sections[k])97      for k in std.objectFields(ini.sections)98    ];99    std.join('\n', main_body + std.flattenArrays(all_sections) + ['']),100101  manifestToml(value):: std.manifestTomlEx(value, '  '),102103  escapeStringPython(str)::104    std.escapeStringJson(str),105106  escapeStringBash(str_)::107    local str = std.toString(str_);108    local trans(ch) =109      if ch == "'" then110        "'\"'\"'"111      else112        ch;113    "'%s'" % std.join('', [trans(ch) for ch in std.stringChars(str)]),114115  escapeStringDollars(str_)::116    local str = std.toString(str_);117    local trans(ch) =118      if ch == '$' then119        '$$'120      else121        ch;122    std.foldl(function(a, b) a + trans(b), std.stringChars(str), ''),123124  manifestJson(value):: std.manifestJsonEx(value, '    ') tailstrict,125126  manifestJsonMinified(value):: std.manifestJsonEx(value, '', '', ':'),127128  manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::129    if !std.isArray(value) then130      error 'manifestYamlStream only takes arrays, got ' + std.type(value)131    else132      '---\n' + std.join(133        '\n---\n', [std.manifestYamlDoc(e, indent_array_in_object) for e in value]134      ) + if c_document_end then '\n...\n' else '\n',135136137  manifestPython(v)::138    if std.isObject(v) then139      local fields = [140        '%s: %s' % [std.escapeStringPython(k), std.manifestPython(v[k])]141        for k in std.objectFields(v)142      ];143      '{%s}' % [std.join(', ', fields)]144    else if std.isArray(v) then145      '[%s]' % [std.join(', ', [std.manifestPython(v2) for v2 in v])]146    else if std.isString(v) then147      '%s' % [std.escapeStringPython(v)]148    else if std.isFunction(v) then149      error 'cannot manifest function'150    else if std.isNumber(v) then151      std.toString(v)152    else if v == true then153      'True'154    else if v == false then155      'False'156    else if v == null then157      'None',158159  manifestPythonVars(conf)::160    local vars = ['%s = %s' % [k, std.manifestPython(conf[k])] for k in std.objectFields(conf)];161    std.join('\n', vars + ['']),162163  manifestXmlJsonml(value)::164    if !std.isArray(value) then165      error 'Expected a JSONML value (an array), got %s' % std.type(value)166    else167      local aux(v) =168        if std.isString(v) then169          v170        else171          local tag = v[0];172          local has_attrs = std.length(v) > 1 && std.isObject(v[1]);173          local attrs = if has_attrs then v[1] else {};174          local children = if has_attrs then v[2:] else v[1:];175          local attrs_str =176            std.join('', [' %s="%s"' % [k, attrs[k]] for k in std.objectFields(attrs)]);177          std.deepJoin(['<', tag, attrs_str, '>', [aux(x) for x in children], '</', tag, '>']);178179      aux(value),180181  uniq(arr, keyF=id)::182    local f(a, b) =183      if std.length(a) == 0 then184        [b]185      else if keyF(a[std.length(a) - 1]) == keyF(b) then186        a187      else188        a + [b];189    std.foldl(f, arr, []),190191  set(arr, keyF=id)::192    std.uniq(std.sort(arr, keyF), keyF),193194  setMember(x, arr, keyF=id)::195    // TODO(dcunnin): Binary chop for O(log n) complexity196    std.length(std.setInter([x], arr, keyF)) > 0,197198  setUnion(a, b, keyF=id)::199    // NOTE: order matters, values in `a` win200    local aux(a, b, i, j, acc) =201      if i >= std.length(a) then202        acc + b[j:]203      else if j >= std.length(b) then204        acc + a[i:]205      else206        local ak = keyF(a[i]);207        local bk = keyF(b[j]);208        if ak == bk then209          aux(a, b, i + 1, j + 1, acc + [a[i]]) tailstrict210        else if ak < bk then211          aux(a, b, i + 1, j, acc + [a[i]]) tailstrict212        else213          aux(a, b, i, j + 1, acc + [b[j]]) tailstrict;214    aux(a, b, 0, 0, []),215216  setInter(a, b, keyF=id)::217    local aux(a, b, i, j, acc) =218      if i >= std.length(a) || j >= std.length(b) then219        acc220      else221        if keyF(a[i]) == keyF(b[j]) then222          aux(a, b, i + 1, j + 1, acc + [a[i]]) tailstrict223        else if keyF(a[i]) < keyF(b[j]) then224          aux(a, b, i + 1, j, acc) tailstrict225        else226          aux(a, b, i, j + 1, acc) tailstrict;227    aux(a, b, 0, 0, []) tailstrict,228229  setDiff(a, b, keyF=id)::230    local aux(a, b, i, j, acc) =231      if i >= std.length(a) then232        acc233      else if j >= std.length(b) then234        acc + a[i:]235      else236        if keyF(a[i]) == keyF(b[j]) then237          aux(a, b, i + 1, j + 1, acc) tailstrict238        else if keyF(a[i]) < keyF(b[j]) then239          aux(a, b, i + 1, j, acc + [a[i]]) tailstrict240        else241          aux(a, b, i, j + 1, acc) tailstrict;242    aux(a, b, 0, 0, []) tailstrict,243244  mergePatch(target, patch)::245    if std.isObject(patch) then246      local target_object =247        if std.isObject(target) then target else {};248249      local target_fields =250        if std.isObject(target_object) then std.objectFields(target_object) else [];251252      local null_fields = [k for k in std.objectFields(patch) if patch[k] == null];253      local both_fields = std.setUnion(target_fields, std.objectFields(patch));254255      {256        [k]:257          if !std.objectHas(patch, k) then258            target_object[k]259          else if !std.objectHas(target_object, k) then260            std.mergePatch(null, patch[k]) tailstrict261          else262            std.mergePatch(target_object[k], patch[k]) tailstrict263        for k in std.setDiff(both_fields, null_fields)264      }265    else266      patch,267268  get(o, f, default=null, inc_hidden=true)::269    if std.objectHasEx(o, f, inc_hidden) then o[f] else default,270271  objectFields(o)::272    std.objectFieldsEx(o, false),273274  objectFieldsAll(o)::275    std.objectFieldsEx(o, true),276277  objectHas(o, f)::278    std.objectHasEx(o, f, false),279280  objectHasAll(o, f)::281    std.objectHasEx(o, f, true),282283  objectValues(o)::284    [o[k] for k in std.objectFields(o)],285286  objectValuesAll(o)::287    [o[k] for k in std.objectFieldsAll(o)],288289  resolvePath(f, r)::290    local arr = std.split(f, '/');291    std.join('/', std.makeArray(std.length(arr) - 1, function(i) arr[i]) + [r]),292293  prune(a)::294    local isContent(b) =295      if b == null then296        false297      else if std.isArray(b) then298        std.length(b) > 0299      else if std.isObject(b) then300        std.length(b) > 0301      else302        true;303    if std.isArray(a) then304      [std.prune(x) for x in a if isContent($.prune(x))]305    else if std.isObject(a) then {306      [x]: $.prune(a[x])307      for x in std.objectFields(a)308      if isContent(std.prune(a[x]))309    } else310      a,311312  find(value, arr)::313    if !std.isArray(arr) then314      error 'find second parameter should be an array, got ' + std.type(arr)315    else316      std.filter(function(i) arr[i] == value, std.range(0, std.length(arr) - 1)),317}