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
38 #[clap(long, short = 'y')]38 #[clap(long, short = 'y')]
39 yaml_stream: bool,39 yaml_stream: bool,
40 /// Number of spaces to pad output manifest with.40 /// Number of spaces to pad output manifest with.
41 /// `0` for hard tabs, `-1` for single line output41 /// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml]
42 #[clap(long, default_value = "3")]42 #[clap(long)]
43 line_padding: usize,43 line_padding: Option<usize>,
44}44}
45impl ConfigureState for ManifestOpts {45impl ConfigureState for ManifestOpts {
46 fn configure(&self, state: &EvaluationState) -> Result<()> {46 fn configure(&self, state: &EvaluationState) -> Result<()> {
50 match self.format {50 match self.format {
51 ManifestFormatName::String => state.set_manifest_format(ManifestFormat::String),51 ManifestFormatName::String => state.set_manifest_format(ManifestFormat::String),
52 ManifestFormatName::Json => {52 ManifestFormatName::Json => {
53 state.set_manifest_format(ManifestFormat::Json(self.line_padding))53 state.set_manifest_format(ManifestFormat::Json(self.line_padding.unwrap_or(3)))
54 }54 }
55 ManifestFormatName::Yaml => {55 ManifestFormatName::Yaml => {
56 state.set_manifest_format(ManifestFormat::Yaml(self.line_padding))56 state.set_manifest_format(ManifestFormat::Yaml(self.line_padding.unwrap_or(2)))
57 }57 }
58 }58 }
59 }59 }
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
1use crate::{1use crate::{
2 builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions},
2 equals,3 equals,
3 error::{Error::*, Result},4 error::{Error::*, Result},
4 operator::evaluate_mod_op,5 operator::evaluate_mod_op,
121 ("join".into(), builtin_join),122 ("join".into(), builtin_join),
122 ("escapeStringJson".into(), builtin_escape_string_json),123 ("escapeStringJson".into(), builtin_escape_string_json),
123 ("manifestJsonEx".into(), builtin_manifest_json_ex),124 ("manifestJsonEx".into(), builtin_manifest_json_ex),
125 ("manifestYamlDocImpl".into(), builtin_manifest_yaml_doc),
124 ("reverse".into(), builtin_reverse),126 ("reverse".into(), builtin_reverse),
125 ("id".into(), builtin_id),127 ("id".into(), builtin_id),
126 ("strReplace".into(), builtin_str_replace),128 ("strReplace".into(), builtin_str_replace),
768 })770 })
769}771}
772
773fn builtin_manifest_yaml_doc(
774 context: Context,
775 _loc: Option<&ExprLocation>,
776 args: &ArgsDesc,
777) -> Result<Val> {
778 parse_args!(context, "manifestYamlDoc", args, 2, [
779 0, value: ty!(any);
780 1, indent_array_in_object: ty!(boolean) => Val::Bool;
781 ], {
782 Ok(Val::Str(manifest_yaml_ex(&value, &ManifestYamlOptions {
783 padding: " ",
784 arr_element_padding: if indent_array_in_object { " " } else { "" },
785 })?.into()))
786 })
787}
770788
771fn builtin_reverse(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {789fn builtin_reverse(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
772 parse_args!(context, "reverse", args, 1, [790 parse_args!(context, "reverse", args, 1, [
794 1, from: ty!(string) => Val::Str;812 1, from: ty!(string) => Val::Str;
795 2, to: ty!(string) => Val::Str;813 2, to: ty!(string) => Val::Str;
796 ], {814 ], {
797 let mut out = String::new();815 Ok(Val::Str(str.replace(&from as &str, &to as &str).into()))
798 let mut last_idx = 0;
799 while let Some(idx) = (&str[last_idx..]).find(&from as &str) {
800 out.push_str(&str[last_idx..last_idx+idx]);
801 out.push_str(&to);
802 last_idx += idx + from.len();
803 }
804 if last_idx == 0 {
805 return Ok(Val::Str(str))
806 }
807 out.push_str(&str[last_idx..]);
808 Ok(Val::Str(out.into()))
809 })816 })
810}817}
811818
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
105 }))105 }))
106 }106 }
107
108 pub fn is_empty(&self) -> bool {
109 if !self.0.this_entries.is_empty() {
110 return false;
111 }
112 self.0
113 .super_obj
114 .as_ref()
115 .map(|s| s.is_empty())
116 .unwrap_or(true)
117 }
107118
108 /// Run callback for every field found in object119 /// Run callback for every field found in object
109 pub(crate) fn enum_fields(&self, handler: &mut impl FnMut(&IStr, &Visibility) -> bool) -> bool {120 pub(crate) fn enum_fields(&self, handler: &mut impl FnMut(&IStr, &Visibility) -> bool) -> bool {
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
2 builtin::{2 builtin::{
3 call_builtin,3 call_builtin,
4 manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},4 manifest::{
5 manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType,
6 ManifestYamlOptions,
7 },
5 },8 },
6 error::{Error::*, LocError},9 error::{Error::*, LocError},
7 evaluate,10 evaluate,
8 function::{parse_function_call, parse_function_call_map, place_args},11 function::{parse_function_call, parse_function_call_map, place_args},
9 native::NativeCallback,12 native::NativeCallback,
10 throw, with_state, Context, ObjValue, Result,13 throw, Context, ObjValue, Result,
11};14};
12use jrsonnet_gc::{Gc, GcCell, Trace};15use jrsonnet_gc::{Gc, GcCell, Trace};
13use jrsonnet_interner::IStr;16use jrsonnet_interner::IStr;
14use jrsonnet_parser::{el, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc};17use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};
15use jrsonnet_types::ValType;18use jrsonnet_types::ValType;
16use std::{collections::HashMap, fmt::Debug, rc::Rc};19use std::{collections::HashMap, fmt::Debug, rc::Rc};
1720
393 pub fn unwrap_num(self) -> Result<f64> {396 pub fn unwrap_num(self) -> Result<f64> {
394 Ok(matches_unwrap!(self, Self::Num(v), v))397 Ok(matches_unwrap!(self, Self::Num(v), v))
395 }398 }
399 pub fn unwrap_str(self) -> Result<IStr> {
400 Ok(matches_unwrap!(self, Self::Str(v), v))
401 }
402 pub fn unwrap_arr(self) -> Result<ArrValue> {
403 Ok(matches_unwrap!(self, Self::Arr(v), v))
404 }
396 pub fn unwrap_func(self) -> Result<Gc<FuncVal>> {405 pub fn unwrap_func(self) -> Result<Gc<FuncVal>> {
397 Ok(matches_unwrap!(self, Self::Func(v), v))406 Ok(matches_unwrap!(self, Self::Func(v), v))
398 }407 }
544 }553 }
545554
546 pub fn to_yaml(&self, padding: usize) -> Result<IStr> {555 pub fn to_yaml(&self, padding: usize) -> Result<IStr> {
547 with_state(|s| {556 let padding = &" ".repeat(padding);
548 let ctx = s
549 .create_default_context()
550 .with_var("__tmp__to_json__".into(), self.clone());
551 evaluate(557 manifest_yaml_ex(
552 ctx,558 self,
553 &el!(Expr::Apply(559 &ManifestYamlOptions {
554 el!(Expr::Index(
555 el!(Expr::Var("std".into())),
556 el!(Expr::Str("manifestYamlDoc".into()))
557 )),560 padding,
558 ArgsDesc::new(
559 vec![
560 el!(Expr::Var("__tmp__to_json__".into())),
561 el!(Expr::Literal(if padding != 0 {561 arr_element_padding: padding,
562 LiteralType::True
563 } else {
564 LiteralType::False
565 })),
566 ],
567 vec![]
568 ),
569 false
570 )),562 },
571 )?563 )
572 .try_cast_str("to json")564 .map(|s| s.into())
573 })
574 }565 }
575 pub fn into_indexable(self) -> Result<IndexableVal> {566 pub fn into_indexable(self) -> Result<IndexableVal> {
576 Ok(match self {567 Ok(match self {
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
374374
375 manifestJsonEx:: $intrinsic(manifestJsonEx),375 manifestJsonEx:: $intrinsic(manifestJsonEx),
376376
377 manifestYamlDoc(value, indent_array_in_object=false)::377 manifestYamlDocImpl:: $intrinsic(manifestYamlDocImpl),
378 local aux(v, path, cindent) =378
379 if v == true then379 manifestYamlDoc(value, indent_array_in_object=false):: std.manifestYamlDocImpl(value, indent_array_in_object),
380 'true'
381 else if v == false then
382 'false'
383 else if v == null then
384 'null'
385 else if std.isNumber(v) then
386 '' + v
387 else if std.isString(v) then
388 local len = std.length(v);
389 if len == 0 then
390 '""'
391 else if v[len - 1] == '\n' then
392 local split = std.split(v, '\n');
393 std.join('\n' + cindent + ' ', ['|'] + split[0:std.length(split) - 1])
394 else
395 std.escapeStringJson(v)
396 else if std.isFunction(v) then
397 error 'Tried to manifest function at ' + path
398 else if std.isArray(v) then
399 if std.length(v) == 0 then
400 '[]'
401 else
402 local params(value) =
403 if std.isArray(value) && std.length(value) > 0 then {
404 // While we could avoid the new line, it yields YAML that is
405 // hard to read, e.g.:
406 // - - - 1
407 // - 2
408 // - - 3
409 // - 4
410 new_indent: cindent + ' ',
411 space: '\n' + self.new_indent,
412 } else if std.isObject(value) && std.length(value) > 0 then {
413 new_indent: cindent + ' ',
414 // In this case we can start on the same line as the - because the indentation
415 // matches up then. The converse is not true, because fields are not always
416 // 1 character long.
417 space: ' ',
418 } else {
419 // In this case, new_indent is only used in the case of multi-line strings.
420 new_indent: cindent,
421 space: ' ',
422 };
423 local range = std.range(0, std.length(v) - 1);
424 local parts = [
425 '-' + param.space + aux(v[i], path + [i], param.new_indent)
426 for i in range
427 for param in [params(v[i])]
428 ];
429 std.join('\n' + cindent, parts)
430 else if std.isObject(v) then
431 if std.length(v) == 0 then
432 '{}'
433 else
434 local params(value) =
435 if std.isArray(value) && std.length(value) > 0 then {
436 // Not indenting allows e.g.
437 // ports:
438 // - 80
439 // instead of
440 // ports:
441 // - 80
442 new_indent: if indent_array_in_object then cindent + ' ' else cindent,
443 space: '\n' + self.new_indent,
444 } else if std.isObject(value) && std.length(value) > 0 then {
445 new_indent: cindent + ' ',
446 space: '\n' + self.new_indent,
447 } else {
448 // In this case, new_indent is only used in the case of multi-line strings.
449 new_indent: cindent,
450 space: ' ',
451 };
452 local lines = [
453 std.escapeStringJson(k) + ':' + param.space + aux(v[k], path + [k], param.new_indent)
454 for k in std.objectFields(v)
455 for param in [params(v[k])]
456 ];
457 std.join('\n' + cindent, lines);
458 aux(value, [], ''),
459380
460 manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::381 manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::
461 if !std.isArray(value) then382 if !std.isArray(value) then