difftreelog
feat `*stripChars` builtins
in: master
12 files changed
.editorconfigdiffbeforeafterboth--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,7 @@
+root = true
+
+[tests/golden/*.jsonnet.golden]
+generated_code = true
+indent_style = space
+indent_size = 4
+insert_final_newline = false
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -90,9 +90,9 @@
[[package]]
name = "anyhow"
-version = "1.0.83"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
+checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "autocfg"
@@ -102,9 +102,9 @@
[[package]]
name = "base64"
-version = "0.21.7"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "beef"
@@ -194,7 +194,7 @@
"heck",
"proc-macro2",
"quote",
- "syn 2.0.61",
+ "syn 2.0.64",
]
[[package]]
@@ -258,6 +258,12 @@
]
[[package]]
+name = "difflib"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
+
+[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -289,9 +295,9 @@
[[package]]
name = "either"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
+checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]]
name = "encode_unicode"
@@ -412,9 +418,9 @@
[[package]]
name = "insta"
-version = "1.38.0"
+version = "1.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc"
+checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5"
dependencies = [
"console",
"lazy_static",
@@ -430,9 +436,9 @@
[[package]]
name = "itertools"
-version = "0.12.1"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
@@ -547,7 +553,7 @@
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.61",
+ "syn 2.0.64",
]
[[package]]
@@ -608,6 +614,17 @@
]
[[package]]
+name = "json-structural-diff"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25c7940d3c84d2079306c176c7b2b37622b6bc5e43fbd1541b1e4a4e1fd02045"
+dependencies = [
+ "difflib",
+ "regex",
+ "serde_json",
+]
+
+[[package]]
name = "keccak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -624,9 +641,9 @@
[[package]]
name = "libc"
-version = "0.2.154"
+version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libjsonnet"
@@ -646,9 +663,9 @@
[[package]]
name = "linux-raw-sys"
-version = "0.4.13"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
@@ -681,7 +698,7 @@
"proc-macro2",
"quote",
"regex-syntax",
- "syn 2.0.61",
+ "syn 2.0.64",
]
[[package]]
@@ -989,22 +1006,22 @@
[[package]]
name = "serde"
-version = "1.0.201"
+version = "1.0.202"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
+checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.201"
+version = "1.0.202"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
+checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.61",
+ "syn 2.0.64",
]
[[package]]
@@ -1121,9 +1138,9 @@
[[package]]
name = "syn"
-version = "2.0.61"
+version = "2.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
+checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f"
dependencies = [
"proc-macro2",
"quote",
@@ -1149,7 +1166,9 @@
"jrsonnet-evaluator",
"jrsonnet-gcmodule",
"jrsonnet-stdlib",
+ "json-structural-diff",
"serde",
+ "serde_json",
]
[[package]]
@@ -1160,22 +1179,22 @@
[[package]]
name = "thiserror"
-version = "1.0.60"
+version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
+checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.60"
+version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
+checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.61",
+ "syn 2.0.64",
]
[[package]]
@@ -1347,5 +1366,5 @@
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.61",
+ "syn 2.0.64",
]
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,8 +44,8 @@
serde_yaml_with_quirks = "0.8.24"
# Error handling
-anyhow = "1.0.80"
-thiserror = "1.0"
+anyhow = "1.0.83"
+thiserror = "1.0.60"
# Code formatting
dprint-core = "0.65.0"
@@ -63,20 +63,20 @@
# Source code parsing.
# Jrsonnet has two parsers for jsonnet - one is for execution, and another is for better parsing diagnostics/lints/LSP.
# First (and fast one) is based on peg, second is based on rowan.
-peg = "0.8.2"
+peg = "0.8.3"
logos = "0.14.0"
ungrammar = "1.16.1"
-rowan = "0.15"
+rowan = "0.15.15"
mimallocator = "0.1.3"
indoc = "2.0"
-insta = "1.35"
+insta = "1.39"
tempfile = "3.10"
pathdiff = "0.2.1"
-hashbrown = "0.14.3"
+hashbrown = "0.14.5"
static_assertions = "1.1"
rustc-hash = "1.1"
-num-bigint = "0.4.4"
+num-bigint = "0.4.5"
derivative = "2.2.0"
strsim = "0.11.0"
structdump = "0.2.0"
@@ -84,16 +84,18 @@
quote = "1.0"
syn = "2.0"
drop_bomb = "0.1.5"
-base64 = "0.21.7"
+base64 = "0.22.1"
indexmap = "2.2.3"
-itertools = "0.12.1"
-xshell = "0.2.5"
+itertools = "0.13.0"
+xshell = "0.2.6"
lsp-server = "0.7.6"
-lsp-types = "0.95.0"
+lsp-types = "0.96.0"
-regex = "1.10.3"
-lru = "0.12.2"
+regex = "1.10"
+lru = "0.12.3"
+
+json-structural-diff = "0.1.0"
[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "deny"
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth1use std::{any::Any, num::NonZeroU32};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IBytes;5use jrsonnet_parser::LocExpr;67use crate::{function::FuncVal, gc::TraceBox, tb, Context, Result, Thunk, Val};89mod spec;10pub use spec::{ArrayLike, *};1112/// Represents a Jsonnet array value.13#[derive(Debug, Clone, Trace)]14// may contain other ArrValue15#[trace(tracking(force))]16pub struct ArrValue(Cc<TraceBox<dyn ArrayLike>>);1718pub trait ArrayLikeIter<T>: Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator {}19impl<I, T> ArrayLikeIter<T> for I where20 I: Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator21{22}2324impl ArrValue {25 pub fn new(v: impl ArrayLike) -> Self {26 Self(Cc::new(tb!(v)))27 }28 pub fn empty() -> Self {29 Self::new(RangeArray::empty())30 }3132 pub fn expr(ctx: Context, exprs: impl IntoIterator<Item = LocExpr>) -> Self {33 Self::new(ExprArray::new(ctx, exprs))34 }3536 pub fn lazy(thunks: Vec<Thunk<Val>>) -> Self {37 Self::new(LazyArray(thunks))38 }3940 pub fn eager(values: Vec<Val>) -> Self {41 Self::new(EagerArray(values))42 }4344 pub fn repeated(data: Self, repeats: usize) -> Option<Self> {45 Some(Self::new(RepeatedArray::new(data, repeats)?))46 }4748 pub fn bytes(bytes: IBytes) -> Self {49 Self::new(BytesArray(bytes))50 }51 pub fn chars(chars: impl Iterator<Item = char>) -> Self {52 Self::new(CharArray(chars.collect()))53 }5455 #[must_use]56 pub fn map(self, mapper: FuncVal) -> Self {57 Self::new(MappedArray::new(self, mapper))58 }5960 pub fn filter(self, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {61 // TODO: ArrValue::Picked(inner, indexes) for large arrays62 let mut out = Vec::new();63 for i in self.iter() {64 let i = i?;65 if filter(&i)? {66 out.push(i);67 };68 }69 Ok(Self::eager(out))70 }7172 pub fn extended(a: Self, b: Self) -> Self {73 // TODO: benchmark for an optimal value, currently just a arbitrary choice74 const ARR_EXTEND_THRESHOLD: usize = 100;7576 if a.is_empty() {77 b78 } else if b.is_empty() {79 a80 } else if a.len() + b.len() > ARR_EXTEND_THRESHOLD {81 Self::new(ExtendedArray::new(a, b))82 } else if let (Some(a), Some(b)) = (a.iter_cheap(), b.iter_cheap()) {83 let mut out = Vec::with_capacity(a.len() + b.len());84 out.extend(a);85 out.extend(b);86 Self::eager(out)87 } else {88 let mut out = Vec::with_capacity(a.len() + b.len());89 out.extend(a.iter_lazy());90 out.extend(b.iter_lazy());91 Self::lazy(out)92 }93 }9495 pub fn range_exclusive(a: i32, b: i32) -> Self {96 Self::new(RangeArray::new_exclusive(a, b))97 }98 pub fn range_inclusive(a: i32, b: i32) -> Self {99 Self::new(RangeArray::new_inclusive(a, b))100 }101102 #[must_use]103 pub fn slice(self, index: Option<i32>, end: Option<i32>, step: Option<NonZeroU32>) -> Self {104 let get_idx = |pos: Option<i32>, len: usize, default| match pos {105 Some(v) if v < 0 => len.saturating_sub((-v) as usize),106 Some(v) => (v as usize).min(len),107 None => default,108 };109 let index = get_idx(index, self.len(), 0);110 let end = get_idx(end, self.len(), self.len());111 let step = step.unwrap_or_else(|| NonZeroU32::new(1).expect("1 != 0"));112113 if index >= end {114 return Self::empty();115 }116117 Self::new(SliceArray {118 inner: self,119 from: index as u32,120 to: end as u32,121 step: step.get(),122 })123 }124125 /// Array length.126 pub fn len(&self) -> usize {127 self.0.len()128 }129130 /// Is array contains no elements?131 pub fn is_empty(&self) -> bool {132 self.0.is_empty()133 }134135 /// Get array element by index, evaluating it, if it is lazy.136 ///137 /// Returns `None` on out-of-bounds condition.138 pub fn get(&self, index: usize) -> Result<Option<Val>> {139 self.0.get(index)140 }141142 /// Returns None if get is either non cheap, or out of bounds143 fn get_cheap(&self, index: usize) -> Option<Val> {144 self.0.get_cheap(index)145 }146147 /// Get array element by index, without evaluation.148 ///149 /// Returns `None` on out-of-bounds condition.150 pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {151 self.0.get_lazy(index)152 }153154 pub fn iter(&self) -> impl ArrayLikeIter<Result<Val>> + '_ {155 (0..self.len()).map(|i| self.get(i).transpose().expect("length checked"))156 }157158 /// Iterate over elements, returning lazy values.159 pub fn iter_lazy(&self) -> impl ArrayLikeIter<Thunk<Val>> + '_ {160 (0..self.len()).map(|i| self.get_lazy(i).expect("length checked"))161 }162163 pub fn iter_cheap(&self) -> Option<impl ArrayLikeIter<Val> + '_> {164 if self.is_cheap() {165 Some((0..self.len()).map(|i| self.get_cheap(i).expect("length and is_cheap checked")))166 } else {167 None168 }169 }170171 /// Return a reversed view on current array.172 #[must_use]173 pub fn reversed(self) -> Self {174 Self::new(ReverseArray(self))175 }176177 pub fn ptr_eq(a: &Self, b: &Self) -> bool {178 Cc::ptr_eq(&a.0, &b.0)179 }180181 /// Is this vec supports `.get_cheap()?`182 pub fn is_cheap(&self) -> bool {183 self.0.is_cheap()184 }185186 pub fn as_any(&self) -> &dyn Any {187 &self.0188 }189}190impl From<Vec<Val>> for ArrValue {191 fn from(value: Vec<Val>) -> Self {192 Self::eager(value)193 }194}195impl From<Vec<Thunk<Val>>> for ArrValue {196 fn from(value: Vec<Thunk<Val>>) -> Self {197 Self::lazy(value)198 }199}200impl FromIterator<Val> for ArrValue {201 fn from_iter<T: IntoIterator<Item = Val>>(iter: T) -> Self {202 Self::eager(iter.into_iter().collect())203 }204}205impl ArrayLike for ArrValue {206 fn len(&self) -> usize {207 self.0.len()208 }209210 fn get(&self, index: usize) -> Result<Option<Val>> {211 self.0.get(index)212 }213214 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {215 self.0.get_lazy(index)216 }217218 fn get_cheap(&self, index: usize) -> Option<Val> {219 self.0.get_cheap(index)220 }221222 fn is_cheap(&self) -> bool {223 self.0.is_cheap()224 }225}226227#[cfg(target_pointer_width = "64")]228static_assertions::assert_eq_size!(ArrValue, [u8; 8]);crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -220,6 +220,13 @@
Arr(ArrValue),
}
impl IndexableVal {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Self::Str(s) => s.is_empty(),
+ Self::Arr(s) => s.is_empty(),
+ }
+ }
+
pub fn to_array(self) -> ArrValue {
match self {
Self::Str(s) => ArrValue::chars(s.chars()),
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -201,6 +201,9 @@
("parseOctal", builtin_parse_octal::INST),
("parseHex", builtin_parse_hex::INST),
("stringChars", builtin_string_chars::INST),
+ ("lstripChars", builtin_lstrip_chars::INST),
+ ("rstripChars", builtin_rstrip_chars::INST),
+ ("stripChars", builtin_strip_chars::INST),
// Misc
("length", builtin_length::INST),
("get", builtin_get::INST),
crates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -3,22 +3,6 @@
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',
- lstripChars(str, chars)::
- if std.length(str) > 0 && std.member(chars, str[0]) then
- std.lstripChars(str[1:], chars)
- else
- str,
-
- rstripChars(str, chars)::
- local len = std.length(str);
- if len > 0 && std.member(chars, str[len - 1]) then
- std.rstripChars(str[:len - 1], chars)
- else
- str,
-
- stripChars(str, chars)::
- std.lstripChars(std.rstripChars(str, chars), chars),
-
mapWithIndex(func, arr)::
if !std.isFunction(func) then
error ('std.mapWithIndex first param must be function, got ' + std.type(func))
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -1,9 +1,11 @@
+use std::collections::BTreeSet;
+
use jrsonnet_evaluator::{
bail,
error::{ErrorKind::*, Result},
function::builtin,
- typed::{Either2, M1},
- val::ArrValue,
+ typed::{Either2, Typed, M1},
+ val::{ArrValue, IndexableVal},
Either, IStr, Val,
};
@@ -215,6 +217,53 @@
})
}
+#[builtin]
+pub fn builtin_string_chars(str: IStr) -> ArrValue {
+ ArrValue::chars(str.chars())
+}
+
+#[builtin]
+pub fn builtin_lstrip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {
+ if str.is_empty() || chars.is_empty() {
+ return Ok(str);
+ }
+
+ let pattern = new_trim_pattern(chars)?;
+ Ok(str.as_str().trim_start_matches(pattern).into())
+}
+
+#[builtin]
+pub fn builtin_rstrip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {
+ if str.is_empty() || chars.is_empty() {
+ return Ok(str);
+ }
+
+ let pattern = new_trim_pattern(chars)?;
+ Ok(str.as_str().trim_end_matches(pattern).into())
+}
+
+#[builtin]
+pub fn builtin_strip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {
+ if str.is_empty() || chars.is_empty() {
+ return Ok(str);
+ }
+
+ let pattern = new_trim_pattern(chars)?;
+ Ok(str.as_str().trim_matches(pattern).into())
+}
+
+fn new_trim_pattern(chars: IndexableVal) -> Result<impl Fn(char) -> bool> {
+ let chars: BTreeSet<char> = match chars {
+ IndexableVal::Str(chars) => chars.chars().collect(),
+ IndexableVal::Arr(chars) => chars
+ .iter()
+ .filter_map(|it| it.map(|it| char::from_untyped(it).ok()).transpose())
+ .collect::<Result<_, _>>()?,
+ };
+
+ Ok(move |char| chars.contains(&char))
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -242,9 +291,4 @@
assert_eq!(parse_nat::<16>("a9").unwrap(), 0xA9 as f64);
assert_eq!(parse_nat::<16>("BbC").unwrap(), 0xBBC as f64);
}
-}
-
-#[builtin]
-pub fn builtin_string_chars(str: IStr) -> ArrValue {
- ArrValue::chars(str.chars())
}
tests/Cargo.tomldiffbeforeafterboth--- a/tests/Cargo.toml
+++ b/tests/Cargo.toml
@@ -12,3 +12,5 @@
jrsonnet-gcmodule.workspace = true
jrsonnet-stdlib.workspace = true
serde.workspace = true
+json-structural-diff.workspace = true
+serde_json.workspace = true
tests/golden/builtin_strings_string.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/builtin_strings_string.jsonnet
@@ -0,0 +1,21 @@
+{
+ lstripChars_singleChar: std.lstripChars("aaabcdef", "a"),
+ lstripChars_multipleChars: std.lstripChars("klmn", "kql"),
+ lstripChars_array: std.lstripChars("forward", [1, "f", [], "o", "d", "for"]),
+
+ rstripChars_singleChar: std.rstripChars("nice_boy", "y"),
+ rstripChars_multipleChars: std.rstripChars("amoguass", "sa"),
+ rstripChars_array: std.rstripChars("cool just cool", ["o", "l", 12.2323443]),
+
+ stripChars_singleCharL: std.stripChars("feefoofaa", "f"),
+ stripChars_singleCharR: std.stripChars("lolkekw", "w"),
+ stripChars_singleChar: std.stripChars("joper jej", "j"),
+
+ stripChars_multipleCharsL: std.stripChars("abcdefg", "cab"),
+ stripChars_multipleCharsR: std.stripChars("still breathing", "gthin"),
+ stripChars_multipleChars: std.stripChars("sus sus sus", "us"),
+
+ stripChars_arrayL: std.stripChars("chel medvedo svin", ["c", 3204990, {"svin": {}}, "vi"]),
+ stripChars_arrayR: std.stripChars("lach-vs-miri", ["r", "i", "craft", "is", "mine"]),
+ stripChars_array: std.stripChars("UwU Lel Stosh", ["h", "U", "s", {}, [], null, "w", [1, 2, 3]]),
+}
tests/golden/builtin_strings_string.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/builtin_strings_string.jsonnet.golden
@@ -0,0 +1,17 @@
+{
+ "lstripChars_array": "rward",
+ "lstripChars_multipleChars": "mn",
+ "lstripChars_singleChar": "bcdef",
+ "rstripChars_array": "cool just c",
+ "rstripChars_multipleChars": "amogu",
+ "rstripChars_singleChar": "nice_bo",
+ "stripChars_array": " Lel Sto",
+ "stripChars_arrayL": "hel medvedo svin",
+ "stripChars_arrayR": "lach-vs-m",
+ "stripChars_multipleChars": " sus ",
+ "stripChars_multipleCharsL": "defg",
+ "stripChars_multipleCharsR": "still brea",
+ "stripChars_singleChar": "oper je",
+ "stripChars_singleCharL": "eefoofaa",
+ "stripChars_singleCharR": "lolkek"
+}
\ No newline at end of file
tests/tests/golden.rsdiffbeforeafterboth--- a/tests/tests/golden.rs
+++ b/tests/tests/golden.rs
@@ -9,7 +9,6 @@
FileImportResolver, State,
};
use jrsonnet_stdlib::StateExt;
-
mod common;
fn run(file: &Path) -> String {
@@ -35,6 +34,8 @@
#[test]
fn test() -> io::Result<()> {
+ use json_structural_diff::JsonDiff;
+
let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
root.push("golden");
@@ -54,6 +55,35 @@
} else {
let golden = fs::read_to_string(golden_path)?;
+ match (serde_json::from_str(&result), serde_json::from_str(&golden)) {
+ (Err(_), Ok(_)) => assert_eq!(
+ result,
+ golden,
+ "unexpected error for golden {}",
+ entry.path().display()
+ ),
+ (Ok(_), Err(_)) => assert_eq!(
+ result,
+ golden,
+ "expected error for golden {}",
+ entry.path().display()
+ ),
+ (Ok(result), Ok(golden)) => {
+ // Show diff relative to golden`.
+ let diff = JsonDiff::diff_string(&golden, &result, false);
+ if let Some(diff) = diff {
+ panic!(
+ "Result \n{result:#}\n\
+ and golden \n{golden:#}\n\
+ did not match structurally:\n{diff:#}\n\
+ for golden {}",
+ entry.path().display()
+ );
+ }
+ }
+ (Err(_), Err(_)) => {}
+ };
+
assert_eq!(
result,
golden,