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.rsdiffbeforeafterboth1use std::string::String;23use proc_macro2::TokenStream;4use quote::quote;5use syn::{6 parenthesized,7 parse::{Parse, ParseStream},8 parse_macro_input,9 punctuated::Punctuated,10 spanned::Spanned,11 token::{self, Comma},12 Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,13 PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18 Ident: PartialEq<I>,19{20 let attrs = attrs21 .iter()22 .filter(|a| a.path().is_ident(&ident))23 .collect::<Vec<_>>();24 if attrs.len() > 1 {25 return Err(Error::new(26 attrs[1].span(),27 "this attribute may be specified only once",28 ));29 } else if attrs.is_empty() {30 return Ok(None);31 }32 let attr = attrs[0];33 let attr = attr.parse_args::<A>()?;3435 Ok(Some(attr))36}3738fn path_is(path: &Path, needed: &str) -> bool {39 path.leading_colon.is_none()40 && !path.segments.is_empty()41 && path.segments.iter().last().unwrap().ident == needed42}4344fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {45 match ty {46 Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {47 let args = &path.path.segments.iter().last().unwrap().arguments;48 Some(args)49 }50 _ => None,51 }52}5354fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {55 let Some(args) = type_is_path(ty, "Option") else {56 return Ok(None);57 };58 // It should have only on angle-bracketed param ("<String>"):59 let PathArguments::AngleBracketed(params) = args else {60 return Err(Error::new(args.span(), "missing option generic"));61 };62 let generic_arg = params.args.iter().next().unwrap();63 // This argument must be a type:64 let GenericArgument::Type(ty) = generic_arg else {65 return Err(Error::new(66 generic_arg.span(),67 "option generic should be a type",68 ));69 };70 Ok(Some(ty))71}7273struct Field {74 attrs: Vec<Attribute>,75 name: Ident,76 _colon: Token![:],77 ty: Type,78}79impl Parse for Field {80 fn parse(input: ParseStream) -> syn::Result<Self> {81 Ok(Self {82 attrs: input.call(Attribute::parse_outer)?,83 name: input.parse()?,84 _colon: input.parse()?,85 ty: input.parse()?,86 })87 }88}8990mod kw {91 syn::custom_keyword!(fields);92 syn::custom_keyword!(rename);93 syn::custom_keyword!(flatten);94 syn::custom_keyword!(add);95 syn::custom_keyword!(hide);96 syn::custom_keyword!(ok);97}9899struct EmptyAttr;100impl Parse for EmptyAttr {101 fn parse(_input: ParseStream) -> Result<Self> {102 Ok(Self)103 }104}105106struct BuiltinAttrs {107 fields: Vec<Field>,108}109impl Parse for BuiltinAttrs {110 fn parse(input: ParseStream) -> syn::Result<Self> {111 if input.is_empty() {112 return Ok(Self { fields: Vec::new() });113 }114 input.parse::<kw::fields>()?;115 let fields;116 parenthesized!(fields in input);117 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;118 Ok(Self {119 fields: p.into_iter().collect(),120 })121 }122}123124enum ArgInfo {125 Normal {126 ty: Box<Type>,127 is_option: bool,128 name: Option<String>,129 cfg_attrs: Vec<Attribute>,130 },131 Lazy {132 is_option: bool,133 name: Option<String>,134 },135 Context,136 Location,137 This,138}139140impl ArgInfo {141 fn parse(name: &str, arg: &FnArg) -> Result<Self> {142 let FnArg::Typed(arg) = arg else {143 unreachable!()144 };145 let ident = match &arg.pat as &Pat {146 Pat::Ident(i) => Some(i.ident.clone()),147 _ => None,148 };149 let ty = &arg.ty;150 if type_is_path(ty, "Context").is_some() {151 return Ok(Self::Context);152 } else if type_is_path(ty, "CallLocation").is_some() {153 return Ok(Self::Location);154 } else if type_is_path(ty, "Thunk").is_some() {155 return Ok(Self::Lazy {156 is_option: false,157 name: ident.map(|v| v.to_string()),158 });159 }160161 match ty as &Type {162 Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),163 _ => {}164 }165166 let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {167 if type_is_path(ty, "Thunk").is_some() {168 return Ok(Self::Lazy {169 is_option: true,170 name: ident.map(|v| v.to_string()),171 });172 }173174 (true, Box::new(ty.clone()))175 } else {176 (false, ty.clone())177 };178179 let cfg_attrs = arg180 .attrs181 .iter()182 .filter(|a| a.path().is_ident("cfg"))183 .cloned()184 .collect();185186 Ok(Self::Normal {187 ty,188 is_option,189 name: ident.map(|v| v.to_string()),190 cfg_attrs,191 })192 }193}194195#[proc_macro_attribute]196pub fn builtin(197 attr: proc_macro::TokenStream,198 item: proc_macro::TokenStream,199) -> proc_macro::TokenStream {200 let attr = parse_macro_input!(attr as BuiltinAttrs);201 let item_fn = item.clone();202 let item_fn: ItemFn = parse_macro_input!(item_fn);203204 match builtin_inner(attr, item_fn, item.into()) {205 Ok(v) => v.into(),206 Err(e) => e.into_compile_error().into(),207 }208}209210#[allow(clippy::too_many_lines)]211fn builtin_inner(212 attr: BuiltinAttrs,213 fun: ItemFn,214 item: proc_macro2::TokenStream,215) -> syn::Result<TokenStream> {216 let ReturnType::Type(_, result) = &fun.sig.output else {217 return Err(Error::new(218 fun.sig.span(),219 "builtin should return something",220 ));221 };222223 let name = fun.sig.ident.to_string();224 let args = fun225 .sig226 .inputs227 .iter()228 .map(|arg| ArgInfo::parse(&name, arg))229 .collect::<Result<Vec<_>>>()?;230231 let params_desc = args.iter().filter_map(|a| match a {232 ArgInfo::Normal {233 is_option,234 name,235 cfg_attrs,236 ..237 } => {238 let name = name239 .as_ref()240 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});241 Some(quote! {242 #(#cfg_attrs)*243 BuiltinParam::new(#name, #is_option),244 })245 }246 ArgInfo::Lazy { is_option, name } => {247 let name = name248 .as_ref()249 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});250 Some(quote! {251 BuiltinParam::new(#name, #is_option),252 })253 }254 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,255 });256257 let mut id = 0usize;258 let pass = args259 .iter()260 .map(|a| match a {261 ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {262 let cid = id;263 id += 1;264 (quote! {#cid}, a)265 }266 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {267 (quote! {compile_error!("should not use id")}, a)268 }269 })270 .map(|(id, a)| match a {271 ArgInfo::Normal {272 ty,273 is_option,274 name,275 cfg_attrs,276 } => {277 let name = name.as_ref().map_or("<unnamed>", String::as_str);278 let eval = quote! {jrsonnet_evaluator::State::push_description(279 || format!("argument <{}> evaluation", #name),280 || <#ty>::from_untyped(value.evaluate()?),281 )?};282 let value = if *is_option {283 quote! {if let Some(value) = &parsed[#id] {284 Some(#eval)285 } else {286 None287 },}288 } else {289 quote! {{290 let value = parsed[#id].as_ref().expect("args shape is checked");291 #eval292 },}293 };294 quote! {295 #(#cfg_attrs)*296 #value297 }298 }299 ArgInfo::Lazy { is_option, .. } => {300 if *is_option {301 quote! {if let Some(value) = &parsed[#id] {302 Some(value.clone())303 } else {304 None305 }}306 } else {307 quote! {308 parsed[#id].as_ref().expect("args shape is correct").clone(),309 }310 }311 }312 ArgInfo::Context => quote! {ctx.clone(),},313 ArgInfo::Location => quote! {location,},314 ArgInfo::This => quote! {self,},315 });316317 let fields = attr.fields.iter().map(|field| {318 let attrs = &field.attrs;319 let name = &field.name;320 let ty = &field.ty;321 quote! {322 #(#attrs)*323 pub #name: #ty,324 }325 });326327 let name = &fun.sig.ident;328 let vis = &fun.vis;329 let static_ext = if attr.fields.is_empty() {330 quote! {331 impl #name {332 pub const INST: &'static dyn StaticBuiltin = &#name {};333 }334 impl StaticBuiltin for #name {}335 }336 } else {337 quote! {}338 };339 let static_derive_copy = if attr.fields.is_empty() {340 quote! {, Copy}341 } else {342 quote! {}343 };344345 Ok(quote! {346 #item347348 #[doc(hidden)]349 #[allow(non_camel_case_types)]350 #[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]351 #vis struct #name {352 #(#fields)*353 }354 const _: () = {355 use ::jrsonnet_evaluator::{356 State, Val,357 function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},358 Result, Context, typed::Typed,359 parser::ExprLocation,360 };361 const PARAMS: &'static [BuiltinParam] = &[362 #(#params_desc)*363 ];364365 #static_ext366 impl Builtin for #name367 where368 Self: 'static369 {370 fn name(&self) -> &str {371 stringify!(#name)372 }373 fn params(&self) -> &[BuiltinParam] {374 PARAMS375 }376 #[allow(unused_variable)]377 fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {378 let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;379380 let result: #result = #name(#(#pass)*);381 <_ as Typed>::into_result(result)382 }383 fn as_any(&self) -> &dyn ::std::any::Any {384 self385 }386 }387 };388 })389}390391#[derive(Default)]392#[allow(clippy::struct_excessive_bools)]393struct TypedAttr {394 rename: Option<String>,395 flatten: bool,396 /// flatten(ok) strategy for flattened optionals397 /// field would be None in case of any parsing error (as in serde)398 flatten_ok: bool,399 // Should it be `field+:` instead of `field:`400 add: bool,401 // Should it be `field::` instead of `field:`402 hide: bool,403}404impl Parse for TypedAttr {405 fn parse(input: ParseStream) -> syn::Result<Self> {406 let mut out = Self::default();407 loop {408 let lookahead = input.lookahead1();409 if lookahead.peek(kw::rename) {410 input.parse::<kw::rename>()?;411 input.parse::<Token![=]>()?;412 let name = input.parse::<LitStr>()?;413 if out.rename.is_some() {414 return Err(Error::new(415 name.span(),416 "rename attribute may only be specified once",417 ));418 }419 out.rename = Some(name.value());420 } else if lookahead.peek(kw::flatten) {421 input.parse::<kw::flatten>()?;422 out.flatten = true;423 if input.peek(token::Paren) {424 let content;425 parenthesized!(content in input);426 let lookahead = content.lookahead1();427 if lookahead.peek(kw::ok) {428 content.parse::<kw::ok>()?;429 out.flatten_ok = true;430 } else {431 return Err(lookahead.error());432 }433 }434 } else if lookahead.peek(kw::add) {435 input.parse::<kw::add>()?;436 out.add = true;437 } else if lookahead.peek(kw::hide) {438 input.parse::<kw::hide>()?;439 out.hide = true;440 } else if input.is_empty() {441 break;442 } else {443 return Err(lookahead.error());444 }445 if input.peek(Token![,]) {446 input.parse::<Token![,]>()?;447 } else {448 break;449 }450 }451 Ok(out)452 }453}454455struct TypedField {456 attr: TypedAttr,457 ident: Ident,458 ty: Type,459 is_option: bool,460}461impl TypedField {462 fn parse(field: &syn::Field) -> Result<Self> {463 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();464 let Some(ident) = field.ident.clone() else {465 return Err(Error::new(466 field.span(),467 "this field should appear in output object, but it has no visible name",468 ));469 };470 let (is_option, ty) = extract_type_from_option(&field.ty)?471 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));472 if is_option && attr.flatten {473 if !attr.flatten_ok {474 return Err(Error::new(475 field.span(),476 "strategy should be set when flattening Option",477 ));478 }479 } else if attr.flatten_ok {480 return Err(Error::new(481 field.span(),482 "flatten(ok) is only useable on optional fields",483 ));484 }485486 Ok(Self {487 attr,488 ident,489 ty,490 is_option,491 })492 }493 /// None if this field is flattened in jsonnet output494 fn name(&self) -> Option<String> {495 if self.attr.flatten {496 return None;497 }498 Some(499 self.attr500 .rename501 .clone()502 .unwrap_or_else(|| self.ident.to_string()),503 )504 }505506 fn expand_field(&self) -> Option<TokenStream> {507 if self.is_option {508 return None;509 }510 let name = self.name()?;511 let ty = &self.ty;512 Some(quote! {513 (#name, <#ty as Typed>::TYPE)514 })515 }516 fn expand_parse(&self) -> TokenStream {517 let ident = &self.ident;518 let ty = &self.ty;519 if self.attr.flatten {520 // optional flatten is handled in same way as serde521 return if self.is_option {522 quote! {523 #ident: <#ty as TypedObj>::parse(&obj).ok(),524 }525 } else {526 quote! {527 #ident: <#ty as TypedObj>::parse(&obj)?,528 }529 };530 };531532 let name = self.name().unwrap();533 let value = if self.is_option {534 quote! {535 if let Some(value) = obj.get(#name.into())? {536 Some(<#ty as Typed>::from_untyped(value)?)537 } else {538 None539 }540 }541 } else {542 quote! {543 <#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?544 }545 };546547 quote! {548 #ident: #value,549 }550 }551 fn expand_serialize(&self) -> TokenStream {552 let ident = &self.ident;553 let ty = &self.ty;554 self.name().map_or_else(555 || {556 if self.is_option {557 quote! {558 if let Some(value) = self.#ident {559 <#ty as TypedObj>::serialize(value, out)?;560 }561 }562 } else {563 quote! {564 <#ty as TypedObj>::serialize(self.#ident, out)?;565 }566 }567 },568 |name| {569 let hide = if self.attr.hide {570 quote! {.hide()}571 } else {572 quote! {}573 };574 let add = if self.attr.add {575 quote! {.add()}576 } else {577 quote! {}578 };579 if self.is_option {580 quote! {581 if let Some(value) = self.#ident {582 out.field(#name)583 #hide584 #add585 .try_value(<#ty as Typed>::into_untyped(value)?)?;586 }587 }588 } else {589 quote! {590 out.field(#name)591 #hide592 #add593 .try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;594 }595 }596 },597 )598 }599}600601#[proc_macro_derive(Typed, attributes(typed))]602pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {603 let input = parse_macro_input!(item as DeriveInput);604605 match derive_typed_inner(input) {606 Ok(v) => v.into(),607 Err(e) => e.to_compile_error().into(),608 }609}610611fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {612 let syn::Data::Struct(data) = &input.data else {613 return Err(Error::new(input.span(), "only structs supported"));614 };615616 let ident = &input.ident;617 let fields = data618 .fields619 .iter()620 .map(TypedField::parse)621 .collect::<Result<Vec<_>>>()?;622623 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();624625 let typed = {626 let fields = fields627 .iter()628 .filter_map(TypedField::expand_field)629 .collect::<Vec<_>>();630 quote! {631 impl #impl_generics Typed for #ident #ty_generics #where_clause {632 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[633 #(#fields,)*634 ]);635636 fn from_untyped(value: Val) -> JrResult<Self> {637 let obj = value.as_obj().expect("shape is correct");638 Self::parse(&obj)639 }640641 fn into_untyped(value: Self) -> JrResult<Val> {642 let mut out = ObjValueBuilder::new();643 value.serialize(&mut out)?;644 Ok(Val::Obj(out.build()))645 }646647 }648 }649 };650651 let fields_parse = fields.iter().map(TypedField::expand_parse);652 let fields_serialize = fields653 .iter()654 .map(TypedField::expand_serialize)655 .collect::<Vec<_>>();656657 Ok(quote! {658 const _: () = {659 use ::jrsonnet_evaluator::{660 typed::{ComplexValType, Typed, TypedObj, CheckType},661 Val, State,662 error::{ErrorKind, Result as JrResult},663 ObjValueBuilder, ObjValue,664 };665666 #typed667668 impl #impl_generics TypedObj for #ident #ty_generics #where_clause {669 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {670 #(#fields_serialize)*671672 Ok(())673 }674 fn parse(obj: &ObjValue) -> JrResult<Self> {675 Ok(Self {676 #(#fields_parse)*677 })678 }679 }680 };681 })682}683684struct FormatInput {685 formatting: LitStr,686 arguments: Vec<Expr>,687}688impl Parse for FormatInput {689 fn parse(input: ParseStream) -> Result<Self> {690 let formatting = input.parse()?;691 let mut arguments = Vec::new();692693 while input.peek(Token![,]) {694 input.parse::<Token![,]>()?;695 if input.is_empty() {696 // Trailing comma697 break;698 }699 let expr = input.parse()?;700 arguments.push(expr);701 }702703 if !input.is_empty() {704 return Err(syn::Error::new(input.span(), "unexpected trailing input"));705 }706707 Ok(Self {708 formatting,709 arguments,710 })711 }712}713fn is_format_str(i: &str) -> bool {714 let mut is_plain = true;715 // -1 = {716 // +1 = }717 let mut is_bracket = 0i8;718 for ele in i.chars() {719 match ele {720 '{' if is_bracket == -1 => {721 is_bracket = 0;722 }723 '}' if is_bracket == -1 => {724 is_plain = false;725 break;726 }727 '}' if is_bracket == 1 => {728 is_bracket = 0;729 }730 '{' if is_bracket == 1 => {731 is_plain = false;732 break;733 }734 '{' => {735 is_bracket = -1;736 }737 '}' => {738 is_bracket = 1;739 }740 _ if is_bracket != 0 => {741 is_plain = false;742 break;743 }744 _ => {}745 }746 }747 !is_plain || is_bracket != 0748}749impl FormatInput {750 fn expand(self) -> TokenStream {751 let format = self.formatting;752 if is_format_str(&format.value()) {753 let args = self.arguments;754 quote! {755 ::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))756 }757 } else {758 if let Some(first) = self.arguments.first() {759 return syn::Error::new(760 first.span(),761 "string has no formatting codes, it should not have the arguments",762 )763 .into_compile_error();764 }765 quote! {766 ::jrsonnet_evaluator::IStr::from(#format)767 }768 }769 }770}771772/// `IStr` formatting helper773///774/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`775/// This macro looks for formatting codes in the input string, and uses776/// `format!()` only when necessary777#[proc_macro]778pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {779 let input = parse_macro_input!(input as FormatInput);780 input.expand().into()781}1use std::string::String;23use proc_macro2::TokenStream;4use quote::quote;5use syn::{6 parenthesized,7 parse::{Parse, ParseStream},8 parse_macro_input,9 punctuated::Punctuated,10 spanned::Spanned,11 token::{self, Comma},12 Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,13 PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18 Ident: PartialEq<I>,19{20 let attrs = attrs21 .iter()22 .filter(|a| a.path().is_ident(&ident))23 .collect::<Vec<_>>();24 if attrs.len() > 1 {25 return Err(Error::new(26 attrs[1].span(),27 "this attribute may be specified only once",28 ));29 } else if attrs.is_empty() {30 return Ok(None);31 }32 let attr = attrs[0];33 let attr = attr.parse_args::<A>()?;3435 Ok(Some(attr))36}37fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)38where39 Ident: PartialEq<I>,40{41 attrs.retain(|a| !a.path().is_ident(&ident));42}4344fn path_is(path: &Path, needed: &str) -> bool {45 path.leading_colon.is_none()46 && !path.segments.is_empty()47 && path.segments.iter().last().unwrap().ident == needed48}4950fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {51 match ty {52 Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {53 let args = &path.path.segments.iter().last().unwrap().arguments;54 Some(args)55 }56 _ => None,57 }58}5960fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {61 let Some(args) = type_is_path(ty, "Option") else {62 return Ok(None);63 };64 // It should have only on angle-bracketed param ("<String>"):65 let PathArguments::AngleBracketed(params) = args else {66 return Err(Error::new(args.span(), "missing option generic"));67 };68 let generic_arg = params.args.iter().next().unwrap();69 // This argument must be a type:70 let GenericArgument::Type(ty) = generic_arg else {71 return Err(Error::new(72 generic_arg.span(),73 "option generic should be a type",74 ));75 };76 Ok(Some(ty))77}7879struct Field {80 attrs: Vec<Attribute>,81 name: Ident,82 _colon: Token![:],83 ty: Type,84}85impl Parse for Field {86 fn parse(input: ParseStream) -> syn::Result<Self> {87 Ok(Self {88 attrs: input.call(Attribute::parse_outer)?,89 name: input.parse()?,90 _colon: input.parse()?,91 ty: input.parse()?,92 })93 }94}9596mod kw {97 syn::custom_keyword!(fields);98 syn::custom_keyword!(rename);99 syn::custom_keyword!(flatten);100 syn::custom_keyword!(add);101 syn::custom_keyword!(hide);102 syn::custom_keyword!(ok);103}104105struct EmptyAttr;106impl Parse for EmptyAttr {107 fn parse(_input: ParseStream) -> Result<Self> {108 Ok(Self)109 }110}111112struct BuiltinAttrs {113 fields: Vec<Field>,114}115impl Parse for BuiltinAttrs {116 fn parse(input: ParseStream) -> syn::Result<Self> {117 if input.is_empty() {118 return Ok(Self { fields: Vec::new() });119 }120 input.parse::<kw::fields>()?;121 let fields;122 parenthesized!(fields in input);123 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;124 Ok(Self {125 fields: p.into_iter().collect(),126 })127 }128}129130enum Optionality {131 Required,132 Optional,133 Default(Expr),134}135impl Optionality {136 fn is_optional(&self) -> bool {137 !matches!(self, Self::Required)138 }139}140141enum ArgInfo {142 Normal {143 ty: Box<Type>,144 optionality: Optionality,145 name: Option<String>,146 cfg_attrs: Vec<Attribute>,147 },148 Lazy {149 is_option: bool,150 name: Option<String>,151 },152 Context,153 Location,154 This,155}156157impl ArgInfo {158 fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {159 let FnArg::Typed(arg) = arg else {160 unreachable!()161 };162 let ident = match &arg.pat as &Pat {163 Pat::Ident(i) => Some(i.ident.clone()),164 _ => None,165 };166 let ty = &arg.ty;167 if type_is_path(ty, "Context").is_some() {168 return Ok(Self::Context);169 } else if type_is_path(ty, "CallLocation").is_some() {170 return Ok(Self::Location);171 } else if type_is_path(ty, "Thunk").is_some() {172 return Ok(Self::Lazy {173 is_option: false,174 name: ident.map(|v| v.to_string()),175 });176 }177178 match ty as &Type {179 Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),180 _ => {}181 }182183 let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {184 remove_attr(&mut arg.attrs, "default");185 (Optionality::Default(default), ty.clone())186 } else if let Some(ty) = extract_type_from_option(ty)? {187 if type_is_path(ty, "Thunk").is_some() {188 return Ok(Self::Lazy {189 is_option: true,190 name: ident.map(|v| v.to_string()),191 });192 }193194 (Optionality::Optional, Box::new(ty.clone()))195 } else {196 (Optionality::Required, ty.clone())197 };198199 let cfg_attrs = arg200 .attrs201 .iter()202 .filter(|a| a.path().is_ident("cfg"))203 .cloned()204 .collect();205206 Ok(Self::Normal {207 ty,208 optionality,209 name: ident.map(|v| v.to_string()),210 cfg_attrs,211 })212 }213}214215#[proc_macro_attribute]216pub fn builtin(217 attr: proc_macro::TokenStream,218 item: proc_macro::TokenStream,219) -> proc_macro::TokenStream {220 let attr = parse_macro_input!(attr as BuiltinAttrs);221 let item_fn = item.clone();222 let item_fn: ItemFn = parse_macro_input!(item_fn);223224 match builtin_inner(attr, item_fn) {225 Ok(v) => v.into(),226 Err(e) => e.into_compile_error().into(),227 }228}229230#[allow(clippy::too_many_lines)]231fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {232 let ReturnType::Type(_, result) = &fun.sig.output else {233 return Err(Error::new(234 fun.sig.span(),235 "builtin should return something",236 ));237 };238239 let name = fun.sig.ident.to_string();240 let args = fun241 .sig242 .inputs243 .iter_mut()244 .map(|arg| ArgInfo::parse(&name, arg))245 .collect::<Result<Vec<_>>>()?;246247 let params_desc = args.iter().filter_map(|a| match a {248 ArgInfo::Normal {249 optionality,250 name,251 cfg_attrs,252 ..253 } => {254 let name = name255 .as_ref()256 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});257 let is_optional = optionality.is_optional();258 Some(quote! {259 #(#cfg_attrs)*260 BuiltinParam::new(#name, #is_optional),261 })262 }263 ArgInfo::Lazy { is_option, name } => {264 let name = name265 .as_ref()266 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});267 Some(quote! {268 BuiltinParam::new(#name, #is_option),269 })270 }271 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,272 });273274 let mut id = 0usize;275 let pass = args276 .iter()277 .map(|a| match a {278 ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {279 let cid = id;280 id += 1;281 (quote! {#cid}, a)282 }283 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {284 (quote! {compile_error!("should not use id")}, a)285 }286 })287 .map(|(id, a)| match a {288 ArgInfo::Normal {289 ty,290 optionality,291 name,292 cfg_attrs,293 } => {294 let name = name.as_ref().map_or("<unnamed>", String::as_str);295 let eval = quote! {jrsonnet_evaluator::State::push_description(296 || format!("argument <{}> evaluation", #name),297 || <#ty>::from_untyped(value.evaluate()?),298 )?};299 let value = match optionality {300 Optionality::Required => quote! {{301 let value = parsed[#id].as_ref().expect("args shape is checked");302 #eval303 },},304 Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {305 Some(#eval)306 } else {307 None308 },},309 Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {310 #eval311 } else {312 let v: #ty = #expr;313 v314 },},315 };316 quote! {317 #(#cfg_attrs)*318 #value319 }320 }321 ArgInfo::Lazy { is_option, .. } => {322 if *is_option {323 quote! {if let Some(value) = &parsed[#id] {324 Some(value.clone())325 } else {326 None327 },}328 } else {329 quote! {330 parsed[#id].as_ref().expect("args shape is correct").clone(),331 }332 }333 }334 ArgInfo::Context => quote! {ctx.clone(),},335 ArgInfo::Location => quote! {location,},336 ArgInfo::This => quote! {self,},337 });338339 let fields = attr.fields.iter().map(|field| {340 let attrs = &field.attrs;341 let name = &field.name;342 let ty = &field.ty;343 quote! {344 #(#attrs)*345 pub #name: #ty,346 }347 });348349 let name = &fun.sig.ident;350 let vis = &fun.vis;351 let static_ext = if attr.fields.is_empty() {352 quote! {353 impl #name {354 pub const INST: &'static dyn StaticBuiltin = &#name {};355 }356 impl StaticBuiltin for #name {}357 }358 } else {359 quote! {}360 };361 let static_derive_copy = if attr.fields.is_empty() {362 quote! {, Copy}363 } else {364 quote! {}365 };366367 Ok(quote! {368 #fun369370 #[doc(hidden)]371 #[allow(non_camel_case_types)]372 #[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]373 #vis struct #name {374 #(#fields)*375 }376 const _: () = {377 use ::jrsonnet_evaluator::{378 State, Val,379 function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},380 Result, Context, typed::Typed,381 parser::ExprLocation,382 };383 const PARAMS: &'static [BuiltinParam] = &[384 #(#params_desc)*385 ];386387 #static_ext388 impl Builtin for #name389 where390 Self: 'static391 {392 fn name(&self) -> &str {393 stringify!(#name)394 }395 fn params(&self) -> &[BuiltinParam] {396 PARAMS397 }398 #[allow(unused_variables)]399 fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {400 let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;401402 let result: #result = #name(#(#pass)*);403 <_ as Typed>::into_result(result)404 }405 fn as_any(&self) -> &dyn ::std::any::Any {406 self407 }408 }409 };410 })411}412413#[derive(Default)]414#[allow(clippy::struct_excessive_bools)]415struct TypedAttr {416 rename: Option<String>,417 flatten: bool,418 /// flatten(ok) strategy for flattened optionals419 /// field would be None in case of any parsing error (as in serde)420 flatten_ok: bool,421 // Should it be `field+:` instead of `field:`422 add: bool,423 // Should it be `field::` instead of `field:`424 hide: bool,425}426impl Parse for TypedAttr {427 fn parse(input: ParseStream) -> syn::Result<Self> {428 let mut out = Self::default();429 loop {430 let lookahead = input.lookahead1();431 if lookahead.peek(kw::rename) {432 input.parse::<kw::rename>()?;433 input.parse::<Token![=]>()?;434 let name = input.parse::<LitStr>()?;435 if out.rename.is_some() {436 return Err(Error::new(437 name.span(),438 "rename attribute may only be specified once",439 ));440 }441 out.rename = Some(name.value());442 } else if lookahead.peek(kw::flatten) {443 input.parse::<kw::flatten>()?;444 out.flatten = true;445 if input.peek(token::Paren) {446 let content;447 parenthesized!(content in input);448 let lookahead = content.lookahead1();449 if lookahead.peek(kw::ok) {450 content.parse::<kw::ok>()?;451 out.flatten_ok = true;452 } else {453 return Err(lookahead.error());454 }455 }456 } else if lookahead.peek(kw::add) {457 input.parse::<kw::add>()?;458 out.add = true;459 } else if lookahead.peek(kw::hide) {460 input.parse::<kw::hide>()?;461 out.hide = true;462 } else if input.is_empty() {463 break;464 } else {465 return Err(lookahead.error());466 }467 if input.peek(Token![,]) {468 input.parse::<Token![,]>()?;469 } else {470 break;471 }472 }473 Ok(out)474 }475}476477struct TypedField {478 attr: TypedAttr,479 ident: Ident,480 ty: Type,481 is_option: bool,482}483impl TypedField {484 fn parse(field: &syn::Field) -> Result<Self> {485 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();486 let Some(ident) = field.ident.clone() else {487 return Err(Error::new(488 field.span(),489 "this field should appear in output object, but it has no visible name",490 ));491 };492 let (is_option, ty) = extract_type_from_option(&field.ty)?493 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));494 if is_option && attr.flatten {495 if !attr.flatten_ok {496 return Err(Error::new(497 field.span(),498 "strategy should be set when flattening Option",499 ));500 }501 } else if attr.flatten_ok {502 return Err(Error::new(503 field.span(),504 "flatten(ok) is only useable on optional fields",505 ));506 }507508 Ok(Self {509 attr,510 ident,511 ty,512 is_option,513 })514 }515 /// None if this field is flattened in jsonnet output516 fn name(&self) -> Option<String> {517 if self.attr.flatten {518 return None;519 }520 Some(521 self.attr522 .rename523 .clone()524 .unwrap_or_else(|| self.ident.to_string()),525 )526 }527528 fn expand_field(&self) -> Option<TokenStream> {529 if self.is_option {530 return None;531 }532 let name = self.name()?;533 let ty = &self.ty;534 Some(quote! {535 (#name, <#ty as Typed>::TYPE)536 })537 }538 fn expand_parse(&self) -> TokenStream {539 let ident = &self.ident;540 let ty = &self.ty;541 if self.attr.flatten {542 // optional flatten is handled in same way as serde543 return if self.is_option {544 quote! {545 #ident: <#ty as TypedObj>::parse(&obj).ok(),546 }547 } else {548 quote! {549 #ident: <#ty as TypedObj>::parse(&obj)?,550 }551 };552 };553554 let name = self.name().unwrap();555 let value = if self.is_option {556 quote! {557 if let Some(value) = obj.get(#name.into())? {558 Some(<#ty as Typed>::from_untyped(value)?)559 } else {560 None561 }562 }563 } else {564 quote! {565 <#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?566 }567 };568569 quote! {570 #ident: #value,571 }572 }573 fn expand_serialize(&self) -> TokenStream {574 let ident = &self.ident;575 let ty = &self.ty;576 self.name().map_or_else(577 || {578 if self.is_option {579 quote! {580 if let Some(value) = self.#ident {581 <#ty as TypedObj>::serialize(value, out)?;582 }583 }584 } else {585 quote! {586 <#ty as TypedObj>::serialize(self.#ident, out)?;587 }588 }589 },590 |name| {591 let hide = if self.attr.hide {592 quote! {.hide()}593 } else {594 quote! {}595 };596 let add = if self.attr.add {597 quote! {.add()}598 } else {599 quote! {}600 };601 if self.is_option {602 quote! {603 if let Some(value) = self.#ident {604 out.field(#name)605 #hide606 #add607 .try_value(<#ty as Typed>::into_untyped(value)?)?;608 }609 }610 } else {611 quote! {612 out.field(#name)613 #hide614 #add615 .try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;616 }617 }618 },619 )620 }621}622623#[proc_macro_derive(Typed, attributes(typed))]624pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {625 let input = parse_macro_input!(item as DeriveInput);626627 match derive_typed_inner(input) {628 Ok(v) => v.into(),629 Err(e) => e.to_compile_error().into(),630 }631}632633fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {634 let syn::Data::Struct(data) = &input.data else {635 return Err(Error::new(input.span(), "only structs supported"));636 };637638 let ident = &input.ident;639 let fields = data640 .fields641 .iter()642 .map(TypedField::parse)643 .collect::<Result<Vec<_>>>()?;644645 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();646647 let typed = {648 let fields = fields649 .iter()650 .filter_map(TypedField::expand_field)651 .collect::<Vec<_>>();652 quote! {653 impl #impl_generics Typed for #ident #ty_generics #where_clause {654 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[655 #(#fields,)*656 ]);657658 fn from_untyped(value: Val) -> JrResult<Self> {659 let obj = value.as_obj().expect("shape is correct");660 Self::parse(&obj)661 }662663 fn into_untyped(value: Self) -> JrResult<Val> {664 let mut out = ObjValueBuilder::new();665 value.serialize(&mut out)?;666 Ok(Val::Obj(out.build()))667 }668669 }670 }671 };672673 let fields_parse = fields.iter().map(TypedField::expand_parse);674 let fields_serialize = fields675 .iter()676 .map(TypedField::expand_serialize)677 .collect::<Vec<_>>();678679 Ok(quote! {680 const _: () = {681 use ::jrsonnet_evaluator::{682 typed::{ComplexValType, Typed, TypedObj, CheckType},683 Val, State,684 error::{ErrorKind, Result as JrResult},685 ObjValueBuilder, ObjValue,686 };687688 #typed689690 impl #impl_generics TypedObj for #ident #ty_generics #where_clause {691 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {692 #(#fields_serialize)*693694 Ok(())695 }696 fn parse(obj: &ObjValue) -> JrResult<Self> {697 Ok(Self {698 #(#fields_parse)*699 })700 }701 }702 };703 })704}705706struct FormatInput {707 formatting: LitStr,708 arguments: Vec<Expr>,709}710impl Parse for FormatInput {711 fn parse(input: ParseStream) -> Result<Self> {712 let formatting = input.parse()?;713 let mut arguments = Vec::new();714715 while input.peek(Token![,]) {716 input.parse::<Token![,]>()?;717 if input.is_empty() {718 // Trailing comma719 break;720 }721 let expr = input.parse()?;722 arguments.push(expr);723 }724725 if !input.is_empty() {726 return Err(syn::Error::new(input.span(), "unexpected trailing input"));727 }728729 Ok(Self {730 formatting,731 arguments,732 })733 }734}735fn is_format_str(i: &str) -> bool {736 let mut is_plain = true;737 // -1 = {738 // +1 = }739 let mut is_bracket = 0i8;740 for ele in i.chars() {741 match ele {742 '{' if is_bracket == -1 => {743 is_bracket = 0;744 }745 '}' if is_bracket == -1 => {746 is_plain = false;747 break;748 }749 '}' if is_bracket == 1 => {750 is_bracket = 0;751 }752 '{' if is_bracket == 1 => {753 is_plain = false;754 break;755 }756 '{' => {757 is_bracket = -1;758 }759 '}' => {760 is_bracket = 1;761 }762 _ if is_bracket != 0 => {763 is_plain = false;764 break;765 }766 _ => {}767 }768 }769 !is_plain || is_bracket != 0770}771impl FormatInput {772 fn expand(self) -> TokenStream {773 let format = self.formatting;774 if is_format_str(&format.value()) {775 let args = self.arguments;776 quote! {777 ::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))778 }779 } else {780 if let Some(first) = self.arguments.first() {781 return syn::Error::new(782 first.span(),783 "string has no formatting codes, it should not have the arguments",784 )785 .into_compile_error();786 }787 quote! {788 ::jrsonnet_evaluator::IStr::from(#format)789 }790 }791 }792}793794/// `IStr` formatting helper795///796/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`797/// This macro looks for formatting codes in the input string, and uses798/// `format!()` only when necessary799#[proc_macro]800pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {801 let input = parse_macro_input!(input as FormatInput);802 input.expand().into()803}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.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()
}