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.rsdiffbeforeafterboth1use std::{borrow::Cow, fmt::Write, ptr};23use crate::{bail, Result, ResultExt, State, Val};45pub trait ManifestFormat {6 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;7 fn manifest(&self, val: Val) -> Result<String> {8 let mut out = String::new();9 self.manifest_buf(val, &mut out)?;10 Ok(out)11 }12 /// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13 /// the meaning).14 ///15 /// Default implementation returns `true`16 fn file_trailing_newline(&self) -> bool {17 true18 }19}20impl<T> ManifestFormat for Box<T>21where22 T: ManifestFormat + ?Sized,23{24 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {25 let inner = &**self;26 inner.manifest_buf(val, buf)27 }28 fn file_trailing_newline(&self) -> bool {29 let inner = &**self;30 inner.file_trailing_newline()31 }32}33impl<T> ManifestFormat for &'_ T34where35 T: ManifestFormat + ?Sized,36{37 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38 let inner = &**self;39 inner.manifest_buf(val, buf)40 }41 fn file_trailing_newline(&self) -> bool {42 let inner = &**self;43 inner.file_trailing_newline()44 }45}4647#[derive(PartialEq, Eq, Clone, Copy)]48enum JsonFormatting {49 // Applied in manifestification50 Manifest,51 /// Used for std.manifestJson52 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest53 Std,54 /// No line breaks, used in `obj+''`55 ToString,56 /// Minified json57 Minify,58}5960pub struct JsonFormat<'s> {61 padding: Cow<'s, str>,62 mtype: JsonFormatting,63 newline: &'s str,64 key_val_sep: &'s str,65 #[cfg(feature = "exp-preserve-order")]66 preserve_order: bool,67 #[cfg(feature = "exp-bigint")]68 preserve_bigints: bool,69 debug_truncate_strings: Option<usize>,70}7172impl<'s> JsonFormat<'s> {73 // Minifying format74 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {75 Self {76 padding: Cow::Borrowed(""),77 mtype: JsonFormatting::Minify,78 newline: "\n",79 key_val_sep: ":",80 #[cfg(feature = "exp-preserve-order")]81 preserve_order,82 #[cfg(feature = "exp-bigint")]83 preserve_bigints: false,84 debug_truncate_strings: None,85 }86 }87 // Same format as std.toString88 pub fn std_to_string() -> Self {89 Self {90 padding: Cow::Borrowed(""),91 mtype: JsonFormatting::ToString,92 newline: "\n",93 key_val_sep: ": ",94 #[cfg(feature = "exp-preserve-order")]95 preserve_order: false,96 #[cfg(feature = "exp-bigint")]97 preserve_bigints: false,98 debug_truncate_strings: None,99 }100 }101 pub fn std_to_json(102 padding: String,103 newline: &'s str,104 key_val_sep: &'s str,105 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,106 ) -> Self {107 Self {108 padding: Cow::Owned(padding),109 mtype: JsonFormatting::Std,110 newline,111 key_val_sep,112 #[cfg(feature = "exp-preserve-order")]113 preserve_order,114 #[cfg(feature = "exp-bigint")]115 preserve_bigints: false,116 debug_truncate_strings: None,117 }118 }119 // Same format as CLI manifestification120 pub fn cli(121 padding: usize,122 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,123 ) -> Self {124 if padding == 0 {125 return Self::minify(126 #[cfg(feature = "exp-preserve-order")]127 preserve_order,128 );129 }130 Self {131 padding: Cow::Owned(" ".repeat(padding)),132 mtype: JsonFormatting::Manifest,133 newline: "\n",134 key_val_sep: ": ",135 #[cfg(feature = "exp-preserve-order")]136 preserve_order,137 #[cfg(feature = "exp-bigint")]138 preserve_bigints: false,139 debug_truncate_strings: None,140 }141 }142 // Same format as CLI manifestification143 pub fn debug() -> Self {144 Self {145 padding: Cow::Borrowed(" "),146 mtype: JsonFormatting::Manifest,147 newline: "\n",148 key_val_sep: ": ",149 #[cfg(feature = "exp-preserve-order")]150 preserve_order: true,151 #[cfg(feature = "exp-bigint")]152 preserve_bigints: true,153 debug_truncate_strings: Some(256),154 }155 }156}157impl Default for JsonFormat<'static> {158 fn default() -> Self {159 Self {160 padding: Cow::Borrowed(" "),161 mtype: JsonFormatting::Manifest,162 newline: "\n",163 key_val_sep: ": ",164 #[cfg(feature = "exp-preserve-order")]165 preserve_order: false,166 #[cfg(feature = "exp-bigint")]167 preserve_bigints: false,168 debug_truncate_strings: None,169 }170 }171}172173pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {174 let mut out = String::new();175 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;176 Ok(out)177}178179#[allow(clippy::too_many_lines)]180fn manifest_json_ex_buf(181 val: &Val,182 buf: &mut String,183 cur_padding: &mut String,184 options: &JsonFormat<'_>,185) -> Result<()> {186 let mtype = options.mtype;187 match val {188 Val::Bool(v) => {189 if *v {190 buf.push_str("true");191 } else {192 buf.push_str("false");193 }194 }195 Val::Null => buf.push_str("null"),196 Val::Str(s) => {197 let flat = s.clone().into_flat();198 if let Some(truncate) = options.debug_truncate_strings {199 if flat.len() > truncate {200 let (start, end) = flat.split_at(truncate / 2);201 let (_, end) = end.split_at(end.len() - truncate / 2);202 escape_string_json_buf(&format!("{start}..{end}"), buf);203 } else {204 escape_string_json_buf(&flat, buf);205 }206 } else {207 escape_string_json_buf(&flat, buf);208 }209 }210 Val::Num(n) => write!(buf, "{n}").unwrap(),211 #[cfg(feature = "exp-bigint")]212 Val::BigInt(n) => {213 if options.preserve_bigints {214 write!(buf, "{n}").unwrap();215 } else {216 write!(buf, "{:?}", n.to_string()).unwrap();217 }218 }219 Val::Arr(items) => {220 buf.push('[');221 if !items.is_empty() {222 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {223 buf.push_str(options.newline);224 }225226 let old_len = cur_padding.len();227 cur_padding.push_str(&options.padding);228 for (i, item) in items.iter().enumerate() {229 if i != 0 {230 buf.push(',');231 if mtype == JsonFormatting::ToString {232 buf.push(' ');233 } else if mtype != JsonFormatting::Minify {234 buf.push_str(options.newline);235 }236 }237 buf.push_str(cur_padding);238 manifest_json_ex_buf(&item?, buf, cur_padding, options)239 .with_description(|| format!("elem <{i}> manifestification"))?;240 }241 cur_padding.truncate(old_len);242243 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {244 buf.push_str(options.newline);245 buf.push_str(cur_padding);246 }247 } else if mtype == JsonFormatting::Std {248 buf.push_str(options.newline);249 buf.push_str(options.newline);250 buf.push_str(cur_padding);251 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {252 buf.push(' ');253 }254 buf.push(']');255 }256 Val::Obj(obj) => {257 obj.run_assertions()?;258 buf.push('{');259 let fields = obj.fields(260 #[cfg(feature = "exp-preserve-order")]261 options.preserve_order,262 );263 if !fields.is_empty() {264 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {265 buf.push_str(options.newline);266 }267268 let old_len = cur_padding.len();269 cur_padding.push_str(&options.padding);270 for (i, field) in fields.into_iter().enumerate() {271 if i != 0 {272 buf.push(',');273 if mtype == JsonFormatting::ToString {274 buf.push(' ');275 } else if mtype != JsonFormatting::Minify {276 buf.push_str(options.newline);277 }278 }279 buf.push_str(cur_padding);280 escape_string_json_buf(&field, buf);281 buf.push_str(options.key_val_sep);282 State::push_description(283 || format!("field <{}> manifestification", field.clone()),284 || {285 let value = obj.get(field.clone())?.unwrap();286 manifest_json_ex_buf(&value, buf, cur_padding, options)?;287 Ok(())288 },289 )?;290 }291 cur_padding.truncate(old_len);292293 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {294 buf.push_str(options.newline);295 buf.push_str(cur_padding);296 }297 } else if mtype == JsonFormatting::Std {298 buf.push_str(options.newline);299 buf.push_str(options.newline);300 buf.push_str(cur_padding);301 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {302 buf.push(' ');303 }304 buf.push('}');305 }306 Val::Func(_) => bail!("tried to manifest function"),307 };308 Ok(())309}310311impl ManifestFormat for JsonFormat<'_> {312 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {313 manifest_json_ex_buf(&val, buf, &mut String::new(), self)314 }315}316317pub struct ToStringFormat;318impl ManifestFormat for ToStringFormat {319 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {320 JsonFormat::std_to_string().manifest_buf(val, out)321 }322 fn file_trailing_newline(&self) -> bool {323 false324 }325}326pub struct StringFormat;327impl ManifestFormat for StringFormat {328 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {329 let Val::Str(s) = val else {330 bail!(331 "output should be string for string manifest format, got {}",332 val.value_type()333 )334 };335 write!(out, "{s}").unwrap();336 Ok(())337 }338 fn file_trailing_newline(&self) -> bool {339 false340 }341}342343pub struct YamlStreamFormat<I>(pub I);344impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {345 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {346 let Val::Arr(arr) = val else {347 bail!(348 "output should be array for yaml stream format, got {}",349 val.value_type()350 )351 };352 if !arr.is_empty() {353 for v in arr.iter() {354 let v = v?;355 out.push_str("---\n");356 self.0.manifest_buf(v, out)?;357 out.push('\n');358 }359 out.push_str("...");360 }361 Ok(())362 }363}364365pub fn escape_string_json(s: &str) -> String {366 let mut buf = String::new();367 escape_string_json_buf(s, &mut buf);368 buf369}370371// Json string encoding was borrowed from https://github.com/serde-rs/json372373const BB: u8 = b'b'; // \x08374const TT: u8 = b't'; // \x09375const NN: u8 = b'n'; // \x0A376const FF: u8 = b'f'; // \x0C377const RR: u8 = b'r'; // \x0D378const QU: u8 = b'"'; // \x22379const BS: u8 = b'\\'; // \x5C380const UU: u8 = b'u'; // \x00...\x1F except the ones above381const __: u8 = 0;382383// Lookup table of escape sequences. A value of b'x' at index i means that byte384// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.385static ESCAPE: [u8; 256] = [386 // 1 2 3 4 5 6 7 8 9 A B C D E F387 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0388 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1389 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2390 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3391 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4392 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5393 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6394 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7395 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8396 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9397 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A398 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B399 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C400 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D401 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E402 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F403];404405pub fn escape_string_json_buf(value: &str, buf: &mut String) {406 // Safety: we only write correct utf-8 in this function407 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };408 let bytes = value.as_bytes();409410 // Perfect for ascii strings, removes any reallocations411 buf.reserve(value.len() + 2);412413 buf.push(b'"');414415 let mut start = 0;416417 for (i, &byte) in bytes.iter().enumerate() {418 let escape = ESCAPE[byte as usize];419 if escape == __ {420 continue;421 }422423 if start < i {424 buf.extend_from_slice(&bytes[start..i]);425 }426 start = i + 1;427428 match escape {429 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {430 buf.extend_from_slice(&[b'\\', escape]);431 }432 self::UU => {433 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";434 let bytes = &[435 b'\\',436 b'u',437 b'0',438 b'0',439 HEX_DIGITS[(byte >> 4) as usize],440 HEX_DIGITS[(byte & 0xF) as usize],441 ];442 buf.extend_from_slice(bytes);443 }444 _ => unreachable!(),445 }446 }447448 if start == bytes.len() {449 buf.push(b'"');450 return;451 }452453 buf.extend_from_slice(&bytes[start..]);454 buf.push(b'"');455}1use std::{borrow::Cow, fmt::Write, ptr};23use crate::{bail, Result, ResultExt, State, Val};45pub trait ManifestFormat {6 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;7 fn manifest(&self, val: Val) -> Result<String> {8 let mut out = String::new();9 self.manifest_buf(val, &mut out)?;10 Ok(out)11 }12 /// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13 /// the meaning).14 ///15 /// Default implementation returns `true`16 fn file_trailing_newline(&self) -> bool {17 true18 }19}20impl<T> ManifestFormat for Box<T>21where22 T: ManifestFormat + ?Sized,23{24 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {25 let inner = &**self;26 inner.manifest_buf(val, buf)27 }28 fn file_trailing_newline(&self) -> bool {29 let inner = &**self;30 inner.file_trailing_newline()31 }32}33impl<T> ManifestFormat for &'_ T34where35 T: ManifestFormat + ?Sized,36{37 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38 let inner = &**self;39 inner.manifest_buf(val, buf)40 }41 fn file_trailing_newline(&self) -> bool {42 let inner = &**self;43 inner.file_trailing_newline()44 }45}4647#[derive(PartialEq, Eq, Clone, Copy)]48enum JsonFormatting {49 // Applied in manifestification50 Manifest,51 /// Used for std.manifestJson52 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest53 Std,54 /// No line breaks, used in `obj+''`55 ToString,56 /// Minified json57 Minify,58}5960pub struct JsonFormat<'s> {61 padding: Cow<'s, str>,62 mtype: JsonFormatting,63 newline: &'s str,64 key_val_sep: &'s str,65 #[cfg(feature = "exp-preserve-order")]66 preserve_order: bool,67 #[cfg(feature = "exp-bigint")]68 preserve_bigints: bool,69 debug_truncate_strings: Option<usize>,70}7172impl<'s> JsonFormat<'s> {73 // Minifying format74 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {75 Self {76 padding: Cow::Borrowed(""),77 mtype: JsonFormatting::Minify,78 newline: "\n",79 key_val_sep: ":",80 #[cfg(feature = "exp-preserve-order")]81 preserve_order,82 #[cfg(feature = "exp-bigint")]83 preserve_bigints: false,84 debug_truncate_strings: None,85 }86 }87 // Same format as std.toString88 pub fn std_to_string() -> Self {89 Self {90 padding: Cow::Borrowed(""),91 mtype: JsonFormatting::ToString,92 newline: "\n",93 key_val_sep: ": ",94 #[cfg(feature = "exp-preserve-order")]95 preserve_order: false,96 #[cfg(feature = "exp-bigint")]97 preserve_bigints: false,98 debug_truncate_strings: None,99 }100 }101 pub fn std_to_json(102 padding: String,103 newline: &'s str,104 key_val_sep: &'s str,105 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,106 ) -> Self {107 Self {108 padding: Cow::Owned(padding),109 mtype: JsonFormatting::Std,110 newline,111 key_val_sep,112 #[cfg(feature = "exp-preserve-order")]113 preserve_order,114 #[cfg(feature = "exp-bigint")]115 preserve_bigints: false,116 debug_truncate_strings: None,117 }118 }119 // Same format as CLI manifestification120 pub fn cli(121 padding: usize,122 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,123 ) -> Self {124 if padding == 0 {125 return Self::minify(126 #[cfg(feature = "exp-preserve-order")]127 preserve_order,128 );129 }130 Self {131 padding: Cow::Owned(" ".repeat(padding)),132 mtype: JsonFormatting::Manifest,133 newline: "\n",134 key_val_sep: ": ",135 #[cfg(feature = "exp-preserve-order")]136 preserve_order,137 #[cfg(feature = "exp-bigint")]138 preserve_bigints: false,139 debug_truncate_strings: None,140 }141 }142 // Same format as CLI manifestification143 pub fn debug() -> Self {144 Self {145 padding: Cow::Borrowed(" "),146 mtype: JsonFormatting::Manifest,147 newline: "\n",148 key_val_sep: ": ",149 #[cfg(feature = "exp-preserve-order")]150 preserve_order: true,151 #[cfg(feature = "exp-bigint")]152 preserve_bigints: true,153 debug_truncate_strings: Some(256),154 }155 }156}157impl Default for JsonFormat<'static> {158 fn default() -> Self {159 Self {160 padding: Cow::Borrowed(" "),161 mtype: JsonFormatting::Manifest,162 newline: "\n",163 key_val_sep: ": ",164 #[cfg(feature = "exp-preserve-order")]165 preserve_order: false,166 #[cfg(feature = "exp-bigint")]167 preserve_bigints: false,168 debug_truncate_strings: None,169 }170 }171}172173pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {174 let mut out = String::new();175 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;176 Ok(out)177}178179#[allow(clippy::too_many_lines)]180fn manifest_json_ex_buf(181 val: &Val,182 buf: &mut String,183 cur_padding: &mut String,184 options: &JsonFormat<'_>,185) -> Result<()> {186 let mtype = options.mtype;187 match val {188 Val::Bool(v) => {189 if *v {190 buf.push_str("true");191 } else {192 buf.push_str("false");193 }194 }195 Val::Null => buf.push_str("null"),196 Val::Str(s) => {197 let flat = s.clone().into_flat();198 if let Some(truncate) = options.debug_truncate_strings {199 if flat.len() > truncate {200 let (start, end) = flat.split_at(truncate / 2);201 let (_, end) = end.split_at(end.len() - truncate / 2);202 escape_string_json_buf(&format!("{start}..{end}"), buf);203 } else {204 escape_string_json_buf(&flat, buf);205 }206 } else {207 escape_string_json_buf(&flat, buf);208 }209 }210 Val::Num(n) => write!(buf, "{n}").unwrap(),211 #[cfg(feature = "exp-bigint")]212 Val::BigInt(n) => {213 if options.preserve_bigints {214 write!(buf, "{n}").unwrap();215 } else {216 write!(buf, "{:?}", n.to_string()).unwrap();217 }218 }219 Val::Arr(items) => {220 buf.push('[');221 if !items.is_empty() {222 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {223 buf.push_str(options.newline);224 }225226 let old_len = cur_padding.len();227 cur_padding.push_str(&options.padding);228 for (i, item) in items.iter().enumerate() {229 if i != 0 {230 buf.push(',');231 if mtype == JsonFormatting::ToString {232 buf.push(' ');233 } else if mtype != JsonFormatting::Minify {234 buf.push_str(options.newline);235 }236 }237 buf.push_str(cur_padding);238 manifest_json_ex_buf(&item?, buf, cur_padding, options)239 .with_description(|| format!("elem <{i}> manifestification"))?;240 }241 cur_padding.truncate(old_len);242243 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {244 buf.push_str(options.newline);245 buf.push_str(cur_padding);246 }247 } else if mtype == JsonFormatting::Std {248 buf.push_str(options.newline);249 buf.push_str(options.newline);250 buf.push_str(cur_padding);251 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {252 buf.push(' ');253 }254 buf.push(']');255 }256 Val::Obj(obj) => {257 obj.run_assertions()?;258 buf.push('{');259 let fields = obj.fields(260 #[cfg(feature = "exp-preserve-order")]261 options.preserve_order,262 );263 if !fields.is_empty() {264 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {265 buf.push_str(options.newline);266 }267268 let old_len = cur_padding.len();269 cur_padding.push_str(&options.padding);270 for (i, field) in fields.into_iter().enumerate() {271 if i != 0 {272 buf.push(',');273 if mtype == JsonFormatting::ToString {274 buf.push(' ');275 } else if mtype != JsonFormatting::Minify {276 buf.push_str(options.newline);277 }278 }279 buf.push_str(cur_padding);280 escape_string_json_buf(&field, buf);281 buf.push_str(options.key_val_sep);282 State::push_description(283 || format!("field <{}> manifestification", field.clone()),284 || {285 let value = obj.get(field.clone())?.unwrap();286 manifest_json_ex_buf(&value, buf, cur_padding, options)?;287 Ok(())288 },289 )?;290 }291 cur_padding.truncate(old_len);292293 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {294 buf.push_str(options.newline);295 buf.push_str(cur_padding);296 }297 } else if mtype == JsonFormatting::Std {298 buf.push_str(options.newline);299 buf.push_str(options.newline);300 buf.push_str(cur_padding);301 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {302 buf.push(' ');303 }304 buf.push('}');305 }306 Val::Func(_) => bail!("tried to manifest function"),307 };308 Ok(())309}310311impl ManifestFormat for JsonFormat<'_> {312 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {313 manifest_json_ex_buf(&val, buf, &mut String::new(), self)314 }315}316317pub struct ToStringFormat;318impl ManifestFormat for ToStringFormat {319 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {320 JsonFormat::std_to_string().manifest_buf(val, out)321 }322 fn file_trailing_newline(&self) -> bool {323 false324 }325}326pub struct StringFormat;327impl ManifestFormat for StringFormat {328 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {329 let Val::Str(s) = val else {330 bail!(331 "output should be string for string manifest format, got {}",332 val.value_type()333 )334 };335 write!(out, "{s}").unwrap();336 Ok(())337 }338 fn file_trailing_newline(&self) -> bool {339 false340 }341}342343pub struct YamlStreamFormat<I> {344 inner: I,345 c_document_end: bool,346 end_newline: bool,347}348impl<I> YamlStreamFormat<I> {349 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {350 Self {351 inner,352 c_document_end,353 // Stdlib format always inserts newline at the end354 end_newline: true,355 }356 }357 pub fn cli(inner: I) -> Self {358 Self {359 inner,360 c_document_end: true,361 end_newline: false,362 }363 }364}365impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {366 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {367 let Val::Arr(arr) = val else {368 bail!(369 "output should be array for yaml stream format, got {}",370 val.value_type()371 )372 };373 if !arr.is_empty() {374 for v in arr.iter() {375 let v = v?;376 out.push_str("---\n");377 self.inner.manifest_buf(v, out)?;378 out.push('\n');379 }380 }381 if self.c_document_end {382 out.push_str("...");383 }384 if self.end_newline {385 out.push('\n');386 }387 Ok(())388 }389}390391pub fn escape_string_json(s: &str) -> String {392 let mut buf = String::new();393 escape_string_json_buf(s, &mut buf);394 buf395}396397// Json string encoding was borrowed from https://github.com/serde-rs/json398399const BB: u8 = b'b'; // \x08400const TT: u8 = b't'; // \x09401const NN: u8 = b'n'; // \x0A402const FF: u8 = b'f'; // \x0C403const RR: u8 = b'r'; // \x0D404const QU: u8 = b'"'; // \x22405const BS: u8 = b'\\'; // \x5C406const UU: u8 = b'u'; // \x00...\x1F except the ones above407const __: u8 = 0;408409// Lookup table of escape sequences. A value of b'x' at index i means that byte410// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.411static ESCAPE: [u8; 256] = [412 // 1 2 3 4 5 6 7 8 9 A B C D E F413 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0414 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1415 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2416 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3417 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4418 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5419 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6420 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7421 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8422 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9423 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A424 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B425 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C426 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D427 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E428 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F429];430431pub fn escape_string_json_buf(value: &str, buf: &mut String) {432 // Safety: we only write correct utf-8 in this function433 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };434 let bytes = value.as_bytes();435436 // Perfect for ascii strings, removes any reallocations437 buf.reserve(value.len() + 2);438439 buf.push(b'"');440441 let mut start = 0;442443 for (i, &byte) in bytes.iter().enumerate() {444 let escape = ESCAPE[byte as usize];445 if escape == __ {446 continue;447 }448449 if start < i {450 buf.extend_from_slice(&bytes[start..i]);451 }452 start = i + 1;453454 match escape {455 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {456 buf.extend_from_slice(&[b'\\', escape]);457 }458 self::UU => {459 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";460 let bytes = &[461 b'\\',462 b'u',463 b'0',464 b'0',465 HEX_DIGITS[(byte >> 4) as usize],466 HEX_DIGITS[(byte & 0xF) as usize],467 ];468 buf.extend_from_slice(bytes);469 }470 _ => unreachable!(),471 }472 }473474 if start == bytes.len() {475 buf.push(b'"');476 return;477 }478479 buf.extend_from_slice(&bytes[start..]);480 buf.push(b'"');481}crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -2,6 +2,7 @@
cell::RefCell,
fmt::{self, Debug, Display},
mem::replace,
+ num::{NonZeroU32, NonZeroUsize},
rc::Rc,
};
@@ -277,26 +278,11 @@
.into(),
))
}
- Self::Arr(arr) => {
- let get_idx = |pos: Option<i32>, len: usize, default| match pos {
- Some(v) if v < 0 => len.saturating_sub((-v) as usize),
- Some(v) => (v as usize).min(len),
- None => default,
- };
- let index = get_idx(index, arr.len(), 0);
- let end = get_idx(end, arr.len(), arr.len());
- let step = step.as_deref().copied().unwrap_or(1);
-
- if index >= end {
- return Ok(Self::Arr(ArrValue::empty()));
- }
-
- Ok(Self::Arr(
- arr.clone()
- .slice(Some(index), Some(end), Some(step))
- .expect("arguments checked"),
- ))
- }
+ Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(
+ index,
+ end,
+ step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),
+ ))),
}
}
}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -34,6 +34,12 @@
Ok(Some(attr))
}
+fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)
+where
+ Ident: PartialEq<I>,
+{
+ attrs.retain(|a| !a.path().is_ident(&ident));
+}
fn path_is(path: &Path, needed: &str) -> bool {
path.leading_colon.is_none()
@@ -121,10 +127,21 @@
}
}
+enum Optionality {
+ Required,
+ Optional,
+ Default(Expr),
+}
+impl Optionality {
+ fn is_optional(&self) -> bool {
+ !matches!(self, Self::Required)
+ }
+}
+
enum ArgInfo {
Normal {
ty: Box<Type>,
- is_option: bool,
+ optionality: Optionality,
name: Option<String>,
cfg_attrs: Vec<Attribute>,
},
@@ -138,7 +155,7 @@
}
impl ArgInfo {
- fn parse(name: &str, arg: &FnArg) -> Result<Self> {
+ fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {
let FnArg::Typed(arg) = arg else {
unreachable!()
};
@@ -163,7 +180,10 @@
_ => {}
}
- let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {
+ let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {
+ remove_attr(&mut arg.attrs, "default");
+ (Optionality::Default(default), ty.clone())
+ } else if let Some(ty) = extract_type_from_option(ty)? {
if type_is_path(ty, "Thunk").is_some() {
return Ok(Self::Lazy {
is_option: true,
@@ -171,9 +191,9 @@
});
}
- (true, Box::new(ty.clone()))
+ (Optionality::Optional, Box::new(ty.clone()))
} else {
- (false, ty.clone())
+ (Optionality::Required, ty.clone())
};
let cfg_attrs = arg
@@ -185,7 +205,7 @@
Ok(Self::Normal {
ty,
- is_option,
+ optionality,
name: ident.map(|v| v.to_string()),
cfg_attrs,
})
@@ -201,18 +221,14 @@
let item_fn = item.clone();
let item_fn: ItemFn = parse_macro_input!(item_fn);
- match builtin_inner(attr, item_fn, item.into()) {
+ match builtin_inner(attr, item_fn) {
Ok(v) => v.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[allow(clippy::too_many_lines)]
-fn builtin_inner(
- attr: BuiltinAttrs,
- fun: ItemFn,
- item: proc_macro2::TokenStream,
-) -> syn::Result<TokenStream> {
+fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {
let ReturnType::Type(_, result) = &fun.sig.output else {
return Err(Error::new(
fun.sig.span(),
@@ -224,13 +240,13 @@
let args = fun
.sig
.inputs
- .iter()
+ .iter_mut()
.map(|arg| ArgInfo::parse(&name, arg))
.collect::<Result<Vec<_>>>()?;
let params_desc = args.iter().filter_map(|a| match a {
ArgInfo::Normal {
- is_option,
+ optionality,
name,
cfg_attrs,
..
@@ -238,9 +254,10 @@
let name = name
.as_ref()
.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});
+ let is_optional = optionality.is_optional();
Some(quote! {
#(#cfg_attrs)*
- BuiltinParam::new(#name, #is_option),
+ BuiltinParam::new(#name, #is_optional),
})
}
ArgInfo::Lazy { is_option, name } => {
@@ -270,7 +287,7 @@
.map(|(id, a)| match a {
ArgInfo::Normal {
ty,
- is_option,
+ optionality,
name,
cfg_attrs,
} => {
@@ -279,17 +296,22 @@
|| format!("argument <{}> evaluation", #name),
|| <#ty>::from_untyped(value.evaluate()?),
)?};
- let value = if *is_option {
- quote! {if let Some(value) = &parsed[#id] {
+ let value = match optionality {
+ Optionality::Required => quote! {{
+ let value = parsed[#id].as_ref().expect("args shape is checked");
+ #eval
+ },},
+ Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {
Some(#eval)
} else {
None
- },}
- } else {
- quote! {{
- let value = parsed[#id].as_ref().expect("args shape is checked");
+ },},
+ Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {
#eval
- },}
+ } else {
+ let v: #ty = #expr;
+ v
+ },},
};
quote! {
#(#cfg_attrs)*
@@ -302,7 +324,7 @@
Some(value.clone())
} else {
None
- }}
+ },}
} else {
quote! {
parsed[#id].as_ref().expect("args shape is correct").clone(),
@@ -343,7 +365,7 @@
};
Ok(quote! {
- #item
+ #fun
#[doc(hidden)]
#[allow(non_camel_case_types)]
@@ -373,7 +395,7 @@
fn params(&self) -> &[BuiltinParam] {
PARAMS
}
- #[allow(unused_variable)]
+ #[allow(unused_variables)]
fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;
crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -265,21 +265,18 @@
}
#[builtin]
-pub fn builtin_remove_at(arr: ArrValue, at: usize) -> Result<ArrValue> {
+pub fn builtin_remove_at(arr: ArrValue, at: i32) -> Result<ArrValue> {
let newArrLeft = arr.clone().slice(None, Some(at), None);
let newArrRight = arr.slice(Some(at + 1), None, None);
- Ok(ArrValue::extended(
- newArrLeft.unwrap_or_else(ArrValue::empty),
- newArrRight.unwrap_or_else(ArrValue::empty),
- ))
+ Ok(ArrValue::extended(newArrLeft, newArrRight))
}
#[builtin]
pub fn builtin_remove(arr: ArrValue, elem: Val) -> Result<ArrValue> {
for (index, item) in arr.iter().enumerate() {
if equals(&item?, &elem)? {
- return builtin_remove_at(arr.clone(), index);
+ return builtin_remove_at(arr.clone(), index as i32);
}
}
Ok(arr)
@@ -325,7 +322,9 @@
#[builtin]
pub fn builtin_prune(
a: Val,
- #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Result<Val> {
fn is_content(val: &Val) -> bool {
match val {
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -102,6 +102,7 @@
("sign", builtin_sign::INST),
("max", builtin_max::INST),
("min", builtin_min::INST),
+ ("clamp", builtin_clamp::INST),
("sum", builtin_sum::INST),
("modulo", builtin_modulo::INST),
("floor", builtin_floor::INST),
@@ -163,11 +164,20 @@
("objectRemoveKey", builtin_object_remove_key::INST),
// Manifest
("escapeStringJson", builtin_escape_string_json::INST),
+ ("escapeStringPython", builtin_escape_string_json::INST),
+ ("escapeStringXML", builtin_escape_string_xml::INST),
("manifestJsonEx", builtin_manifest_json_ex::INST),
+ ("manifestJson", builtin_manifest_json::INST),
+ ("manifestJsonMinified", builtin_manifest_json_minified::INST),
("manifestYamlDoc", builtin_manifest_yaml_doc::INST),
+ ("manifestYamlStream", builtin_manifest_yaml_stream::INST),
("manifestTomlEx", builtin_manifest_toml_ex::INST),
+ ("manifestToml", builtin_manifest_toml::INST),
("toString", builtin_to_string::INST),
- // Parsing
+ ("manifestPython", builtin_manifest_python::INST),
+ ("manifestPythonVars", builtin_manifest_python_vars::INST),
+ ("manifestXmlJsonml", builtin_manifest_xml_jsonml::INST),
+ // Parse
("parseJson", builtin_parse_json::INST),
("parseYaml", builtin_parse_yaml::INST),
// Strings
@@ -175,10 +185,13 @@
("substr", builtin_substr::INST),
("char", builtin_char::INST),
("strReplace", builtin_str_replace::INST),
+ ("escapeStringBash", builtin_escape_string_bash::INST),
+ ("escapeStringDollars", builtin_escape_string_dollars::INST),
("isEmpty", builtin_is_empty::INST),
("equalsIgnoreCase", builtin_equals_ignore_case::INST),
("splitLimit", builtin_splitlimit::INST),
("splitLimitR", builtin_splitlimitr::INST),
+ ("split", builtin_split::INST),
("asciiUpper", builtin_ascii_upper::INST),
("asciiLower", builtin_ascii_lower::INST),
("findSubstr", builtin_find_substr::INST),
@@ -190,6 +203,7 @@
("stringChars", builtin_string_chars::INST),
// Misc
("length", builtin_length::INST),
+ ("get", builtin_get::INST),
("startsWith", builtin_starts_with::INST),
("endsWith", builtin_ends_with::INST),
// Sets
crates/jrsonnet-stdlib/src/manifest/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/mod.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/mod.rs
@@ -1,13 +1,17 @@
+mod python;
mod toml;
+mod xml;
mod yaml;
use jrsonnet_evaluator::{
function::builtin,
- manifest::{escape_string_json, JsonFormat},
+ manifest::{escape_string_json, JsonFormat, YamlStreamFormat},
IStr, ObjValue, Result, Val,
};
+pub use python::{PythonFormat, PythonVarsFormat};
pub use toml::TomlFormat;
pub use yaml::YamlFormat;
+pub use xml::XmlJsonmlFormat;
#[builtin]
pub fn builtin_escape_string_json(str_: IStr) -> Result<String> {
@@ -17,51 +21,149 @@
#[builtin]
pub fn builtin_manifest_json_ex(
value: Val,
- indent: IStr,
+ indent: String,
newline: Option<IStr>,
key_val_sep: Option<IStr>,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Result<String> {
let newline = newline.as_deref().unwrap_or("\n");
let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
value.manifest(JsonFormat::std_to_json(
- indent.to_string(),
+ indent,
newline,
key_val_sep,
#[cfg(feature = "exp-preserve-order")]
- preserve_order.unwrap_or(false),
+ preserve_order,
+ ))
+}
+
+#[builtin]
+pub fn builtin_manifest_json(
+ value: Val,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+) -> Result<String> {
+ builtin_manifest_json_ex(
+ value,
+ " ".to_owned(),
+ None,
+ None,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ )
+}
+
+#[builtin]
+pub fn builtin_manifest_json_minified(
+ value: Val,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+) -> Result<String> {
+ value.manifest(JsonFormat::minify(
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
))
}
#[builtin]
pub fn builtin_manifest_yaml_doc(
value: Val,
- indent_array_in_object: Option<bool>,
- quote_keys: Option<bool>,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+ #[default(false)] indent_array_in_object: bool,
+ #[default(true)] quote_keys: bool,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Result<String> {
value.manifest(YamlFormat::std_to_yaml(
- indent_array_in_object.unwrap_or(false),
- quote_keys.unwrap_or(true),
+ indent_array_in_object,
+ quote_keys,
#[cfg(feature = "exp-preserve-order")]
- preserve_order.unwrap_or(false),
+ preserve_order,
+ ))
+}
+
+#[builtin]
+pub fn builtin_manifest_yaml_stream(
+ value: Val,
+ #[default(false)] indent_array_in_object: bool,
+ #[default(true)] c_document_end: bool,
+ #[default(true)] quote_keys: bool,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+) -> Result<String> {
+ value.manifest(YamlStreamFormat::std_yaml_stream(
+ YamlFormat::std_to_yaml(
+ indent_array_in_object,
+ quote_keys,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ ),
+ c_document_end,
))
}
#[builtin]
pub fn builtin_manifest_toml_ex(
value: ObjValue,
- indent: IStr,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+ indent: String,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Result<String> {
Val::Obj(value).manifest(TomlFormat::std_to_toml(
- indent.to_string(),
+ indent,
#[cfg(feature = "exp-preserve-order")]
- preserve_order.unwrap_or(false),
+ preserve_order,
))
}
#[builtin]
+pub fn builtin_manifest_toml(
+ value: ObjValue,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+) -> Result<String> {
+ builtin_manifest_toml_ex(
+ value,
+ " ".to_owned(),
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ )
+}
+
+#[builtin]
pub fn builtin_to_string(a: Val) -> Result<IStr> {
a.to_string()
}
+
+#[builtin]
+pub fn builtin_manifest_python(v: Val) -> Result<String> {
+ v.manifest(PythonFormat {})
+}
+#[builtin]
+pub fn builtin_manifest_python_vars(v: Val) -> Result<String> {
+ v.manifest(PythonVarsFormat {})
+}
+
+#[builtin]
+pub fn builtin_escape_string_xml(str_: String) -> String {
+ xml::escape_string_xml(str_.as_str())
+}
+
+#[builtin]
+pub fn builtin_manifest_xml_jsonml(value: Val) -> Result<String> {
+ value.manifest(XmlJsonmlFormat::std_to_xml())
+}
crates/jrsonnet-stdlib/src/manifest/python.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest/python.rs
@@ -0,0 +1,87 @@
+use jrsonnet_evaluator::{
+ bail,
+ manifest::{escape_string_json_buf, ManifestFormat, ToStringFormat},
+ Result, Val,
+};
+
+pub struct PythonFormat {
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+}
+
+impl ManifestFormat for PythonFormat {
+ fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+ match val {
+ Val::Bool(true) => buf.push_str("True"),
+ Val::Bool(false) => buf.push_str("False"),
+ Val::Null => buf.push_str("None"),
+ Val::Str(s) => escape_string_json_buf(&s.to_string(), buf),
+ Val::Num(_) => ToStringFormat.manifest_buf(val, buf)?,
+ Val::Arr(arr) => {
+ buf.push('[');
+ for (i, el) in arr.iter().enumerate() {
+ let el = el?;
+ if i != 0 {
+ buf.push_str(", ");
+ }
+ self.manifest_buf(el, buf)?;
+ }
+ buf.push(']');
+ }
+ Val::Obj(obj) => {
+ obj.run_assertions()?;
+ buf.push('{');
+ let fields = obj.fields(
+ #[cfg(feature = "exp-preserve-order")]
+ self.preserve_order,
+ );
+ for (i, field) in fields.into_iter().enumerate() {
+ if i != 0 {
+ buf.push_str(", ");
+ }
+ escape_string_json_buf(&field, buf);
+ buf.push_str(": ");
+ let value = obj.get(field)?.expect("field exists");
+ self.manifest_buf(value, buf)?;
+ }
+ buf.push('}');
+ }
+ Val::Func(_) => bail!("tried to manifest function"),
+ }
+ Ok(())
+ }
+}
+
+pub struct PythonVarsFormat {
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
+}
+
+impl PythonVarsFormat {}
+
+impl ManifestFormat for PythonVarsFormat {
+ fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+ let inner = PythonFormat {
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: self.preserve_order,
+ };
+ let Val::Obj(obj) = val else {
+ bail!("python vars root should be object");
+ };
+ obj.run_assertions()?;
+
+ let fields = obj.fields(
+ #[cfg(feature = "exp-preserve-order")]
+ self.preserve_order,
+ );
+
+ for field in fields {
+ // Yep, no escaping
+ buf.push_str(&field);
+ buf.push_str(" = ");
+ inner.manifest_buf(obj.get(field)?.expect("field exists"), buf)?;
+ buf.push('\n');
+ }
+ Ok(())
+ }
+}
crates/jrsonnet-stdlib/src/manifest/xml.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs
@@ -0,0 +1,173 @@
+use jrsonnet_evaluator::{
+ bail,
+ manifest::{ManifestFormat, ToStringFormat},
+ typed::{ComplexValType, Either4, Typed, ValType},
+ val::{ArrValue, IndexableVal},
+ Either, ObjValue, Result, ResultExt, Val,
+};
+
+pub struct XmlJsonmlFormat {
+ force_closing: bool,
+}
+impl XmlJsonmlFormat {
+ pub fn std_to_xml() -> Self {
+ Self {
+ force_closing: true,
+ }
+ }
+ pub fn cli() -> Self {
+ Self {
+ force_closing: false,
+ }
+ }
+}
+
+enum JSONMLValue {
+ Tag {
+ tag: String,
+ attrs: ObjValue,
+ children: Vec<JSONMLValue>,
+ },
+ String(String),
+}
+impl Typed for JSONMLValue {
+ const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);
+
+ fn into_untyped(_typed: Self) -> Result<Val> {
+ unreachable!("not used, reserved for parseXML?")
+ }
+
+ fn from_untyped(untyped: Val) -> Result<Self> {
+ let Val::Arr(arr) = untyped else {
+ if let Val::Str(s) = untyped {
+ return Ok(Self::String(s.to_string()));
+ };
+ bail!("expected JSONML value (an array or string)");
+ };
+ if arr.len() < 1 {
+ bail!("JSONML value should have tag");
+ };
+ let tag = String::from_untyped(
+ arr.get(0)
+ .with_description(|| "getting JSONML tag")?
+ .expect("length checked"),
+ )?;
+ let (has_attrs, attrs) = if arr.len() >= 2 {
+ let maybe_attrs = arr
+ .get(1)
+ .with_description(|| "getting JSONML attrs")?
+ .expect("length checked");
+ if let Val::Obj(attrs) = maybe_attrs {
+ (true, attrs)
+ } else {
+ (false, ObjValue::new_empty())
+ }
+ } else {
+ (false, ObjValue::new_empty())
+ };
+ Ok(Self::Tag {
+ tag,
+ attrs,
+ children: Typed::from_untyped(Val::Arr(arr.slice(
+ Some(if has_attrs { 2 } else { 1 }),
+ None,
+ None,
+ )))?,
+ })
+ }
+}
+
+impl ManifestFormat for XmlJsonmlFormat {
+ fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+ let val = JSONMLValue::from_untyped(val).with_description(|| "parsing JSONML value")?;
+ manifest_jsonml(&val, buf, self)
+ }
+}
+
+fn manifest_jsonml(v: &JSONMLValue, buf: &mut String, opts: &XmlJsonmlFormat) -> Result<()> {
+ match v {
+ JSONMLValue::Tag {
+ tag,
+ attrs,
+ children,
+ } => {
+ let has_children = !children.is_empty();
+ buf.push('<');
+ buf.push_str(&tag);
+ attrs.run_assertions()?;
+ for (key, value) in attrs.iter() {
+ buf.push(' ');
+ buf.push_str(&key);
+ buf.push('=');
+ buf.push('"');
+ let value = value?;
+ let value = if let Val::Str(s) = value {
+ s.to_string()
+ } else {
+ ToStringFormat.manifest(value)?
+ };
+ escape_string_xml_buf(&value, buf);
+ buf.push('"');
+ }
+ if !has_children && !opts.force_closing {
+ buf.push('/');
+ }
+ buf.push('>');
+ for child in children {
+ manifest_jsonml(&child, buf, opts)?;
+ }
+ if has_children || opts.force_closing {
+ buf.push('<');
+ buf.push('/');
+ buf.push_str(&tag);
+ buf.push('>');
+ }
+ Ok(())
+ }
+ JSONMLValue::String(s) => {
+ escape_string_xml_buf(s, buf);
+ Ok(())
+ }
+ }
+}
+
+pub fn escape_string_xml(str: &str) -> String {
+ let mut out = String::new();
+ escape_string_xml_buf(str, &mut out);
+ out
+}
+
+fn escape_string_xml_buf(str: &str, out: &mut String) {
+ if str.is_empty() {
+ return;
+ }
+ let mut remaining = str;
+
+ let mut found = false;
+ while let Some(position) = remaining
+ .bytes()
+ .position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))
+ {
+ found = true;
+
+ let (plain, rem) = remaining.split_at(position);
+ out.push_str(plain);
+
+ out.push_str(match rem.as_bytes()[0] {
+ b'<' => "<",
+ b'>' => ">",
+ b'&' => "&",
+ b'"' => """,
+ b'\'' => "'",
+ _ => unreachable!("position() searches for those matches"),
+ });
+
+ remaining = &rem[1..];
+ }
+ if !found {
+ // No match - no escapes required
+ out.push_str(&str);
+ return;
+ }
+ out.push_str(&remaining);
+}
crates/jrsonnet-stdlib/src/math.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/math.rs
+++ b/crates/jrsonnet-stdlib/src/math.rs
@@ -24,6 +24,12 @@
a.min(b)
}
+#[allow(non_snake_case)]
+#[builtin]
+pub fn builtin_clamp(x: f64, minVal: f64, maxVal: f64) -> f64 {
+ x.clamp(minVal, maxVal)
+}
+
#[builtin]
pub fn builtin_sum(arr: Vec<f64>) -> f64 {
arr.iter().sum()
crates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -23,6 +23,30 @@
}
}
+#[builtin]
+pub fn builtin_get(
+ o: ObjValue,
+ f: IStr,
+ default: Option<Thunk<Val>>,
+ #[default(true)]
+ inc_hidden: bool,
+) -> Result<Val> {
+ let do_default = move || {
+ let Some(default) = default else {
+ return Ok(Val::Null);
+ };
+ default.evaluate()
+ };
+ // Happy path for invisible fields
+ if !inc_hidden && !o.has_field_ex(f.clone(), false) {
+ return do_default();
+ }
+ let Some(v) = o.get(f)? else {
+ return do_default();
+ };
+ Ok(v)
+}
+
#[builtin(fields(
settings: Rc<RefCell<Settings>>,
))]
crates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -8,10 +8,11 @@
pub fn builtin_object_fields_ex(
obj: ObjValue,
hidden: bool,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Vec<Val> {
+
+ #[default(false)]
#[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
+ preserve_order: bool,
+) -> Vec<Val> {
let out = obj.fields_ex(
hidden,
#[cfg(feature = "exp-preserve-order")]
@@ -23,7 +24,10 @@
#[builtin]
pub fn builtin_object_fields(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Vec<Val> {
builtin_object_fields_ex(
o,
@@ -36,7 +40,10 @@
#[builtin]
pub fn builtin_object_fields_all(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> Vec<Val> {
builtin_object_fields_ex(
o,
@@ -49,10 +56,9 @@
pub fn builtin_object_values_ex(
o: ObjValue,
include_hidden: bool,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
) -> ArrValue {
- #[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
o.values_ex(
include_hidden,
#[cfg(feature = "exp-preserve-order")]
@@ -62,7 +68,10 @@
#[builtin]
pub fn builtin_object_values(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ArrValue {
builtin_object_values_ex(
o,
@@ -74,7 +83,10 @@
#[builtin]
pub fn builtin_object_values_all(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ArrValue {
builtin_object_values_ex(
o,
@@ -87,10 +99,8 @@
pub fn builtin_object_keys_values_ex(
o: ObjValue,
include_hidden: bool,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
) -> ArrValue {
- #[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
o.key_values_ex(
include_hidden,
#[cfg(feature = "exp-preserve-order")]
@@ -100,7 +110,10 @@
#[builtin]
pub fn builtin_object_keys_values(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ArrValue {
builtin_object_keys_values_ex(
o,
@@ -112,7 +125,10 @@
#[builtin]
pub fn builtin_object_keys_values_all(
o: ObjValue,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ArrValue {
builtin_object_keys_values_ex(
o,
@@ -141,12 +157,13 @@
pub fn builtin_object_remove_key(
obj: ObjValue,
key: IStr,
+
// Standard implementation uses std.objectFields without such argument, we can't
// assume order preservation should always be enabled/disabled
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+ #[default(false)]
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: bool,
) -> ObjValue {
- #[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
let mut new_obj = ObjValueBuilder::with_capacity(obj.len() - 1);
for (k, v) in obj.iter(
#[cfg(feature = "exp-preserve-order")]
crates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -139,8 +139,12 @@
}
#[builtin]
-pub fn builtin_sort(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
- super::sort::sort(arr, keyF.unwrap_or_else(FuncVal::identity))
+pub fn builtin_sort(
+ arr: ArrValue,
+
+ #[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
+ super::sort::sort(arr, keyF)
}
fn uniq_identity(arr: Vec<Val>) -> Result<Vec<Val>> {
@@ -174,11 +178,14 @@
#[builtin]
#[allow(non_snake_case)]
-pub fn builtin_uniq(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_uniq(
+ arr: ArrValue,
+
+ #[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
if arr.len() <= 1 {
return Ok(arr);
}
- let keyF = keyF.unwrap_or(FuncVal::identity());
if keyF.is_identity() {
Ok(ArrValue::eager(uniq_identity(
arr.iter().collect::<Result<Vec<Val>>>()?,
@@ -190,11 +197,14 @@
#[builtin]
#[allow(non_snake_case)]
-pub fn builtin_set(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_set(
+ arr: ArrValue,
+
+ #[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
if arr.len() <= 1 {
return Ok(arr);
}
- let keyF = keyF.unwrap_or(FuncVal::identity());
if keyF.is_identity() {
let arr = arr.iter().collect::<Result<Vec<Val>>>()?;
let arr = sort_identity(arr)?;
crates/jrsonnet-stdlib/src/std.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()
}