difftreelog
refactor move more stdlib functions to builtins
in: master
16 files changed
crates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -4,7 +4,7 @@
use jrsonnet_evaluator::manifest::{
JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat,
};
-use jrsonnet_stdlib::{TomlFormat, YamlFormat};
+use jrsonnet_stdlib::{TomlFormat, XmlJsonmlFormat, YamlFormat};
#[derive(Clone, Copy, ValueEnum)]
pub enum ManifestFormatName {
@@ -13,6 +13,7 @@
Json,
Yaml,
Toml,
+ XmlJsonml,
}
#[derive(Parser)]
@@ -70,10 +71,11 @@
#[cfg(feature = "exp-preserve-order")]
preserve_order,
)),
+ ManifestFormatName::XmlJsonml => Box::new(XmlJsonmlFormat::cli()),
}
};
if self.yaml_stream {
- Box::new(YamlStreamFormat(format))
+ Box::new(YamlStreamFormat::cli(format))
} else {
format
}
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -1,4 +1,7 @@
-use std::any::Any;
+use std::{
+ any::Any,
+ num::{NonZeroU32, NonZeroUsize},
+};
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::IBytes;
@@ -99,28 +102,29 @@
Self::new(RangeArray::new_inclusive(a, b))
}
+ /// # Panics
+ /// If step == 0
#[must_use]
- pub fn slice(
- self,
- from: Option<usize>,
- to: Option<usize>,
- step: Option<usize>,
- ) -> Option<Self> {
- let len = self.len();
- let from = from.unwrap_or(0);
- let to = to.unwrap_or(len).min(len);
- let step = step.unwrap_or(1);
+ pub fn slice(self, index: Option<i32>, end: Option<i32>, step: Option<NonZeroU32>) -> Self {
+ let get_idx = |pos: Option<i32>, len: usize, default| match pos {
+ Some(v) if v < 0 => len.saturating_sub((-v) as usize),
+ Some(v) => (v as usize).min(len),
+ None => default,
+ };
+ let index = get_idx(index, self.len(), 0);
+ let end = get_idx(end, self.len(), self.len());
+ let step = step.unwrap_or_else(|| NonZeroU32::new(1).expect("1 != 0"));
- if from >= to || step == 0 {
- return None;
+ if index >= end {
+ return Self::empty();
}
- Some(Self::new(SliceArray {
+ Self::new(SliceArray {
inner: self,
- from: from as u32,
- to: to as u32,
- step: step as u32,
- }))
+ from: index as u32,
+ to: end as u32,
+ step: step.get(),
+ })
}
/// Array length.
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -340,7 +340,28 @@
}
}
-pub struct YamlStreamFormat<I>(pub I);
+pub struct YamlStreamFormat<I> {
+ inner: I,
+ c_document_end: bool,
+ end_newline: bool,
+}
+impl<I> YamlStreamFormat<I> {
+ pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {
+ Self {
+ inner,
+ c_document_end,
+ // Stdlib format always inserts newline at the end
+ end_newline: true,
+ }
+ }
+ pub fn cli(inner: I) -> Self {
+ Self {
+ inner,
+ c_document_end: true,
+ end_newline: false,
+ }
+ }
+}
impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {
fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {
let Val::Arr(arr) = val else {
@@ -353,11 +374,16 @@
for v in arr.iter() {
let v = v?;
out.push_str("---\n");
- self.0.manifest_buf(v, out)?;
+ self.inner.manifest_buf(v, out)?;
out.push('\n');
}
+ }
+ if self.c_document_end {
out.push_str("...");
}
+ if self.end_newline {
+ out.push('\n');
+ }
Ok(())
}
}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -2,6 +2,7 @@
cell::RefCell,
fmt::{self, Debug, Display},
mem::replace,
+ num::{NonZeroU32, NonZeroUsize},
rc::Rc,
};
@@ -277,26 +278,11 @@
.into(),
))
}
- Self::Arr(arr) => {
- let get_idx = |pos: Option<i32>, len: usize, default| match pos {
- Some(v) if v < 0 => len.saturating_sub((-v) as usize),
- Some(v) => (v as usize).min(len),
- None => default,
- };
- let index = get_idx(index, arr.len(), 0);
- let end = get_idx(end, arr.len(), arr.len());
- let step = step.as_deref().copied().unwrap_or(1);
-
- if index >= end {
- return Ok(Self::Arr(ArrValue::empty()));
- }
-
- Ok(Self::Arr(
- arr.clone()
- .slice(Some(index), Some(end), Some(step))
- .expect("arguments checked"),
- ))
- }
+ Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(
+ index,
+ end,
+ step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),
+ ))),
}
}
}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -34,6 +34,12 @@
Ok(Some(attr))
}
+fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)
+where
+ Ident: PartialEq<I>,
+{
+ attrs.retain(|a| !a.path().is_ident(&ident));
+}
fn path_is(path: &Path, needed: &str) -> bool {
path.leading_colon.is_none()
@@ -121,10 +127,21 @@
}
}
+enum Optionality {
+ Required,
+ Optional,
+ Default(Expr),
+}
+impl Optionality {
+ fn is_optional(&self) -> bool {
+ !matches!(self, Self::Required)
+ }
+}
+
enum ArgInfo {
Normal {
ty: Box<Type>,
- is_option: bool,
+ optionality: Optionality,
name: Option<String>,
cfg_attrs: Vec<Attribute>,
},
@@ -138,7 +155,7 @@
}
impl ArgInfo {
- fn parse(name: &str, arg: &FnArg) -> Result<Self> {
+ fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {
let FnArg::Typed(arg) = arg else {
unreachable!()
};
@@ -163,7 +180,10 @@
_ => {}
}
- let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {
+ let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {
+ remove_attr(&mut arg.attrs, "default");
+ (Optionality::Default(default), ty.clone())
+ } else if let Some(ty) = extract_type_from_option(ty)? {
if type_is_path(ty, "Thunk").is_some() {
return Ok(Self::Lazy {
is_option: true,
@@ -171,9 +191,9 @@
});
}
- (true, Box::new(ty.clone()))
+ (Optionality::Optional, Box::new(ty.clone()))
} else {
- (false, ty.clone())
+ (Optionality::Required, ty.clone())
};
let cfg_attrs = arg
@@ -185,7 +205,7 @@
Ok(Self::Normal {
ty,
- is_option,
+ optionality,
name: ident.map(|v| v.to_string()),
cfg_attrs,
})
@@ -201,18 +221,14 @@
let item_fn = item.clone();
let item_fn: ItemFn = parse_macro_input!(item_fn);
- match builtin_inner(attr, item_fn, item.into()) {
+ match builtin_inner(attr, item_fn) {
Ok(v) => v.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[allow(clippy::too_many_lines)]
-fn builtin_inner(
- attr: BuiltinAttrs,
- fun: ItemFn,
- item: proc_macro2::TokenStream,
-) -> syn::Result<TokenStream> {
+fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {
let ReturnType::Type(_, result) = &fun.sig.output else {
return Err(Error::new(
fun.sig.span(),
@@ -224,13 +240,13 @@
let args = fun
.sig
.inputs
- .iter()
+ .iter_mut()
.map(|arg| ArgInfo::parse(&name, arg))
.collect::<Result<Vec<_>>>()?;
let params_desc = args.iter().filter_map(|a| match a {
ArgInfo::Normal {
- is_option,
+ optionality,
name,
cfg_attrs,
..
@@ -238,9 +254,10 @@
let name = name
.as_ref()
.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});
+ let is_optional = optionality.is_optional();
Some(quote! {
#(#cfg_attrs)*
- BuiltinParam::new(#name, #is_option),
+ BuiltinParam::new(#name, #is_optional),
})
}
ArgInfo::Lazy { is_option, name } => {
@@ -270,7 +287,7 @@
.map(|(id, a)| match a {
ArgInfo::Normal {
ty,
- is_option,
+ optionality,
name,
cfg_attrs,
} => {
@@ -279,17 +296,22 @@
|| format!("argument <{}> evaluation", #name),
|| <#ty>::from_untyped(value.evaluate()?),
)?};
- let value = if *is_option {
- quote! {if let Some(value) = &parsed[#id] {
+ let value = match optionality {
+ Optionality::Required => quote! {{
+ let value = parsed[#id].as_ref().expect("args shape is checked");
+ #eval
+ },},
+ Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {
Some(#eval)
} else {
None
- },}
- } else {
- quote! {{
- let value = parsed[#id].as_ref().expect("args shape is checked");
+ },},
+ Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {
#eval
- },}
+ } else {
+ let v: #ty = #expr;
+ v
+ },},
};
quote! {
#(#cfg_attrs)*
@@ -302,7 +324,7 @@
Some(value.clone())
} else {
None
- }}
+ },}
} else {
quote! {
parsed[#id].as_ref().expect("args shape is correct").clone(),
@@ -343,7 +365,7 @@
};
Ok(quote! {
- #item
+ #fun
#[doc(hidden)]
#[allow(non_camel_case_types)]
@@ -373,7 +395,7 @@
fn params(&self) -> &[BuiltinParam] {
PARAMS
}
- #[allow(unused_variable)]
+ #[allow(unused_variables)]
fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;
crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -265,21 +265,18 @@
}
#[builtin]
-pub fn builtin_remove_at(arr: ArrValue, at: usize) -> Result<ArrValue> {
+pub fn builtin_remove_at(arr: ArrValue, at: i32) -> Result<ArrValue> {
let newArrLeft = arr.clone().slice(None, Some(at), None);
let newArrRight = arr.slice(Some(at + 1), None, None);
- Ok(ArrValue::extended(
- newArrLeft.unwrap_or_else(ArrValue::empty),
- newArrRight.unwrap_or_else(ArrValue::empty),
- ))
+ Ok(ArrValue::extended(newArrLeft, newArrRight))
}
#[builtin]
pub fn builtin_remove(arr: ArrValue, elem: Val) -> Result<ArrValue> {
for (index, item) in arr.iter().enumerate() {
if equals(&item?, &elem)? {
- return builtin_remove_at(arr.clone(), index);
+ return builtin_remove_at(arr.clone(), index as i32);
}
}
Ok(arr)
@@ -325,7 +322,9 @@
#[builtin]
pub fn builtin_prune(
a: Val,
- #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Result<Val> {
fn is_content(val: &Val) -> bool {
match val {
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -102,6 +102,7 @@
("sign", builtin_sign::INST),
("max", builtin_max::INST),
("min", builtin_min::INST),
+ ("clamp", builtin_clamp::INST),
("sum", builtin_sum::INST),
("modulo", builtin_modulo::INST),
("floor", builtin_floor::INST),
@@ -163,11 +164,20 @@
("objectRemoveKey", builtin_object_remove_key::INST),
// Manifest
("escapeStringJson", builtin_escape_string_json::INST),
+ ("escapeStringPython", builtin_escape_string_json::INST),
+ ("escapeStringXML", builtin_escape_string_xml::INST),
("manifestJsonEx", builtin_manifest_json_ex::INST),
+ ("manifestJson", builtin_manifest_json::INST),
+ ("manifestJsonMinified", builtin_manifest_json_minified::INST),
("manifestYamlDoc", builtin_manifest_yaml_doc::INST),
+ ("manifestYamlStream", builtin_manifest_yaml_stream::INST),
("manifestTomlEx", builtin_manifest_toml_ex::INST),
+ ("manifestToml", builtin_manifest_toml::INST),
("toString", builtin_to_string::INST),
- // Parsing
+ ("manifestPython", builtin_manifest_python::INST),
+ ("manifestPythonVars", builtin_manifest_python_vars::INST),
+ ("manifestXmlJsonml", builtin_manifest_xml_jsonml::INST),
+ // Parse
("parseJson", builtin_parse_json::INST),
("parseYaml", builtin_parse_yaml::INST),
// Strings
@@ -175,10 +185,13 @@
("substr", builtin_substr::INST),
("char", builtin_char::INST),
("strReplace", builtin_str_replace::INST),
+ ("escapeStringBash", builtin_escape_string_bash::INST),
+ ("escapeStringDollars", builtin_escape_string_dollars::INST),
("isEmpty", builtin_is_empty::INST),
("equalsIgnoreCase", builtin_equals_ignore_case::INST),
("splitLimit", builtin_splitlimit::INST),
("splitLimitR", builtin_splitlimitr::INST),
+ ("split", builtin_split::INST),
("asciiUpper", builtin_ascii_upper::INST),
("asciiLower", builtin_ascii_lower::INST),
("findSubstr", builtin_find_substr::INST),
@@ -190,6 +203,7 @@
("stringChars", builtin_string_chars::INST),
// Misc
("length", builtin_length::INST),
+ ("get", builtin_get::INST),
("startsWith", builtin_starts_with::INST),
("endsWith", builtin_ends_with::INST),
// Sets
crates/jrsonnet-stdlib/src/manifest/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/mod.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/mod.rs
@@ -1,13 +1,17 @@
+mod python;
mod toml;
+mod xml;
mod yaml;
use jrsonnet_evaluator::{
function::builtin,
- manifest::{escape_string_json, JsonFormat},
+ manifest::{escape_string_json, JsonFormat, YamlStreamFormat},
IStr, ObjValue, Result, Val,
};
+pub use python::{PythonFormat, PythonVarsFormat};
pub use toml::TomlFormat;
pub use yaml::YamlFormat;
+pub use xml::XmlJsonmlFormat;
#[builtin]
pub fn builtin_escape_string_json(str_: IStr) -> Result<String> {
@@ -17,51 +21,149 @@
#[builtin]
pub fn builtin_manifest_json_ex(
value: Val,
- indent: IStr,
+ indent: String,
newline: Option<IStr>,
key_val_sep: Option<IStr>,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Result<String> {
let newline = newline.as_deref().unwrap_or("\n");
let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
value.manifest(JsonFormat::std_to_json(
- indent.to_string(),
+ indent,
newline,
key_val_sep,
#[cfg(feature = "exp-preserve-order")]
- preserve_order.unwrap_or(false),
+ preserve_order,
+ ))
+}
+
+#[builtin]
+pub fn builtin_manifest_json(
+ value: Val,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+) -> Result<String> {
+ builtin_manifest_json_ex(
+ value,
+ " ".to_owned(),
+ None,
+ None,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ )
+}
+
+#[builtin]
+pub fn builtin_manifest_json_minified(
+ value: Val,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+) -> Result<String> {
+ value.manifest(JsonFormat::minify(
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
))
}
#[builtin]
pub fn builtin_manifest_yaml_doc(
value: Val,
- indent_array_in_object: Option<bool>,
- quote_keys: Option<bool>,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+ #[default(false)] indent_array_in_object: bool,
+ #[default(true)] quote_keys: bool,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Result<String> {
value.manifest(YamlFormat::std_to_yaml(
- indent_array_in_object.unwrap_or(false),
- quote_keys.unwrap_or(true),
+ indent_array_in_object,
+ quote_keys,
#[cfg(feature = "exp-preserve-order")]
- preserve_order.unwrap_or(false),
+ preserve_order,
+ ))
+}
+
+#[builtin]
+pub fn builtin_manifest_yaml_stream(
+ value: Val,
+ #[default(false)] indent_array_in_object: bool,
+ #[default(true)] c_document_end: bool,
+ #[default(true)] quote_keys: bool,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+) -> Result<String> {
+ value.manifest(YamlStreamFormat::std_yaml_stream(
+ YamlFormat::std_to_yaml(
+ indent_array_in_object,
+ quote_keys,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ ),
+ c_document_end,
))
}
#[builtin]
pub fn builtin_manifest_toml_ex(
value: ObjValue,
- indent: IStr,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+ indent: String,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Result<String> {
Val::Obj(value).manifest(TomlFormat::std_to_toml(
- indent.to_string(),
+ indent,
#[cfg(feature = "exp-preserve-order")]
- preserve_order.unwrap_or(false),
+ preserve_order,
))
}
#[builtin]
+pub fn builtin_manifest_toml(
+ value: ObjValue,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+) -> Result<String> {
+ builtin_manifest_toml_ex(
+ value,
+ " ".to_owned(),
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ )
+}
+
+#[builtin]
pub fn builtin_to_string(a: Val) -> Result<IStr> {
a.to_string()
}
+
+#[builtin]
+pub fn builtin_manifest_python(v: Val) -> Result<String> {
+ v.manifest(PythonFormat {})
+}
+#[builtin]
+pub fn builtin_manifest_python_vars(v: Val) -> Result<String> {
+ v.manifest(PythonVarsFormat {})
+}
+
+#[builtin]
+pub fn builtin_escape_string_xml(str_: String) -> String {
+ xml::escape_string_xml(str_.as_str())
+}
+
+#[builtin]
+pub fn builtin_manifest_xml_jsonml(value: Val) -> Result<String> {
+ value.manifest(XmlJsonmlFormat::std_to_xml())
+}
crates/jrsonnet-stdlib/src/manifest/python.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest/python.rs
@@ -0,0 +1,87 @@
+use jrsonnet_evaluator::{
+ bail,
+ manifest::{escape_string_json_buf, ManifestFormat, ToStringFormat},
+ Result, Val,
+};
+
+pub struct PythonFormat {
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+}
+
+impl ManifestFormat for PythonFormat {
+ fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+ match val {
+ Val::Bool(true) => buf.push_str("True"),
+ Val::Bool(false) => buf.push_str("False"),
+ Val::Null => buf.push_str("None"),
+ Val::Str(s) => escape_string_json_buf(&s.to_string(), buf),
+ Val::Num(_) => ToStringFormat.manifest_buf(val, buf)?,
+ Val::Arr(arr) => {
+ buf.push('[');
+ for (i, el) in arr.iter().enumerate() {
+ let el = el?;
+ if i != 0 {
+ buf.push_str(", ");
+ }
+ self.manifest_buf(el, buf)?;
+ }
+ buf.push(']');
+ }
+ Val::Obj(obj) => {
+ obj.run_assertions()?;
+ buf.push('{');
+ let fields = obj.fields(
+ #[cfg(feature = "exp-preserve-order")]
+ self.preserve_order,
+ );
+ for (i, field) in fields.into_iter().enumerate() {
+ if i != 0 {
+ buf.push_str(", ");
+ }
+ escape_string_json_buf(&field, buf);
+ buf.push_str(": ");
+ let value = obj.get(field)?.expect("field exists");
+ self.manifest_buf(value, buf)?;
+ }
+ buf.push('}');
+ }
+ Val::Func(_) => bail!("tried to manifest function"),
+ }
+ Ok(())
+ }
+}
+
+pub struct PythonVarsFormat {
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+}
+
+impl PythonVarsFormat {}
+
+impl ManifestFormat for PythonVarsFormat {
+ fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+ let inner = PythonFormat {
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: self.preserve_order,
+ };
+ let Val::Obj(obj) = val else {
+ bail!("python vars root should be object");
+ };
+ obj.run_assertions()?;
+
+ let fields = obj.fields(
+ #[cfg(feature = "exp-preserve-order")]
+ self.preserve_order,
+ );
+
+ for field in fields {
+ // Yep, no escaping
+ buf.push_str(&field);
+ buf.push_str(" = ");
+ inner.manifest_buf(obj.get(field)?.expect("field exists"), buf)?;
+ buf.push('\n');
+ }
+ Ok(())
+ }
+}
crates/jrsonnet-stdlib/src/manifest/xml.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs
@@ -0,0 +1,173 @@
+use jrsonnet_evaluator::{
+ bail,
+ manifest::{ManifestFormat, ToStringFormat},
+ typed::{ComplexValType, Either4, Typed, ValType},
+ val::{ArrValue, IndexableVal},
+ Either, ObjValue, Result, ResultExt, Val,
+};
+
+pub struct XmlJsonmlFormat {
+ force_closing: bool,
+}
+impl XmlJsonmlFormat {
+ pub fn std_to_xml() -> Self {
+ Self {
+ force_closing: true,
+ }
+ }
+ pub fn cli() -> Self {
+ Self {
+ force_closing: false,
+ }
+ }
+}
+
+enum JSONMLValue {
+ Tag {
+ tag: String,
+ attrs: ObjValue,
+ children: Vec<JSONMLValue>,
+ },
+ String(String),
+}
+impl Typed for JSONMLValue {
+ const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);
+
+ fn into_untyped(_typed: Self) -> Result<Val> {
+ unreachable!("not used, reserved for parseXML?")
+ }
+
+ fn from_untyped(untyped: Val) -> Result<Self> {
+ let Val::Arr(arr) = untyped else {
+ if let Val::Str(s) = untyped {
+ return Ok(Self::String(s.to_string()));
+ };
+ bail!("expected JSONML value (an array or string)");
+ };
+ if arr.len() < 1 {
+ bail!("JSONML value should have tag");
+ };
+ let tag = String::from_untyped(
+ arr.get(0)
+ .with_description(|| "getting JSONML tag")?
+ .expect("length checked"),
+ )?;
+ let (has_attrs, attrs) = if arr.len() >= 2 {
+ let maybe_attrs = arr
+ .get(1)
+ .with_description(|| "getting JSONML attrs")?
+ .expect("length checked");
+ if let Val::Obj(attrs) = maybe_attrs {
+ (true, attrs)
+ } else {
+ (false, ObjValue::new_empty())
+ }
+ } else {
+ (false, ObjValue::new_empty())
+ };
+ Ok(Self::Tag {
+ tag,
+ attrs,
+ children: Typed::from_untyped(Val::Arr(arr.slice(
+ Some(if has_attrs { 2 } else { 1 }),
+ None,
+ None,
+ )))?,
+ })
+ }
+}
+
+impl ManifestFormat for XmlJsonmlFormat {
+ fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+ let val = JSONMLValue::from_untyped(val).with_description(|| "parsing JSONML value")?;
+ manifest_jsonml(&val, buf, self)
+ }
+}
+
+fn manifest_jsonml(v: &JSONMLValue, buf: &mut String, opts: &XmlJsonmlFormat) -> Result<()> {
+ match v {
+ JSONMLValue::Tag {
+ tag,
+ attrs,
+ children,
+ } => {
+ let has_children = !children.is_empty();
+ buf.push('<');
+ buf.push_str(&tag);
+ attrs.run_assertions()?;
+ for (key, value) in attrs.iter() {
+ buf.push(' ');
+ buf.push_str(&key);
+ buf.push('=');
+ buf.push('"');
+ let value = value?;
+ let value = if let Val::Str(s) = value {
+ s.to_string()
+ } else {
+ ToStringFormat.manifest(value)?
+ };
+ escape_string_xml_buf(&value, buf);
+ buf.push('"');
+ }
+ if !has_children && !opts.force_closing {
+ buf.push('/');
+ }
+ buf.push('>');
+ for child in children {
+ manifest_jsonml(&child, buf, opts)?;
+ }
+ if has_children || opts.force_closing {
+ buf.push('<');
+ buf.push('/');
+ buf.push_str(&tag);
+ buf.push('>');
+ }
+ Ok(())
+ }
+ JSONMLValue::String(s) => {
+ escape_string_xml_buf(s, buf);
+ Ok(())
+ }
+ }
+}
+
+pub fn escape_string_xml(str: &str) -> String {
+ let mut out = String::new();
+ escape_string_xml_buf(str, &mut out);
+ out
+}
+
+fn escape_string_xml_buf(str: &str, out: &mut String) {
+ if str.is_empty() {
+ return;
+ }
+ let mut remaining = str;
+
+ let mut found = false;
+ while let Some(position) = remaining
+ .bytes()
+ .position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))
+ {
+ found = true;
+
+ let (plain, rem) = remaining.split_at(position);
+ out.push_str(plain);
+
+ out.push_str(match rem.as_bytes()[0] {
+ b'<' => "<",
+ b'>' => ">",
+ b'&' => "&",
+ b'"' => """,
+ b'\'' => "'",
+ _ => unreachable!("position() searches for those matches"),
+ });
+
+ remaining = &rem[1..];
+ }
+ if !found {
+ // No match - no escapes required
+ out.push_str(&str);
+ return;
+ }
+ out.push_str(&remaining);
+}
crates/jrsonnet-stdlib/src/math.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/math.rs
+++ b/crates/jrsonnet-stdlib/src/math.rs
@@ -24,6 +24,12 @@
a.min(b)
}
+#[allow(non_snake_case)]
+#[builtin]
+pub fn builtin_clamp(x: f64, minVal: f64, maxVal: f64) -> f64 {
+ x.clamp(minVal, maxVal)
+}
+
#[builtin]
pub fn builtin_sum(arr: Vec<f64>) -> f64 {
arr.iter().sum()
crates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -23,6 +23,30 @@
}
}
+#[builtin]
+pub fn builtin_get(
+ o: ObjValue,
+ f: IStr,
+ default: Option<Thunk<Val>>,
+ #[default(true)]
+ inc_hidden: bool,
+) -> Result<Val> {
+ let do_default = move || {
+ let Some(default) = default else {
+ return Ok(Val::Null);
+ };
+ default.evaluate()
+ };
+ // Happy path for invisible fields
+ if !inc_hidden && !o.has_field_ex(f.clone(), false) {
+ return do_default();
+ }
+ let Some(v) = o.get(f)? else {
+ return do_default();
+ };
+ Ok(v)
+}
+
#[builtin(fields(
settings: Rc<RefCell<Settings>>,
))]
crates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -8,10 +8,11 @@
pub fn builtin_object_fields_ex(
obj: ObjValue,
hidden: bool,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Vec<Val> {
+
+ #[default(false)]
#[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
+ preserve_order: bool,
+) -> Vec<Val> {
let out = obj.fields_ex(
hidden,
#[cfg(feature = "exp-preserve-order")]
@@ -23,7 +24,10 @@
#[builtin]
pub fn builtin_object_fields(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Vec<Val> {
builtin_object_fields_ex(
o,
@@ -36,7 +40,10 @@
#[builtin]
pub fn builtin_object_fields_all(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Vec<Val> {
builtin_object_fields_ex(
o,
@@ -49,10 +56,9 @@
pub fn builtin_object_values_ex(
o: ObjValue,
include_hidden: bool,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
) -> ArrValue {
- #[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
o.values_ex(
include_hidden,
#[cfg(feature = "exp-preserve-order")]
@@ -62,7 +68,10 @@
#[builtin]
pub fn builtin_object_values(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ArrValue {
builtin_object_values_ex(
o,
@@ -74,7 +83,10 @@
#[builtin]
pub fn builtin_object_values_all(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ArrValue {
builtin_object_values_ex(
o,
@@ -87,10 +99,8 @@
pub fn builtin_object_keys_values_ex(
o: ObjValue,
include_hidden: bool,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
) -> ArrValue {
- #[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
o.key_values_ex(
include_hidden,
#[cfg(feature = "exp-preserve-order")]
@@ -100,7 +110,10 @@
#[builtin]
pub fn builtin_object_keys_values(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ArrValue {
builtin_object_keys_values_ex(
o,
@@ -112,7 +125,10 @@
#[builtin]
pub fn builtin_object_keys_values_all(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ArrValue {
builtin_object_keys_values_ex(
o,
@@ -141,12 +157,13 @@
pub fn builtin_object_remove_key(
obj: ObjValue,
key: IStr,
+
// Standard implementation uses std.objectFields without such argument, we can't
// assume order preservation should always be enabled/disabled
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ObjValue {
- #[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
let mut new_obj = ObjValueBuilder::with_capacity(obj.len() - 1);
for (k, v) in obj.iter(
#[cfg(feature = "exp-preserve-order")]
crates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -139,8 +139,12 @@
}
#[builtin]
-pub fn builtin_sort(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
- super::sort::sort(arr, keyF.unwrap_or_else(FuncVal::identity))
+pub fn builtin_sort(
+ arr: ArrValue,
+
+ #[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
+ super::sort::sort(arr, keyF)
}
fn uniq_identity(arr: Vec<Val>) -> Result<Vec<Val>> {
@@ -174,11 +178,14 @@
#[builtin]
#[allow(non_snake_case)]
-pub fn builtin_uniq(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_uniq(
+ arr: ArrValue,
+
+ #[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
if arr.len() <= 1 {
return Ok(arr);
}
- let keyF = keyF.unwrap_or(FuncVal::identity());
if keyF.is_identity() {
Ok(ArrValue::eager(uniq_identity(
arr.iter().collect::<Result<Vec<Val>>>()?,
@@ -190,11 +197,14 @@
#[builtin]
#[allow(non_snake_case)]
-pub fn builtin_set(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_set(
+ arr: ArrValue,
+
+ #[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
if arr.len() <= 1 {
return Ok(arr);
}
- let keyF = keyF.unwrap_or(FuncVal::identity());
if keyF.is_identity() {
let arr = arr.iter().collect::<Result<Vec<Val>>>()?;
let arr = sort_identity(arr)?;
crates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth1{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 lstripChars(str, chars)::8 if std.length(str) > 0 && std.member(chars, str[0]) then9 std.lstripChars(str[1:], chars)10 else11 str,1213 rstripChars(str, chars)::14 local len = std.length(str);15 if len > 0 && std.member(chars, str[len - 1]) then16 std.rstripChars(str[:len - 1], chars)17 else18 str,1920 stripChars(str, chars)::21 std.lstripChars(std.rstripChars(str, chars), chars),2223 split(str, c):: std.splitLimit(str, c, -1),2425 mapWithIndex(func, arr)::26 if !std.isFunction(func) then27 error ('std.mapWithIndex first param must be function, got ' + std.type(func))28 else if !std.isArray(arr) && !std.isString(arr) then29 error ('std.mapWithIndex second param must be array, got ' + std.type(arr))30 else31 std.makeArray(std.length(arr), function(i) func(i, arr[i])),3233 mapWithKey(func, obj)::34 if !std.isFunction(func) then35 error ('std.mapWithKey first param must be function, got ' + std.type(func))36 else if !std.isObject(obj) then37 error ('std.mapWithKey second param must be object, got ' + std.type(obj))38 else39 { [k]: func(k, obj[k]) for k in std.objectFields(obj) },4041 lines(arr)::42 std.join('\n', arr + ['']),4344 deepJoin(arr)::45 if std.isString(arr) then46 arr47 else if std.isArray(arr) then48 std.join('', [std.deepJoin(x) for x in arr])49 else50 error 'Expected string or array, got %s' % std.type(arr),5152 assertEqual(a, b)::53 if a == b then54 true55 else56 error 'Assertion failed. ' + a + ' != ' + b,5758 clamp(x, minVal, maxVal)::59 if x < minVal then minVal60 else if x > maxVal then maxVal61 else x,6263 manifestIni(ini)::64 local body_lines(body) =65 std.join([], [66 local value_or_values = body[k];67 if std.isArray(value_or_values) then68 ['%s = %s' % [k, value] for value in value_or_values]69 else70 ['%s = %s' % [k, value_or_values]]7172 for k in std.objectFields(body)73 ]);7475 local section_lines(sname, sbody) = ['[%s]' % [sname]] + body_lines(sbody),76 main_body = if std.objectHas(ini, 'main') then body_lines(ini.main) else [],77 all_sections = [78 section_lines(k, ini.sections[k])79 for k in std.objectFields(ini.sections)80 ];81 std.join('\n', main_body + std.flattenArrays(all_sections) + ['']),8283 manifestToml(value):: std.manifestTomlEx(value, ' '),8485 escapeStringPython(str)::86 std.escapeStringJson(str),8788 escapeStringBash(str_)::89 local str = std.toString(str_);90 local trans(ch) =91 if ch == "'" then92 "'\"'\"'"93 else94 ch;95 "'%s'" % std.join('', [trans(ch) for ch in std.stringChars(str)]),9697 escapeStringDollars(str_)::98 local str = std.toString(str_);99 local trans(ch) =100 if ch == '$' then101 '$$'102 else103 ch;104 std.foldl(function(a, b) a + trans(b), std.stringChars(str), ''),105106 local xml_escapes = {107 '<': '<',108 '>': '>',109 '&': '&',110 '"': '"',111 "'": ''',112 },113114 escapeStringXML(str_)::115 local str = std.toString(str_);116 std.join('', [std.get(xml_escapes, ch, ch) for ch in std.stringChars(str)]),117118 manifestJson(value):: std.manifestJsonEx(value, ' ') tailstrict,119120 manifestJsonMinified(value):: std.manifestJsonEx(value, '', '', ':'),121122 manifestYamlStream(value, indent_array_in_object=false, c_document_end=true, quote_keys=true)::123 if !std.isArray(value) then124 error 'manifestYamlStream only takes arrays, got ' + std.type(value)125 else126 '---\n' + std.join(127 '\n---\n', [std.manifestYamlDoc(e, indent_array_in_object, quote_keys) for e in value]128 ) + if c_document_end then '\n...\n' else '\n',129130 manifestPython(v)::131 if std.isObject(v) then132 local fields = [133 '%s: %s' % [std.escapeStringPython(k), std.manifestPython(v[k])]134 for k in std.objectFields(v)135 ];136 '{%s}' % [std.join(', ', fields)]137 else if std.isArray(v) then138 '[%s]' % [std.join(', ', [std.manifestPython(v2) for v2 in v])]139 else if std.isString(v) then140 '%s' % [std.escapeStringPython(v)]141 else if std.isFunction(v) then142 error 'cannot manifest function'143 else if std.isNumber(v) then144 std.toString(v)145 else if v == true then146 'True'147 else if v == false then148 'False'149 else if v == null then150 'None',151152 manifestPythonVars(conf)::153 local vars = ['%s = %s' % [k, std.manifestPython(conf[k])] for k in std.objectFields(conf)];154 std.join('\n', vars + ['']),155156 manifestXmlJsonml(value)::157 if !std.isArray(value) then158 error 'Expected a JSONML value (an array), got %s' % std.type(value)159 else160 local aux(v) =161 if std.isString(v) then162 v163 else164 local tag = v[0];165 local has_attrs = std.length(v) > 1 && std.isObject(v[1]);166 local attrs = if has_attrs then v[1] else {};167 local children = if has_attrs then v[2:] else v[1:];168 local attrs_str =169 std.join('', [' %s="%s"' % [k, attrs[k]] for k in std.objectFields(attrs)]);170 std.deepJoin(['<', tag, attrs_str, '>', [aux(x) for x in children], '</', tag, '>']);171172 aux(value),173174 mergePatch(target, patch)::175 if std.isObject(patch) then176 local target_object =177 if std.isObject(target) then target else {};178179 local target_fields =180 if std.isObject(target_object) then std.objectFields(target_object) else [];181182 local null_fields = [k for k in std.objectFields(patch) if patch[k] == null];183 local both_fields = std.setUnion(target_fields, std.objectFields(patch));184185 {186 [k]:187 if !std.objectHas(patch, k) then188 target_object[k]189 else if !std.objectHas(target_object, k) then190 std.mergePatch(null, patch[k]) tailstrict191 else192 std.mergePatch(target_object[k], patch[k]) tailstrict193 for k in std.setDiff(both_fields, null_fields)194 }195 else196 patch,197198 get(o, f, default=null, inc_hidden=true)::199 if std.objectHasEx(o, f, inc_hidden) then o[f] else default,200201 resolvePath(f, r)::202 local arr = std.split(f, '/');203 std.join('/', std.makeArray(std.length(arr) - 1, function(i) arr[i]) + [r]),204205 find(value, arr)::206 if !std.isArray(arr) then207 error 'find second parameter should be an array, got ' + std.type(arr)208 else209 std.filter(function(i) arr[i] == value, std.range(0, std.length(arr) - 1)),210211 // Compat212 __compare_array(arr1, arr2)::213 assert std.isArray(arr1) && std.isArray(arr2);214 std.__compare(arr1, arr2),215 __array_less(arr1, arr2):: std.__compare_array(arr1, arr2) == -1,216 __array_greater(arr1, arr2):: std.__compare_array(arr1, arr2) == 1,217 __array_less_or_equal(arr1, arr2):: std.__compare_array(arr1, arr2) <= 0,218 __array_greater_or_equal(arr1, arr2):: std.__compare_array(arr1, arr2) >= 0,219}crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -28,6 +28,20 @@
}
#[builtin]
+pub fn builtin_escape_string_bash(str: String) -> String {
+ const QUOTE: char = '\'';
+ let mut out = str.replace(QUOTE, "'\"'\"'");
+ out.insert(0, QUOTE);
+ out.push(QUOTE);
+ out
+}
+
+#[builtin]
+pub fn builtin_escape_string_dollars(str: String) -> String {
+ str.replace('$', "$$")
+}
+
+#[builtin]
pub fn builtin_is_empty(str: String) -> bool {
str.is_empty()
}
@@ -66,6 +80,12 @@
}
#[builtin]
+pub fn builtin_split(str: IStr, c: IStr) -> ArrValue {
+ use Either2::*;
+ builtin_splitlimit(str, c, B(M1))
+}
+
+#[builtin]
pub fn builtin_ascii_upper(str: IStr) -> String {
str.to_ascii_uppercase()
}