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

difftreelog

Merge pull request #64 from CertainLach/feat/manifest-yaml-doc-builtin

Yaroslav Bolyukin2021-10-29parents: #27b30fb #c0cb444.patch.diff
in: master
Make manifestYamlDoc builtin

6 files changed

modifiedcrates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -38,9 +38,9 @@
 	#[clap(long, short = 'y')]
 	yaml_stream: bool,
 	/// Number of spaces to pad output manifest with.
-	/// `0` for hard tabs, `-1` for single line output
-	#[clap(long, default_value = "3")]
-	line_padding: usize,
+	/// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml]
+	#[clap(long)]
+	line_padding: Option<usize>,
 }
 impl ConfigureState for ManifestOpts {
 	fn configure(&self, state: &EvaluationState) -> Result<()> {
@@ -50,10 +50,10 @@
 			match self.format {
 				ManifestFormatName::String => state.set_manifest_format(ManifestFormat::String),
 				ManifestFormatName::Json => {
-					state.set_manifest_format(ManifestFormat::Json(self.line_padding))
+					state.set_manifest_format(ManifestFormat::Json(self.line_padding.unwrap_or(3)))
 				}
 				ManifestFormatName::Yaml => {
-					state.set_manifest_format(ManifestFormat::Yaml(self.line_padding))
+					state.set_manifest_format(ManifestFormat::Yaml(self.line_padding.unwrap_or(2)))
 				}
 			}
 		}
modifiedcrates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth
157 buf.push('"');157 buf.push('"');
158}158}
159
160pub struct ManifestYamlOptions<'s> {
161 /// Padding before fields, i.e
162 /// ```yaml
163 /// a:
164 /// b:
165 /// ## <- this
166 /// ```
167 pub padding: &'s str,
168 /// Padding before array elements in objects
169 /// ```yaml
170 /// a:
171 /// - 1
172 /// ## <- this
173 /// ```
174 pub arr_element_padding: &'s str,
175}
176
177pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {
178 let mut out = String::new();
179 manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;
180 Ok(out)
181}
182fn manifest_yaml_ex_buf(
183 val: &Val,
184 buf: &mut String,
185 cur_padding: &mut String,
186 options: &ManifestYamlOptions<'_>,
187) -> Result<()> {
188 use std::fmt::Write;
189 match val {
190 Val::Bool(v) => {
191 if *v {
192 buf.push_str("true")
193 } else {
194 buf.push_str("false")
195 }
196 }
197 Val::Null => buf.push_str("null"),
198 Val::Str(s) => {
199 if s.is_empty() {
200 buf.push_str("\"\"");
201 } else if let Some(s) = s.strip_suffix('\n') {
202 buf.push('|');
203 for line in s.split('\n') {
204 buf.push('\n');
205 buf.push_str(options.padding);
206 buf.push_str(line);
207 }
208 } else {
209 escape_string_json_buf(s, buf)
210 }
211 }
212 Val::Num(n) => write!(buf, "{}", *n).unwrap(),
213 Val::Arr(a) => {
214 if a.is_empty() {
215 buf.push_str("[]");
216 } else {
217 for (i, item) in a.iter().enumerate() {
218 if i != 0 {
219 buf.push('\n');
220 buf.push_str(cur_padding);
221 }
222 let item = item?;
223 buf.push('-');
224 match &item {
225 Val::Arr(a) if !a.is_empty() => {
226 buf.push('\n');
227 buf.push_str(cur_padding);
228 buf.push_str(options.padding);
229 }
230 _ => buf.push(' '),
231 }
232 let extra_padding = match &item {
233 Val::Arr(a) => !a.is_empty(),
234 Val::Obj(o) => !o.is_empty(),
235 _ => false,
236 };
237 let prev_len = cur_padding.len();
238 if extra_padding {
239 cur_padding.push_str(options.padding);
240 }
241 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;
242 cur_padding.truncate(prev_len);
243 }
244 }
245 }
246 Val::Obj(o) => {
247 if o.is_empty() {
248 buf.push_str("{}");
249 } else {
250 for (i, key) in o.fields().iter().enumerate() {
251 if i != 0 {
252 buf.push('\n');
253 buf.push_str(cur_padding);
254 }
255 escape_string_json_buf(key, buf);
256 buf.push(':');
257 let prev_len = cur_padding.len();
258 let item = o.get(key.clone())?.expect("field exists");
259 match &item {
260 Val::Arr(a) if !a.is_empty() => {
261 buf.push('\n');
262 buf.push_str(cur_padding);
263 buf.push_str(options.arr_element_padding);
264 cur_padding.push_str(options.arr_element_padding);
265 }
266 Val::Obj(o) if !o.is_empty() => {
267 buf.push('\n');
268 buf.push_str(cur_padding);
269 buf.push_str(options.padding);
270 cur_padding.push_str(options.padding);
271 }
272 _ => buf.push(' '),
273 }
274 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;
275 cur_padding.truncate(prev_len);
276 }
277 }
278 }
279 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
280 }
281 Ok(())
282}
159283
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -1,4 +1,5 @@
 use crate::{
+	builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions},
 	equals,
 	error::{Error::*, Result},
 	operator::evaluate_mod_op,
