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.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -11,7 +11,7 @@
/// Represents a Jsonnet array value.
#[derive(Debug, Clone, Trace)]
-// may contrain other ArrValue
+// may contain other ArrValue
#[trace(tracking(force))]
pub struct ArrValue(Cc<TraceBox<dyn ArrayLike>>);
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.rsdiffbeforeafterboth1#![allow(clippy::similar_names)]23use std::{4 cell::{Ref, RefCell, RefMut},5 collections::HashMap,6 rc::Rc,7};89pub use arrays::*;10pub use compat::*;11pub use encoding::*;12pub use hash::*;13use jrsonnet_evaluator::{14 error::{ErrorKind::*, Result},15 function::{CallLocation, FuncVal, TlaArg},16 tb,17 trace::PathResolver,18 ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val,19};20use jrsonnet_gcmodule::Trace;21use jrsonnet_parser::Source;22pub use manifest::*;23pub use math::*;24pub use misc::*;25pub use objects::*;26pub use operator::*;27pub use parse::*;28pub use sets::*;29pub use sort::*;30pub use strings::*;31pub use types::*;3233#[cfg(feature = "exp-regex")]34pub use crate::regex::*;3536mod arrays;37mod compat;38mod encoding;39mod expr;40mod hash;41mod manifest;42mod math;43mod misc;44mod objects;45mod operator;46mod parse;47#[cfg(feature = "exp-regex")]48mod regex;49mod sets;50mod sort;51mod strings;52mod types;5354#[allow(clippy::too_many_lines)]55pub fn stdlib_uncached(settings: Rc<RefCell<Settings>>) -> ObjValue {56 let mut builder = ObjValueBuilder::new();5758 let expr = expr::stdlib_expr();59 let eval = jrsonnet_evaluator::evaluate(ContextBuilder::dangerous_empty_state().build(), &expr)60 .expect("stdlib.jsonnet should have no errors")61 .as_obj()62 .expect("stdlib.jsonnet should evaluate to object");6364 builder.with_super(eval);6566 // FIXME: Use PHF67 for (name, builtin) in [68 // Types69 ("type", builtin_type::INST),70 ("isString", builtin_is_string::INST),71 ("isNumber", builtin_is_number::INST),72 ("isBoolean", builtin_is_boolean::INST),73 ("isObject", builtin_is_object::INST),74 ("isArray", builtin_is_array::INST),75 ("isFunction", builtin_is_function::INST),76 // Arrays77 ("makeArray", builtin_make_array::INST),78 ("repeat", builtin_repeat::INST),79 ("slice", builtin_slice::INST),80 ("map", builtin_map::INST),81 ("flatMap", builtin_flatmap::INST),82 ("filter", builtin_filter::INST),83 ("foldl", builtin_foldl::INST),84 ("foldr", builtin_foldr::INST),85 ("range", builtin_range::INST),86 ("join", builtin_join::INST),87 ("reverse", builtin_reverse::INST),88 ("any", builtin_any::INST),89 ("all", builtin_all::INST),90 ("member", builtin_member::INST),91 ("contains", builtin_contains::INST),92 ("count", builtin_count::INST),93 ("avg", builtin_avg::INST),94 ("removeAt", builtin_remove_at::INST),95 ("remove", builtin_remove::INST),96 ("flattenArrays", builtin_flatten_arrays::INST),97 ("flattenDeepArray", builtin_flatten_deep_array::INST),98 ("prune", builtin_prune::INST),99 ("filterMap", builtin_filter_map::INST),100 // Math101 ("abs", builtin_abs::INST),102 ("sign", builtin_sign::INST),103 ("max", builtin_max::INST),104 ("min", builtin_min::INST),105 ("clamp", builtin_clamp::INST),106 ("sum", builtin_sum::INST),107 ("modulo", builtin_modulo::INST),108 ("floor", builtin_floor::INST),109 ("ceil", builtin_ceil::INST),110 ("log", builtin_log::INST),111 ("pow", builtin_pow::INST),112 ("sqrt", builtin_sqrt::INST),113 ("sin", builtin_sin::INST),114 ("cos", builtin_cos::INST),115 ("tan", builtin_tan::INST),116 ("asin", builtin_asin::INST),117 ("acos", builtin_acos::INST),118 ("atan", builtin_atan::INST),119 ("atan2", builtin_atan2::INST),120 ("exp", builtin_exp::INST),121 ("mantissa", builtin_mantissa::INST),122 ("exponent", builtin_exponent::INST),123 ("round", builtin_round::INST),124 ("isEven", builtin_is_even::INST),125 ("isOdd", builtin_is_odd::INST),126 ("isInteger", builtin_is_integer::INST),127 ("isDecimal", builtin_is_decimal::INST),128 // Operator129 ("mod", builtin_mod::INST),130 ("primitiveEquals", builtin_primitive_equals::INST),131 ("equals", builtin_equals::INST),132 ("xor", builtin_xor::INST),133 ("xnor", builtin_xnor::INST),134 ("format", builtin_format::INST),135 // Sort136 ("sort", builtin_sort::INST),137 ("uniq", builtin_uniq::INST),138 ("set", builtin_set::INST),139 ("minArray", builtin_min_array::INST),140 ("maxArray", builtin_max_array::INST),141 // Hash142 ("md5", builtin_md5::INST),143 ("sha1", builtin_sha1::INST),144 ("sha256", builtin_sha256::INST),145 ("sha512", builtin_sha512::INST),146 ("sha3", builtin_sha3::INST),147 // Encoding148 ("encodeUTF8", builtin_encode_utf8::INST),149 ("decodeUTF8", builtin_decode_utf8::INST),150 ("base64", builtin_base64::INST),151 ("base64Decode", builtin_base64_decode::INST),152 ("base64DecodeBytes", builtin_base64_decode_bytes::INST),153 // Objects154 ("objectFieldsEx", builtin_object_fields_ex::INST),155 ("objectFields", builtin_object_fields::INST),156 ("objectFieldsAll", builtin_object_fields_all::INST),157 ("objectValues", builtin_object_values::INST),158 ("objectValuesAll", builtin_object_values_all::INST),159 ("objectKeysValues", builtin_object_keys_values::INST),160 ("objectKeysValuesAll", builtin_object_keys_values_all::INST),161 ("objectHasEx", builtin_object_has_ex::INST),162 ("objectHas", builtin_object_has::INST),163 ("objectHasAll", builtin_object_has_all::INST),164 ("objectRemoveKey", builtin_object_remove_key::INST),165 // Manifest166 ("escapeStringJson", builtin_escape_string_json::INST),167 ("escapeStringPython", builtin_escape_string_python::INST),168 ("escapeStringXML", builtin_escape_string_xml::INST),169 ("manifestJsonEx", builtin_manifest_json_ex::INST),170 ("manifestJson", builtin_manifest_json::INST),171 ("manifestJsonMinified", builtin_manifest_json_minified::INST),172 ("manifestYamlDoc", builtin_manifest_yaml_doc::INST),173 ("manifestYamlStream", builtin_manifest_yaml_stream::INST),174 ("manifestTomlEx", builtin_manifest_toml_ex::INST),175 ("manifestToml", builtin_manifest_toml::INST),176 ("toString", builtin_to_string::INST),177 ("manifestPython", builtin_manifest_python::INST),178 ("manifestPythonVars", builtin_manifest_python_vars::INST),179 ("manifestXmlJsonml", builtin_manifest_xml_jsonml::INST),180 // Parse181 ("parseJson", builtin_parse_json::INST),182 ("parseYaml", builtin_parse_yaml::INST),183 // Strings184 ("codepoint", builtin_codepoint::INST),185 ("substr", builtin_substr::INST),186 ("char", builtin_char::INST),187 ("strReplace", builtin_str_replace::INST),188 ("escapeStringBash", builtin_escape_string_bash::INST),189 ("escapeStringDollars", builtin_escape_string_dollars::INST),190 ("isEmpty", builtin_is_empty::INST),191 ("equalsIgnoreCase", builtin_equals_ignore_case::INST),192 ("splitLimit", builtin_splitlimit::INST),193 ("splitLimitR", builtin_splitlimitr::INST),194 ("split", builtin_split::INST),195 ("asciiUpper", builtin_ascii_upper::INST),196 ("asciiLower", builtin_ascii_lower::INST),197 ("findSubstr", builtin_find_substr::INST),198 ("parseInt", builtin_parse_int::INST),199 #[cfg(feature = "exp-bigint")]200 ("bigint", builtin_bigint::INST),201 ("parseOctal", builtin_parse_octal::INST),202 ("parseHex", builtin_parse_hex::INST),203 ("stringChars", builtin_string_chars::INST),204 // Misc205 ("length", builtin_length::INST),206 ("get", builtin_get::INST),207 ("startsWith", builtin_starts_with::INST),208 ("endsWith", builtin_ends_with::INST),209 // Sets210 ("setMember", builtin_set_member::INST),211 ("setInter", builtin_set_inter::INST),212 ("setDiff", builtin_set_diff::INST),213 ("setUnion", builtin_set_union::INST),214 // Regex215 #[cfg(feature = "exp-regex")]216 ("regexQuoteMeta", builtin_regex_quote_meta::INST),217 // Compat218 ("__compare", builtin___compare::INST),219 ]220 .iter()221 .copied()222 {223 builder.method(name, builtin);224 }225226 builder.method(227 "extVar",228 builtin_ext_var {229 settings: settings.clone(),230 },231 );232 builder.method(233 "native",234 builtin_native {235 settings: settings.clone(),236 },237 );238 builder.method("trace", builtin_trace { settings });239 builder.method("id", FuncVal::Id);240241 #[cfg(feature = "exp-regex")]242 {243 // Regex244 let regex_cache = RegexCache::default();245 builder.method(246 "regexFullMatch",247 builtin_regex_full_match {248 cache: regex_cache.clone(),249 },250 );251 builder.method(252 "regexPartialMatch",253 builtin_regex_partial_match {254 cache: regex_cache.clone(),255 },256 );257 builder.method(258 "regexReplace",259 builtin_regex_replace {260 cache: regex_cache.clone(),261 },262 );263 builder.method(264 "regexGlobalReplace",265 builtin_regex_global_replace { cache: regex_cache },266 );267 };268269 builder.build()270}271272pub trait TracePrinter {273 fn print_trace(&self, loc: CallLocation, value: IStr);274}275276pub struct StdTracePrinter {277 resolver: PathResolver,278}279impl StdTracePrinter {280 pub fn new(resolver: PathResolver) -> Self {281 Self { resolver }282 }283}284impl TracePrinter for StdTracePrinter {285 fn print_trace(&self, loc: CallLocation, value: IStr) {286 eprint!("TRACE:");287 if let Some(loc) = loc.0 {288 let locs = loc.0.map_source_locations(&[loc.1]);289 eprint!(290 " {}:{}",291 loc.0.source_path().path().map_or_else(292 || loc.0.source_path().to_string(),293 |p| self.resolver.resolve(p)294 ),295 locs[0].line296 );297 }298 eprintln!(" {value}");299 }300}301302pub struct Settings {303 /// Used for `std.extVar`304 pub ext_vars: HashMap<IStr, TlaArg>,305 /// Used for `std.native`306 pub ext_natives: HashMap<IStr, FuncVal>,307 /// Used for `std.trace`308 pub trace_printer: Box<dyn TracePrinter>,309 /// Used for `std.thisFile`310 pub path_resolver: PathResolver,311}312313fn extvar_source(name: &str, code: impl Into<IStr>) -> Source {314 let source_name = format!("<extvar:{name}>");315 Source::new_virtual(source_name.into(), code.into())316}317318#[derive(Trace, Clone)]319pub struct ContextInitializer {320 /// When we don't need to support legacy-this-file, we can reuse same context for all files321 #[cfg(not(feature = "legacy-this-file"))]322 context: jrsonnet_evaluator::Context,323 /// For `populate`324 #[cfg(not(feature = "legacy-this-file"))]325 stdlib_thunk: Thunk<Val>,326 /// Otherwise, we can only keep first stdlib layer, and then stack thisFile on top of it327 #[cfg(feature = "legacy-this-file")]328 stdlib_obj: ObjValue,329 settings: Rc<RefCell<Settings>>,330}331impl ContextInitializer {332 pub fn new(s: State, resolver: PathResolver) -> Self {333 let settings = Settings {334 ext_vars: HashMap::new(),335 ext_natives: HashMap::new(),336 trace_printer: Box::new(StdTracePrinter::new(resolver.clone())),337 path_resolver: resolver,338 };339 let settings = Rc::new(RefCell::new(settings));340 let stdlib_obj = stdlib_uncached(settings.clone());341 #[cfg(not(feature = "legacy-this-file"))]342 let stdlib_thunk = Thunk::evaluated(Val::Obj(stdlib_obj));343 #[cfg(feature = "legacy-this-file")]344 let _ = s;345 Self {346 #[cfg(not(feature = "legacy-this-file"))]347 context: {348 let mut context = ContextBuilder::with_capacity(s, 1);349 context.bind("std", stdlib_thunk.clone());350 context.build()351 },352 #[cfg(not(feature = "legacy-this-file"))]353 stdlib_thunk,354 #[cfg(feature = "legacy-this-file")]355 stdlib_obj,356 settings,357 }358 }359 pub fn settings(&self) -> Ref<Settings> {360 self.settings.borrow()361 }362 pub fn settings_mut(&self) -> RefMut<Settings> {363 self.settings.borrow_mut()364 }365 pub fn add_ext_var(&self, name: IStr, value: Val) {366 self.settings_mut()367 .ext_vars368 .insert(name, TlaArg::Val(value));369 }370 pub fn add_ext_str(&self, name: IStr, value: IStr) {371 self.settings_mut()372 .ext_vars373 .insert(name, TlaArg::String(value));374 }375 pub fn add_ext_code(&self, name: &str, code: impl Into<IStr>) -> Result<()> {376 let code = code.into();377 let source = extvar_source(name, code.clone());378 let parsed = jrsonnet_parser::parse(379 &code,380 &jrsonnet_parser::ParserSettings {381 source: source.clone(),382 },383 )384 .map_err(|e| ImportSyntaxError {385 path: source,386 error: Box::new(e),387 })?;388 // self.data_mut().volatile_files.insert(source_name, code);389 self.settings_mut()390 .ext_vars391 .insert(name.into(), TlaArg::Code(parsed));392 Ok(())393 }394 pub fn add_native(&self, name: impl Into<IStr>, cb: impl Into<FuncVal>) {395 self.settings_mut()396 .ext_natives397 .insert(name.into(), cb.into());398 }399}400impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {401 fn reserve_vars(&self) -> usize {402 1403 }404 #[cfg(not(feature = "legacy-this-file"))]405 fn initialize(&self, _s: State, _source: Source) -> jrsonnet_evaluator::Context {406 self.context.clone()407 }408 #[cfg(not(feature = "legacy-this-file"))]409 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {410 builder.bind("std", self.stdlib_thunk.clone());411 }412 #[cfg(feature = "legacy-this-file")]413 fn populate(&self, source: Source, builder: &mut ContextBuilder) {414 let mut std = ObjValueBuilder::new();415 std.with_super(self.stdlib_obj.clone());416 std.field("thisFile").hide().value({417 let source_path = source.source_path();418 source_path.path().map_or_else(419 || source_path.to_string(),420 |p| self.settings().path_resolver.resolve(p),421 )422 });423 let stdlib_with_this_file = std.build();424425 builder.bind("std", Thunk::evaluated(Val::Obj(stdlib_with_this_file)));426 }427 fn as_any(&self) -> &dyn std::any::Any {428 self429 }430}431432pub trait StateExt {433 /// This method was previously implemented in jrsonnet-evaluator itself434 fn with_stdlib(&self);435}436437impl StateExt for State {438 fn with_stdlib(&self) {439 let initializer = ContextInitializer::new(self.clone(), PathResolver::new_cwd_fallback());440 self.settings_mut().context_initializer = tb!(initializer);441 }442}1#![allow(clippy::similar_names)]23use std::{4 cell::{Ref, RefCell, RefMut},5 collections::HashMap,6 rc::Rc,7};89pub use arrays::*;10pub use compat::*;11pub use encoding::*;12pub use hash::*;13use jrsonnet_evaluator::{14 error::{ErrorKind::*, Result},15 function::{CallLocation, FuncVal, TlaArg},16 tb,17 trace::PathResolver,18 ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val,19};20use jrsonnet_gcmodule::Trace;21use jrsonnet_parser::Source;22pub use manifest::*;23pub use math::*;24pub use misc::*;25pub use objects::*;26pub use operator::*;27pub use parse::*;28pub use sets::*;29pub use sort::*;30pub use strings::*;31pub use types::*;3233#[cfg(feature = "exp-regex")]34pub use crate::regex::*;3536mod arrays;37mod compat;38mod encoding;39mod expr;40mod hash;41mod manifest;42mod math;43mod misc;44mod objects;45mod operator;46mod parse;47#[cfg(feature = "exp-regex")]48mod regex;49mod sets;50mod sort;51mod strings;52mod types;5354#[allow(clippy::too_many_lines)]55pub fn stdlib_uncached(settings: Rc<RefCell<Settings>>) -> ObjValue {56 let mut builder = ObjValueBuilder::new();5758 let expr = expr::stdlib_expr();59 let eval = jrsonnet_evaluator::evaluate(ContextBuilder::dangerous_empty_state().build(), &expr)60 .expect("stdlib.jsonnet should have no errors")61 .as_obj()62 .expect("stdlib.jsonnet should evaluate to object");6364 builder.with_super(eval);6566 // FIXME: Use PHF67 for (name, builtin) in [68 // Types69 ("type", builtin_type::INST),70 ("isString", builtin_is_string::INST),71 ("isNumber", builtin_is_number::INST),72 ("isBoolean", builtin_is_boolean::INST),73 ("isObject", builtin_is_object::INST),74 ("isArray", builtin_is_array::INST),75 ("isFunction", builtin_is_function::INST),76 // Arrays77 ("makeArray", builtin_make_array::INST),78 ("repeat", builtin_repeat::INST),79 ("slice", builtin_slice::INST),80 ("map", builtin_map::INST),81 ("flatMap", builtin_flatmap::INST),82 ("filter", builtin_filter::INST),83 ("foldl", builtin_foldl::INST),84 ("foldr", builtin_foldr::INST),85 ("range", builtin_range::INST),86 ("join", builtin_join::INST),87 ("reverse", builtin_reverse::INST),88 ("any", builtin_any::INST),89 ("all", builtin_all::INST),90 ("member", builtin_member::INST),91 ("contains", builtin_contains::INST),92 ("count", builtin_count::INST),93 ("avg", builtin_avg::INST),94 ("removeAt", builtin_remove_at::INST),95 ("remove", builtin_remove::INST),96 ("flattenArrays", builtin_flatten_arrays::INST),97 ("flattenDeepArray", builtin_flatten_deep_array::INST),98 ("prune", builtin_prune::INST),99 ("filterMap", builtin_filter_map::INST),100 // Math101 ("abs", builtin_abs::INST),102 ("sign", builtin_sign::INST),103 ("max", builtin_max::INST),104 ("min", builtin_min::INST),105 ("clamp", builtin_clamp::INST),106 ("sum", builtin_sum::INST),107 ("modulo", builtin_modulo::INST),108 ("floor", builtin_floor::INST),109 ("ceil", builtin_ceil::INST),110 ("log", builtin_log::INST),111 ("pow", builtin_pow::INST),112 ("sqrt", builtin_sqrt::INST),113 ("sin", builtin_sin::INST),114 ("cos", builtin_cos::INST),115 ("tan", builtin_tan::INST),116 ("asin", builtin_asin::INST),117 ("acos", builtin_acos::INST),118 ("atan", builtin_atan::INST),119 ("atan2", builtin_atan2::INST),120 ("exp", builtin_exp::INST),121 ("mantissa", builtin_mantissa::INST),122 ("exponent", builtin_exponent::INST),123 ("round", builtin_round::INST),124 ("isEven", builtin_is_even::INST),125 ("isOdd", builtin_is_odd::INST),126 ("isInteger", builtin_is_integer::INST),127 ("isDecimal", builtin_is_decimal::INST),128 // Operator129 ("mod", builtin_mod::INST),130 ("primitiveEquals", builtin_primitive_equals::INST),131 ("equals", builtin_equals::INST),132 ("xor", builtin_xor::INST),133 ("xnor", builtin_xnor::INST),134 ("format", builtin_format::INST),135 // Sort136 ("sort", builtin_sort::INST),137 ("uniq", builtin_uniq::INST),138 ("set", builtin_set::INST),139 ("minArray", builtin_min_array::INST),140 ("maxArray", builtin_max_array::INST),141 // Hash142 ("md5", builtin_md5::INST),143 ("sha1", builtin_sha1::INST),144 ("sha256", builtin_sha256::INST),145 ("sha512", builtin_sha512::INST),146 ("sha3", builtin_sha3::INST),147 // Encoding148 ("encodeUTF8", builtin_encode_utf8::INST),149 ("decodeUTF8", builtin_decode_utf8::INST),150 ("base64", builtin_base64::INST),151 ("base64Decode", builtin_base64_decode::INST),152 ("base64DecodeBytes", builtin_base64_decode_bytes::INST),153 // Objects154 ("objectFieldsEx", builtin_object_fields_ex::INST),155 ("objectFields", builtin_object_fields::INST),156 ("objectFieldsAll", builtin_object_fields_all::INST),157 ("objectValues", builtin_object_values::INST),158 ("objectValuesAll", builtin_object_values_all::INST),159 ("objectKeysValues", builtin_object_keys_values::INST),160 ("objectKeysValuesAll", builtin_object_keys_values_all::INST),161 ("objectHasEx", builtin_object_has_ex::INST),162 ("objectHas", builtin_object_has::INST),163 ("objectHasAll", builtin_object_has_all::INST),164 ("objectRemoveKey", builtin_object_remove_key::INST),165 // Manifest166 ("escapeStringJson", builtin_escape_string_json::INST),167 ("escapeStringPython", builtin_escape_string_python::INST),168 ("escapeStringXML", builtin_escape_string_xml::INST),169 ("manifestJsonEx", builtin_manifest_json_ex::INST),170 ("manifestJson", builtin_manifest_json::INST),171 ("manifestJsonMinified", builtin_manifest_json_minified::INST),172 ("manifestYamlDoc", builtin_manifest_yaml_doc::INST),173 ("manifestYamlStream", builtin_manifest_yaml_stream::INST),174 ("manifestTomlEx", builtin_manifest_toml_ex::INST),175 ("manifestToml", builtin_manifest_toml::INST),176 ("toString", builtin_to_string::INST),177 ("manifestPython", builtin_manifest_python::INST),178 ("manifestPythonVars", builtin_manifest_python_vars::INST),179 ("manifestXmlJsonml", builtin_manifest_xml_jsonml::INST),180 // Parse181 ("parseJson", builtin_parse_json::INST),182 ("parseYaml", builtin_parse_yaml::INST),183 // Strings184 ("codepoint", builtin_codepoint::INST),185 ("substr", builtin_substr::INST),186 ("char", builtin_char::INST),187 ("strReplace", builtin_str_replace::INST),188 ("escapeStringBash", builtin_escape_string_bash::INST),189 ("escapeStringDollars", builtin_escape_string_dollars::INST),190 ("isEmpty", builtin_is_empty::INST),191 ("equalsIgnoreCase", builtin_equals_ignore_case::INST),192 ("splitLimit", builtin_splitlimit::INST),193 ("splitLimitR", builtin_splitlimitr::INST),194 ("split", builtin_split::INST),195 ("asciiUpper", builtin_ascii_upper::INST),196 ("asciiLower", builtin_ascii_lower::INST),197 ("findSubstr", builtin_find_substr::INST),198 ("parseInt", builtin_parse_int::INST),199 #[cfg(feature = "exp-bigint")]200 ("bigint", builtin_bigint::INST),201 ("parseOctal", builtin_parse_octal::INST),202 ("parseHex", builtin_parse_hex::INST),203 ("stringChars", builtin_string_chars::INST),204 ("lstripChars", builtin_lstrip_chars::INST),205 ("rstripChars", builtin_rstrip_chars::INST),206 ("stripChars", builtin_strip_chars::INST),207 // Misc208 ("length", builtin_length::INST),209 ("get", builtin_get::INST),210 ("startsWith", builtin_starts_with::INST),211 ("endsWith", builtin_ends_with::INST),212 // Sets213 ("setMember", builtin_set_member::INST),214 ("setInter", builtin_set_inter::INST),215 ("setDiff", builtin_set_diff::INST),216 ("setUnion", builtin_set_union::INST),217 // Regex218 #[cfg(feature = "exp-regex")]219 ("regexQuoteMeta", builtin_regex_quote_meta::INST),220 // Compat221 ("__compare", builtin___compare::INST),222 ]223 .iter()224 .copied()225 {226 builder.method(name, builtin);227 }228229 builder.method(230 "extVar",231 builtin_ext_var {232 settings: settings.clone(),233 },234 );235 builder.method(236 "native",237 builtin_native {238 settings: settings.clone(),239 },240 );241 builder.method("trace", builtin_trace { settings });242 builder.method("id", FuncVal::Id);243244 #[cfg(feature = "exp-regex")]245 {246 // Regex247 let regex_cache = RegexCache::default();248 builder.method(249 "regexFullMatch",250 builtin_regex_full_match {251 cache: regex_cache.clone(),252 },253 );254 builder.method(255 "regexPartialMatch",256 builtin_regex_partial_match {257 cache: regex_cache.clone(),258 },259 );260 builder.method(261 "regexReplace",262 builtin_regex_replace {263 cache: regex_cache.clone(),264 },265 );266 builder.method(267 "regexGlobalReplace",268 builtin_regex_global_replace { cache: regex_cache },269 );270 };271272 builder.build()273}274275pub trait TracePrinter {276 fn print_trace(&self, loc: CallLocation, value: IStr);277}278279pub struct StdTracePrinter {280 resolver: PathResolver,281}282impl StdTracePrinter {283 pub fn new(resolver: PathResolver) -> Self {284 Self { resolver }285 }286}287impl TracePrinter for StdTracePrinter {288 fn print_trace(&self, loc: CallLocation, value: IStr) {289 eprint!("TRACE:");290 if let Some(loc) = loc.0 {291 let locs = loc.0.map_source_locations(&[loc.1]);292 eprint!(293 " {}:{}",294 loc.0.source_path().path().map_or_else(295 || loc.0.source_path().to_string(),296 |p| self.resolver.resolve(p)297 ),298 locs[0].line299 );300 }301 eprintln!(" {value}");302 }303}304305pub struct Settings {306 /// Used for `std.extVar`307 pub ext_vars: HashMap<IStr, TlaArg>,308 /// Used for `std.native`309 pub ext_natives: HashMap<IStr, FuncVal>,310 /// Used for `std.trace`311 pub trace_printer: Box<dyn TracePrinter>,312 /// Used for `std.thisFile`313 pub path_resolver: PathResolver,314}315316fn extvar_source(name: &str, code: impl Into<IStr>) -> Source {317 let source_name = format!("<extvar:{name}>");318 Source::new_virtual(source_name.into(), code.into())319}320321#[derive(Trace, Clone)]322pub struct ContextInitializer {323 /// When we don't need to support legacy-this-file, we can reuse same context for all files324 #[cfg(not(feature = "legacy-this-file"))]325 context: jrsonnet_evaluator::Context,326 /// For `populate`327 #[cfg(not(feature = "legacy-this-file"))]328 stdlib_thunk: Thunk<Val>,329 /// Otherwise, we can only keep first stdlib layer, and then stack thisFile on top of it330 #[cfg(feature = "legacy-this-file")]331 stdlib_obj: ObjValue,332 settings: Rc<RefCell<Settings>>,333}334impl ContextInitializer {335 pub fn new(s: State, resolver: PathResolver) -> Self {336 let settings = Settings {337 ext_vars: HashMap::new(),338 ext_natives: HashMap::new(),339 trace_printer: Box::new(StdTracePrinter::new(resolver.clone())),340 path_resolver: resolver,341 };342 let settings = Rc::new(RefCell::new(settings));343 let stdlib_obj = stdlib_uncached(settings.clone());344 #[cfg(not(feature = "legacy-this-file"))]345 let stdlib_thunk = Thunk::evaluated(Val::Obj(stdlib_obj));346 #[cfg(feature = "legacy-this-file")]347 let _ = s;348 Self {349 #[cfg(not(feature = "legacy-this-file"))]350 context: {351 let mut context = ContextBuilder::with_capacity(s, 1);352 context.bind("std", stdlib_thunk.clone());353 context.build()354 },355 #[cfg(not(feature = "legacy-this-file"))]356 stdlib_thunk,357 #[cfg(feature = "legacy-this-file")]358 stdlib_obj,359 settings,360 }361 }362 pub fn settings(&self) -> Ref<Settings> {363 self.settings.borrow()364 }365 pub fn settings_mut(&self) -> RefMut<Settings> {366 self.settings.borrow_mut()367 }368 pub fn add_ext_var(&self, name: IStr, value: Val) {369 self.settings_mut()370 .ext_vars371 .insert(name, TlaArg::Val(value));372 }373 pub fn add_ext_str(&self, name: IStr, value: IStr) {374 self.settings_mut()375 .ext_vars376 .insert(name, TlaArg::String(value));377 }378 pub fn add_ext_code(&self, name: &str, code: impl Into<IStr>) -> Result<()> {379 let code = code.into();380 let source = extvar_source(name, code.clone());381 let parsed = jrsonnet_parser::parse(382 &code,383 &jrsonnet_parser::ParserSettings {384 source: source.clone(),385 },386 )387 .map_err(|e| ImportSyntaxError {388 path: source,389 error: Box::new(e),390 })?;391 // self.data_mut().volatile_files.insert(source_name, code);392 self.settings_mut()393 .ext_vars394 .insert(name.into(), TlaArg::Code(parsed));395 Ok(())396 }397 pub fn add_native(&self, name: impl Into<IStr>, cb: impl Into<FuncVal>) {398 self.settings_mut()399 .ext_natives400 .insert(name.into(), cb.into());401 }402}403impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {404 fn reserve_vars(&self) -> usize {405 1406 }407 #[cfg(not(feature = "legacy-this-file"))]408 fn initialize(&self, _s: State, _source: Source) -> jrsonnet_evaluator::Context {409 self.context.clone()410 }411 #[cfg(not(feature = "legacy-this-file"))]412 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {413 builder.bind("std", self.stdlib_thunk.clone());414 }415 #[cfg(feature = "legacy-this-file")]416 fn populate(&self, source: Source, builder: &mut ContextBuilder) {417 let mut std = ObjValueBuilder::new();418 std.with_super(self.stdlib_obj.clone());419 std.field("thisFile").hide().value({420 let source_path = source.source_path();421 source_path.path().map_or_else(422 || source_path.to_string(),423 |p| self.settings().path_resolver.resolve(p),424 )425 });426 let stdlib_with_this_file = std.build();427428 builder.bind("std", Thunk::evaluated(Val::Obj(stdlib_with_this_file)));429 }430 fn as_any(&self) -> &dyn std::any::Any {431 self432 }433}434435pub trait StateExt {436 /// This method was previously implemented in jrsonnet-evaluator itself437 fn with_stdlib(&self);438}439440impl StateExt for State {441 fn with_stdlib(&self) {442 let initializer = ContextInitializer::new(self.clone(), PathResolver::new_cwd_fallback());443 self.settings_mut().context_initializer = tb!(initializer);444 }445}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,