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.rsdiffbeforeafterboth1use jrsonnet_evaluator::{function::builtin, typed::PositiveF64};23#[builtin]4pub fn builtin_abs(n: f64) -> f64 {5 n.abs()6}78#[builtin]9pub fn builtin_sign(n: f64) -> f64 {10 if n == 0. {11 0.12 } else {13 n.signum()14 }15}1617#[builtin]18pub fn builtin_max(a: f64, b: f64) -> f64 {19 a.max(b)20}2122#[builtin]23pub fn builtin_min(a: f64, b: f64) -> f64 {24 a.min(b)25}2627#[builtin]28pub fn builtin_sum(arr: Vec<f64>) -> f64 {29 arr.iter().sum()30}3132#[builtin]33pub fn builtin_modulo(x: f64, y: f64) -> f64 {34 x % y35}3637#[builtin]38pub fn builtin_floor(x: f64) -> f64 {39 x.floor()40}4142#[builtin]43pub fn builtin_ceil(x: f64) -> f64 {44 x.ceil()45}4647#[builtin]48pub fn builtin_log(x: f64) -> f64 {49 x.ln()50}5152#[builtin]53pub fn builtin_pow(x: f64, n: f64) -> f64 {54 x.powf(n)55}5657#[builtin]58pub fn builtin_sqrt(x: PositiveF64) -> f64 {59 x.0.sqrt()60}6162#[builtin]63pub fn builtin_sin(x: f64) -> f64 {64 x.sin()65}6667#[builtin]68pub fn builtin_cos(x: f64) -> f64 {69 x.cos()70}7172#[builtin]73pub fn builtin_tan(x: f64) -> f64 {74 x.tan()75}7677#[builtin]78pub fn builtin_asin(x: f64) -> f64 {79 x.asin()80}8182#[builtin]83pub fn builtin_acos(x: f64) -> f64 {84 x.acos()85}8687#[builtin]88pub fn builtin_atan(x: f64) -> f64 {89 x.atan()90}9192#[builtin]93pub fn builtin_atan2(y: f64, x: f64) -> f64 {94 y.atan2(x)95}9697#[builtin]98pub fn builtin_exp(x: f64) -> f64 {99 x.exp()100}101102fn frexp(s: f64) -> (f64, i16) {103 if s == 0.0 {104 (s, 0)105 } else {106 let lg = s.abs().log2();107 let x = (lg - lg.floor() - 1.0).exp2();108 let exp = lg.floor() + 1.0;109 (s.signum() * x, exp as i16)110 }111}112113#[builtin]114pub fn builtin_mantissa(x: f64) -> f64 {115 frexp(x).0116}117118#[builtin]119pub fn builtin_exponent(x: f64) -> i16 {120 frexp(x).1121}122123#[builtin]124pub fn builtin_round(x: f64) -> f64 {125 x.round()126}127128#[builtin]129pub fn builtin_is_even(x: f64) -> bool {130 builtin_round(x) % 2.0 == 0.0131}132133#[builtin]134#[allow(clippy::float_cmp)]135pub fn builtin_is_odd(x: f64) -> bool {136 builtin_round(x) % 2.0 == 1.0137}138139#[builtin]140#[allow(clippy::float_cmp)]141pub fn builtin_is_integer(x: f64) -> bool {142 builtin_round(x) == x143}144145#[builtin]146#[allow(clippy::float_cmp)]147pub fn builtin_is_decimal(x: f64) -> bool {148 builtin_round(x) != x149}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.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -1,6 +1,5 @@
{
local std = self,
- local id = std.id,
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',
@@ -19,8 +18,6 @@
stripChars(str, chars)::
std.lstripChars(std.rstripChars(str, chars), chars),
-
- split(str, c):: std.splitLimit(str, c, -1),
mapWithIndex(func, arr)::
if !std.isFunction(func) then
@@ -55,11 +52,6 @@
else
error 'Assertion failed. ' + a + ' != ' + b,
- clamp(x, minVal, maxVal)::
- if x < minVal then minVal
- else if x > maxVal then maxVal
- else x,
-
manifestIni(ini)::
local body_lines(body) =
std.join([], [
@@ -79,98 +71,7 @@
for k in std.objectFields(ini.sections)
];
std.join('\n', main_body + std.flattenArrays(all_sections) + ['']),
-
- manifestToml(value):: std.manifestTomlEx(value, ' '),
-
- escapeStringPython(str)::
- std.escapeStringJson(str),
-
- escapeStringBash(str_)::
- local str = std.toString(str_);
- local trans(ch) =
- if ch == "'" then
- "'\"'\"'"
- else
- ch;
- "'%s'" % std.join('', [trans(ch) for ch in std.stringChars(str)]),
-
- escapeStringDollars(str_)::
- local str = std.toString(str_);
- local trans(ch) =
- if ch == '$' then
- '$$'
- else
- ch;
- std.foldl(function(a, b) a + trans(b), std.stringChars(str), ''),
-
- local xml_escapes = {
- '<': '<',
- '>': '>',
- '&': '&',
- '"': '"',
- "'": ''',
- },
-
- escapeStringXML(str_)::
- local str = std.toString(str_);
- std.join('', [std.get(xml_escapes, ch, ch) for ch in std.stringChars(str)]),
-
- manifestJson(value):: std.manifestJsonEx(value, ' ') tailstrict,
-
- manifestJsonMinified(value):: std.manifestJsonEx(value, '', '', ':'),
-
- manifestYamlStream(value, indent_array_in_object=false, c_document_end=true, quote_keys=true)::
- if !std.isArray(value) then
- error 'manifestYamlStream only takes arrays, got ' + std.type(value)
- else
- '---\n' + std.join(
- '\n---\n', [std.manifestYamlDoc(e, indent_array_in_object, quote_keys) for e in value]
- ) + if c_document_end then '\n...\n' else '\n',
-
- manifestPython(v)::
- if std.isObject(v) then
- local fields = [
- '%s: %s' % [std.escapeStringPython(k), std.manifestPython(v[k])]
- for k in std.objectFields(v)
- ];
- '{%s}' % [std.join(', ', fields)]
- else if std.isArray(v) then
- '[%s]' % [std.join(', ', [std.manifestPython(v2) for v2 in v])]
- else if std.isString(v) then
- '%s' % [std.escapeStringPython(v)]
- else if std.isFunction(v) then
- error 'cannot manifest function'
- else if std.isNumber(v) then
- std.toString(v)
- else if v == true then
- 'True'
- else if v == false then
- 'False'
- else if v == null then
- 'None',
- manifestPythonVars(conf)::
- local vars = ['%s = %s' % [k, std.manifestPython(conf[k])] for k in std.objectFields(conf)];
- std.join('\n', vars + ['']),
-
- manifestXmlJsonml(value)::
- if !std.isArray(value) then
- error 'Expected a JSONML value (an array), got %s' % std.type(value)
- else
- local aux(v) =
- if std.isString(v) then
- v
- else
- local tag = v[0];
- local has_attrs = std.length(v) > 1 && std.isObject(v[1]);
- local attrs = if has_attrs then v[1] else {};
- local children = if has_attrs then v[2:] else v[1:];
- local attrs_str =
- std.join('', [' %s="%s"' % [k, attrs[k]] for k in std.objectFields(attrs)]);
- std.deepJoin(['<', tag, attrs_str, '>', [aux(x) for x in children], '</', tag, '>']);
-
- aux(value),
-
mergePatch(target, patch)::
if std.isObject(patch) then
local target_object =
@@ -194,9 +95,6 @@
}
else
patch,
-
- get(o, f, default=null, inc_hidden=true)::
- if std.objectHasEx(o, f, inc_hidden) then o[f] else default,
resolvePath(f, r)::
local arr = std.split(f, '/');
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()
}