@@ -121,6 +122,7 @@
 			("join".into(), builtin_join),
 			("escapeStringJson".into(), builtin_escape_string_json),
 			("manifestJsonEx".into(), builtin_manifest_json_ex),
+			("manifestYamlDocImpl".into(), builtin_manifest_yaml_doc),
 			("reverse".into(), builtin_reverse),
 			("id".into(), builtin_id),
 			("strReplace".into(), builtin_str_replace),
@@ -768,6 +770,22 @@
 	})
 }
 
+fn builtin_manifest_yaml_doc(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "manifestYamlDoc", args, 2, [
+		0, value: ty!(any);
+		1, indent_array_in_object: ty!(boolean) => Val::Bool;
+	], {
+		Ok(Val::Str(manifest_yaml_ex(&value, &ManifestYamlOptions {
+			padding: "  ",
+			arr_element_padding: if indent_array_in_object { "  " } else { "" },
+		})?.into()))
+	})
+}
+
 fn builtin_reverse(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
 	parse_args!(context, "reverse", args, 1, [
 		0, value: ty!(array) => Val::Arr;
@@ -794,18 +812,7 @@
 		1, from: ty!(string) => Val::Str;
 		2, to: ty!(string) => Val::Str;
 	], {
-		let mut out = String::new();
-		let mut last_idx = 0;
-		while let Some(idx) = (&str[last_idx..]).find(&from as &str) {
-			out.push_str(&str[last_idx..last_idx+idx]);
-			out.push_str(&to);
-			last_idx += idx + from.len();
-		}
-		if last_idx == 0 {
-			return Ok(Val::Str(str))
-		}
-		out.push_str(&str[last_idx..]);
-		Ok(Val::Str(out.into()))
+		Ok(Val::Str(str.replace(&from as &str, &to as &str).into()))
 	})
 }
 
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -105,6 +105,17 @@
 		}))
 	}
 
+	pub fn is_empty(&self) -> bool {
+		if !self.0.this_entries.is_empty() {
+			return false;
+		}
+		self.0
+			.super_obj
+			.as_ref()
+			.map(|s| s.is_empty())
+			.unwrap_or(true)
+	}
+
 	/// Run callback for every field found in object
 	pub(crate) fn enum_fields(&self, handler: &mut impl FnMut(&IStr, &Visibility) -> bool) -> bool {
 		if let Some(s) = &self.0.super_obj {
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -1,17 +1,20 @@
 use crate::{
 	builtin::{
 		call_builtin,
-		manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},
+		manifest::{
+			manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType,
+			ManifestYamlOptions,
+		},
 	},
 	error::{Error::*, LocError},
 	evaluate,
 	function::{parse_function_call, parse_function_call_map, place_args},
 	native::NativeCallback,
-	throw, with_state, Context, ObjValue, Result,
+	throw, Context, ObjValue, Result,
 };
 use jrsonnet_gc::{Gc, GcCell, Trace};
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{el, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc};
+use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};
 use jrsonnet_types::ValType;
 use std::{collections::HashMap, fmt::Debug, rc::Rc};
 
@@ -393,6 +396,12 @@
 	pub fn unwrap_num(self) -> Result<f64> {
 		Ok(matches_unwrap!(self, Self::Num(v), v))
 	}
+	pub fn unwrap_str(self) -> Result<IStr> {
+		Ok(matches_unwrap!(self, Self::Str(v), v))
+	}
+	pub fn unwrap_arr(self) -> Result<ArrValue> {
+		Ok(matches_unwrap!(self, Self::Arr(v), v))
+	}
 	pub fn unwrap_func(self) -> Result<Gc<FuncVal>> {
 		Ok(matches_unwrap!(self, Self::Func(v), v))
 	}
