difftreelog
Merge pull request #64 from CertainLach/feat/manifest-yaml-doc-builtin
in: master
Make manifestYamlDoc builtin
6 files changed
crates/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)))
}
}
}
crates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth1use crate::error::Error::*;2use crate::error::Result;3use crate::{throw, Val};45#[derive(PartialEq, Clone, Copy)]6pub enum ManifestType {7 // Applied in manifestification8 Manifest,9 /// Used for std.manifestJson10 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest11 Std,12 /// No line breaks, used in `obj+''`13 ToString,14 /// Minified json15 Minify,16}1718pub struct ManifestJsonOptions<'s> {19 pub padding: &'s str,20 pub mtype: ManifestType,21}2223pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {24 let mut out = String::new();25 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;26 Ok(out)27}28fn manifest_json_ex_buf(29 val: &Val,30 buf: &mut String,31 cur_padding: &mut String,32 options: &ManifestJsonOptions<'_>,33) -> Result<()> {34 use std::fmt::Write;35 let mtype = options.mtype;36 match val {37 Val::Bool(v) => {38 if *v {39 buf.push_str("true");40 } else {41 buf.push_str("false");42 }43 }44 Val::Null => buf.push_str("null"),45 Val::Str(s) => escape_string_json_buf(s, buf),46 Val::Num(n) => write!(buf, "{}", n).unwrap(),47 Val::Arr(items) => {48 buf.push('[');49 if !items.is_empty() {50 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {51 buf.push('\n');52 }5354 let old_len = cur_padding.len();55 cur_padding.push_str(options.padding);56 for (i, item) in items.iter().enumerate() {57 if i != 0 {58 buf.push(',');59 if mtype == ManifestType::ToString {60 buf.push(' ');61 } else if mtype != ManifestType::Minify {62 buf.push('\n');63 }64 }65 buf.push_str(cur_padding);66 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;67 }68 cur_padding.truncate(old_len);6970 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {71 buf.push('\n');72 buf.push_str(cur_padding);73 }74 } else if mtype == ManifestType::Std {75 buf.push_str("\n\n");76 buf.push_str(cur_padding);77 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {78 buf.push(' ');79 }80 buf.push(']');81 }82 Val::Obj(obj) => {83 obj.run_assertions()?;84 buf.push('{');85 let fields = obj.fields();86 if !fields.is_empty() {87 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {88 buf.push('\n');89 }9091 let old_len = cur_padding.len();92 cur_padding.push_str(options.padding);93 for (i, field) in fields.into_iter().enumerate() {94 if i != 0 {95 buf.push(',');96 if mtype == ManifestType::ToString {97 buf.push(' ');98 } else if mtype != ManifestType::Minify {99 buf.push('\n');100 }101 }102 buf.push_str(cur_padding);103 escape_string_json_buf(&field, buf);104 buf.push_str(": ");105 crate::push(106 None,107 || format!("field <{}> manifestification", field.clone()),108 || {109 let value = obj.get(field.clone())?.unwrap();110 manifest_json_ex_buf(&value, buf, cur_padding, options)111 },112 )?;113 }114 cur_padding.truncate(old_len);115116 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {117 buf.push('\n');118 buf.push_str(cur_padding);119 }120 } else if mtype == ManifestType::Std {121 buf.push_str("\n\n");122 buf.push_str(cur_padding);123 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {124 buf.push(' ');125 }126 buf.push('}');127 }128 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),129 };130 Ok(())131}132133pub fn escape_string_json(s: &str) -> String {134 let mut buf = String::new();135 escape_string_json_buf(s, &mut buf);136 buf137}138139fn escape_string_json_buf(s: &str, buf: &mut String) {140 use std::fmt::Write;141 buf.push('"');142 for c in s.chars() {143 match c {144 '"' => buf.push_str("\\\""),145 '\\' => buf.push_str("\\\\"),146 '\u{0008}' => buf.push_str("\\b"),147 '\u{000c}' => buf.push_str("\\f"),148 '\n' => buf.push_str("\\n"),149 '\r' => buf.push_str("\\r"),150 '\t' => buf.push_str("\\t"),151 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {152 write!(buf, "\\u{:04x}", c as u32).unwrap()153 }154 c => buf.push(c),155 }156 }157 buf.push('"');158}1use crate::error::Error::*;2use crate::error::Result;3use crate::{throw, Val};45#[derive(PartialEq, Clone, Copy)]6pub enum ManifestType {7 // Applied in manifestification8 Manifest,9 /// Used for std.manifestJson10 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest11 Std,12 /// No line breaks, used in `obj+''`13 ToString,14 /// Minified json15 Minify,16}1718pub struct ManifestJsonOptions<'s> {19 pub padding: &'s str,20 pub mtype: ManifestType,21}2223pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {24 let mut out = String::new();25 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;26 Ok(out)27}28fn manifest_json_ex_buf(29 val: &Val,30 buf: &mut String,31 cur_padding: &mut String,32 options: &ManifestJsonOptions<'_>,33) -> Result<()> {34 use std::fmt::Write;35 let mtype = options.mtype;36 match val {37 Val::Bool(v) => {38 if *v {39 buf.push_str("true");40 } else {41 buf.push_str("false");42 }43 }44 Val::Null => buf.push_str("null"),45 Val::Str(s) => escape_string_json_buf(s, buf),46 Val::Num(n) => write!(buf, "{}", n).unwrap(),47 Val::Arr(items) => {48 buf.push('[');49 if !items.is_empty() {50 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {51 buf.push('\n');52 }5354 let old_len = cur_padding.len();55 cur_padding.push_str(options.padding);56 for (i, item) in items.iter().enumerate() {57 if i != 0 {58 buf.push(',');59 if mtype == ManifestType::ToString {60 buf.push(' ');61 } else if mtype != ManifestType::Minify {62 buf.push('\n');63 }64 }65 buf.push_str(cur_padding);66 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;67 }68 cur_padding.truncate(old_len);6970 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {71 buf.push('\n');72 buf.push_str(cur_padding);73 }74 } else if mtype == ManifestType::Std {75 buf.push_str("\n\n");76 buf.push_str(cur_padding);77 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {78 buf.push(' ');79 }80 buf.push(']');81 }82 Val::Obj(obj) => {83 obj.run_assertions()?;84 buf.push('{');85 let fields = obj.fields();86 if !fields.is_empty() {87 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {88 buf.push('\n');89 }9091 let old_len = cur_padding.len();92 cur_padding.push_str(options.padding);93 for (i, field) in fields.into_iter().enumerate() {94 if i != 0 {95 buf.push(',');96 if mtype == ManifestType::ToString {97 buf.push(' ');98 } else if mtype != ManifestType::Minify {99 buf.push('\n');100 }101 }102 buf.push_str(cur_padding);103 escape_string_json_buf(&field, buf);104 buf.push_str(": ");105 crate::push(106 None,107 || format!("field <{}> manifestification", field.clone()),108 || {109 let value = obj.get(field.clone())?.unwrap();110 manifest_json_ex_buf(&value, buf, cur_padding, options)111 },112 )?;113 }114 cur_padding.truncate(old_len);115116 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {117 buf.push('\n');118 buf.push_str(cur_padding);119 }120 } else if mtype == ManifestType::Std {121 buf.push_str("\n\n");122 buf.push_str(cur_padding);123 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {124 buf.push(' ');125 }126 buf.push('}');127 }128 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),129 };130 Ok(())131}132133pub fn escape_string_json(s: &str) -> String {134 let mut buf = String::new();135 escape_string_json_buf(s, &mut buf);136 buf137}138139fn escape_string_json_buf(s: &str, buf: &mut String) {140 use std::fmt::Write;141 buf.push('"');142 for c in s.chars() {143 match c {144 '"' => buf.push_str("\\\""),145 '\\' => buf.push_str("\\\\"),146 '\u{0008}' => buf.push_str("\\b"),147 '\u{000c}' => buf.push_str("\\f"),148 '\n' => buf.push_str("\\n"),149 '\r' => buf.push_str("\\r"),150 '\t' => buf.push_str("\\t"),151 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {152 write!(buf, "\\u{:04x}", c as u32).unwrap()153 }154 c => buf.push(c),155 }156 }157 buf.push('"');158}159160pub struct ManifestYamlOptions<'s> {161 /// Padding before fields, i.e162 /// ```yaml163 /// a:164 /// b:165 /// ## <- this166 /// ```167 pub padding: &'s str,168 /// Padding before array elements in objects169 /// ```yaml170 /// a:171 /// - 1172 /// ## <- this173 /// ```174 pub arr_element_padding: &'s str,175}176177pub 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}crates/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()))
})
}
crates/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 {
crates/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 {
crates/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