From e6a71c3471df933e33b455e6f16a40e0d6584ad5 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Fri, 17 May 2024 03:09:59 +0300 Subject: [PATCH] feat: `*stripChars` builtins --- .editorconfig | 7 ++ Cargo.lock | 79 ++++++++++++------- Cargo.toml | 28 ++++--- crates/jrsonnet-evaluator/src/arr/mod.rs | 2 +- crates/jrsonnet-evaluator/src/val.rs | 7 ++ crates/jrsonnet-stdlib/src/lib.rs | 3 + crates/jrsonnet-stdlib/src/std.jsonnet | 16 ---- crates/jrsonnet-stdlib/src/strings.rs | 58 ++++++++++++-- tests/Cargo.toml | 2 + tests/golden/builtin_strings_string.jsonnet | 21 +++++ .../builtin_strings_string.jsonnet.golden | 17 ++++ tests/tests/golden.rs | 32 +++++++- 12 files changed, 204 insertions(+), 68 deletions(-) create mode 100644 .editorconfig create mode 100644 tests/golden/builtin_strings_string.jsonnet create mode 100644 tests/golden/builtin_strings_string.jsonnet.golden diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..8ab4cdfe --- /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 diff --git a/Cargo.lock b/Cargo.lock index 05039bdd..863c2514 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ dependencies = [ [[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 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[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 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.64", ] [[package]] @@ -257,6 +257,12 @@ dependencies = [ "syn 1.0.109", ] +[[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" @@ -289,9 +295,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" [[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 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[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 @@ checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[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 @@ version = "0.5.0-pre96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.64", ] [[package]] @@ -607,6 +613,17 @@ dependencies = [ "peg", ] +[[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" @@ -624,9 +641,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[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 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[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 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn 2.0.61", + "syn 2.0.64", ] [[package]] @@ -989,22 +1006,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[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 @@ dependencies = [ [[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 @@ dependencies = [ "jrsonnet-evaluator", "jrsonnet-gcmodule", "jrsonnet-stdlib", + "json-structural-diff", "serde", + "serde_json", ] [[package]] @@ -1160,22 +1179,22 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[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 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.64", ] diff --git a/Cargo.toml b/Cargo.toml index 2ace9a6e..0c45205d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,8 +44,8 @@ serde_json = "1.0.114" 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 @@ bincode = "1.3" # 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 @@ proc-macro2 = "1.0" 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" diff --git a/crates/jrsonnet-evaluator/src/arr/mod.rs b/crates/jrsonnet-evaluator/src/arr/mod.rs index 17036a20..b83a3e95 100644 --- a/crates/jrsonnet-evaluator/src/arr/mod.rs +++ b/crates/jrsonnet-evaluator/src/arr/mod.rs @@ -11,7 +11,7 @@ pub use spec::{ArrayLike, *}; /// Represents a Jsonnet array value. #[derive(Debug, Clone, Trace)] -// may contrain other ArrValue +// may contain other ArrValue #[trace(tracking(force))] pub struct ArrValue(Cc>); diff --git a/crates/jrsonnet-evaluator/src/val.rs b/crates/jrsonnet-evaluator/src/val.rs index a7412e5e..bae2088f 100644 --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -220,6 +220,13 @@ pub enum IndexableVal { 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()), diff --git a/crates/jrsonnet-stdlib/src/lib.rs b/crates/jrsonnet-stdlib/src/lib.rs index a0f51f8c..f183c843 100644 --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -201,6 +201,9 @@ pub fn stdlib_uncached(settings: Rc>) -> ObjValue { ("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), diff --git a/crates/jrsonnet-stdlib/src/std.jsonnet b/crates/jrsonnet-stdlib/src/std.jsonnet index 6f793848..020b69b2 100644 --- 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)) diff --git a/crates/jrsonnet-stdlib/src/strings.rs b/crates/jrsonnet-stdlib/src/strings.rs index 4efdb0cb..656d1c57 100644 --- 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 @@ pub fn builtin_bigint(v: Either![f64, IStr]) -> Result { }) } +#[builtin] +pub fn builtin_string_chars(str: IStr) -> ArrValue { + ArrValue::chars(str.chars()) +} + +#[builtin] +pub fn builtin_lstrip_chars(str: IStr, chars: IndexableVal) -> Result { + 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 { + 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 { + 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 bool> { + let chars: BTreeSet = 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::>()?, + }; + + Ok(move |char| chars.contains(&char)) +} + #[cfg(test)] mod tests { use super::*; @@ -243,8 +292,3 @@ mod tests { assert_eq!(parse_nat::<16>("BbC").unwrap(), 0xBBC as f64); } } - -#[builtin] -pub fn builtin_string_chars(str: IStr) -> ArrValue { - ArrValue::chars(str.chars()) -} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index fb4d5f54..3c3fa3a3 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -12,3 +12,5 @@ jrsonnet-evaluator.workspace = true jrsonnet-gcmodule.workspace = true jrsonnet-stdlib.workspace = true serde.workspace = true +json-structural-diff.workspace = true +serde_json.workspace = true diff --git a/tests/golden/builtin_strings_string.jsonnet b/tests/golden/builtin_strings_string.jsonnet new file mode 100644 index 00000000..8464de5f --- /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]]), +} diff --git a/tests/golden/builtin_strings_string.jsonnet.golden b/tests/golden/builtin_strings_string.jsonnet.golden new file mode 100644 index 00000000..47ef53c8 --- /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 diff --git a/tests/tests/golden.rs b/tests/tests/golden.rs index 0ec1379f..976dbaf2 100644 --- a/tests/tests/golden.rs +++ b/tests/tests/golden.rs @@ -9,7 +9,6 @@ use jrsonnet_evaluator::{ FileImportResolver, State, }; use jrsonnet_stdlib::StateExt; - mod common; fn run(file: &Path) -> String { @@ -35,6 +34,8 @@ fn run(file: &Path) -> String { #[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 @@ fn test() -> io::Result<()> { } 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,