@@ -544,33 +553,15 @@
 	}
 
 	pub fn to_yaml(&self, padding: usize) -> Result<IStr> {
-		with_state(|s| {
-			let ctx = s
-				.create_default_context()
-				.with_var("__tmp__to_json__".into(), self.clone());
-			evaluate(
-				ctx,
-				&el!(Expr::Apply(
-					el!(Expr::Index(
-						el!(Expr::Var("std".into())),
-						el!(Expr::Str("manifestYamlDoc".into()))
-					)),
-					ArgsDesc::new(
-						vec![
-							el!(Expr::Var("__tmp__to_json__".into())),
-							el!(Expr::Literal(if padding != 0 {
-								LiteralType::True
-							} else {
-								LiteralType::False
-							})),
-						],
-						vec![]
-					),
-					false
-				)),
-			)?
-			.try_cast_str("to json")
-		})
+		let padding = &" ".repeat(padding);
+		manifest_yaml_ex(
+			self,
+			&ManifestYamlOptions {
+				padding,
+				arr_element_padding: padding,
+			},
+		)
+		.map(|s| s.into())
 	}
 	pub fn into_indexable(self) -> Result<IndexableVal> {
 		Ok(match self {
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -374,88 +374,9 @@
 
   manifestJsonEx:: $intrinsic(manifestJsonEx),
 
-  manifestYamlDoc(value, indent_array_in_object=false)::
-    local aux(v, path, cindent) =
-      if v == true then
-        'true'
-      else if v == false then
-        'false'
-      else if v == null then
-        'null'
-      else if std.isNumber(v) then
-        '' + v
-      else if std.isString(v) then
-        local len = std.length(v);
-        if len == 0 then
-          '""'
-        else if v[len - 1] == '\n' then
-          local split = std.split(v, '\n');
-          std.join('\n' + cindent + '  ', ['|'] + split[0:std.length(split) - 1])
-        else
-          std.escapeStringJson(v)
-      else if std.isFunction(v) then
-        error 'Tried to manifest function at ' + path
-      else if std.isArray(v) then
-        if std.length(v) == 0 then
-          '[]'
-        else
-          local params(value) =
-            if std.isArray(value) && std.length(value) > 0 then {
-              // While we could avoid the new line, it yields YAML that is
-              // hard to read, e.g.:
-              // - - - 1
-              //     - 2
-              //   - - 3
-              //     - 4
-              new_indent: cindent + '  ',
-              space: '\n' + self.new_indent,
-            } else if std.isObject(value) && std.length(value) > 0 then {
-              new_indent: cindent + '  ',
-              // In this case we can start on the same line as the - because the indentation
-              // matches up then.  The converse is not true, because fields are not always
-              // 1 character long.
-              space: ' ',
-            } else {
-              // In this case, new_indent is only used in the case of multi-line strings.
-              new_indent: cindent,
-              space: ' ',
-            };
-          local range = std.range(0, std.length(v) - 1);
-          local parts = [
-            '-' + param.space + aux(v[i], path + [i], param.new_indent)
-            for i in range
-            for param in [params(v[i])]
-          ];
-          std.join('\n' + cindent, parts)
-      else if std.isObject(v) then
-        if std.length(v) == 0 then
-          '{}'
-        else
-          local params(value) =
-            if std.isArray(value) && std.length(value) > 0 then {
-              // Not indenting allows e.g.
-              // ports:
-              // - 80
-              // instead of
-              // ports:
-              //   - 80
-              new_indent: if indent_array_in_object then cindent + '  ' else cindent,
-              space: '\n' + self.new_indent,
-            } else if std.isObject(value) && std.length(value) > 0 then {
-              new_indent: cindent + '  ',
-              space: '\n' + self.new_indent,
-            } else {
-              // In this case, new_indent is only used in the case of multi-line strings.
-              new_indent: cindent,
-              space: ' ',
-            };
-          local lines = [
-            std.escapeStringJson(k) + ':' + param.space + aux(v[k], path + [k], param.new_indent)
-            for k in std.objectFields(v)
-            for param in [params(v[k])]
-          ];
-          std.join('\n' + cindent, lines);
-    aux(value, [], ''),
+  manifestYamlDocImpl:: $intrinsic(manifestYamlDocImpl),
+
+  manifestYamlDoc(value, indent_array_in_object=false):: std.manifestYamlDocImpl(value, indent_array_in_object),
 
   manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::
     if !std.isArray(value) then