diff --git a/Cargo.lock b/Cargo.lock index 47a83ef..16ad390 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "block-buffer" version = "0.10.3" @@ -26,12 +20,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -81,19 +69,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "env_logger" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" -dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] - [[package]] name = "erased-serde" version = "0.3.24" @@ -103,27 +78,6 @@ dependencies = [ "serde", ] -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -134,40 +88,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "hermit-abi" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856b5cb0902c2b6d65d5fd97dfa30f9b70c7538e770b98eab5ed52d8db923e01" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "io-lifetimes" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "is-terminal" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", -] - [[package]] name = "itoa" version = "1.0.5" @@ -179,8 +99,6 @@ name = "jetro" version = "0.1.0" dependencies = [ "dynfmt", - "env_logger", - "log", "pest", "pest_derive", "serde", @@ -199,21 +117,6 @@ version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - [[package]] name = "memchr" version = "2.5.0" @@ -305,20 +208,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "rustix" -version = "0.36.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "ryu" version = "1.0.12" @@ -378,15 +267,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.38" @@ -430,100 +310,3 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml index d6b4d60..045b35d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,5 @@ exclude = [".gitignore"] serde_json = "1.0.91" pest = "2.5.4" pest_derive = "2.5.4" -log = "0.4" -env_logger = "0.10.0" serde = { version = "1.0.152", features = ["derive"] } dynfmt = { version = "0.1.5", features = ["curly"] } diff --git a/README.md b/README.md index d82ea2b..c91e480 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,32 @@ Jetro is a tool for transforming, querying and comparing data in JSON format. +```rust +let data = serde_json::json!({ + "name": "mr snuggle", + "some_entry": { + "some_obj": { + "obj": { + "a": "object_a", + "b": "object_b", + "c": "object_c", + "d": "object_d" + } + } + } +}); + +let mut values = Path::collect(data, ">/..obj/#pick('a','b')"); + +#[derive(Serialize, Deserialize)] +struct Output { + a: String, + b: String, +} + +let output: Option = values.from_index(0); +``` + # example ```json @@ -38,7 +64,7 @@ Jetro is a tool for transforming, querying and comparing data in JSON format. ### Queries -```json +``` >/bar ```
@@ -65,8 +91,8 @@ Jetro is a tool for transforming, querying and comparing data in JSON format. --- -```json ->/pick('foo', >/..person/formats('Herrn {} {}', 'firstname', 'lastname') as 'fullname'/fullname as 'fullname') +``` +>/#pick('foo', >/..person/#formats('Herrn {} {}', 'firstname', 'lastname') as 'fullname'/fullname as 'fullname') ```
@@ -95,8 +121,8 @@ Jetro is a tool for transforming, querying and comparing data in JSON format. --- -```json ->/pick('friend', >/..person/formats('Herrn {} {}', 'firstname', 'lastname') as 'fullname'/fullname as 'fullname', >/foo/..contract) +``` +>/#pick('friend', >/..person/#formats('Herrn {} {}', 'firstname', 'lastname') as 'fullname'/fullname as 'fullname', >/foo/..contract) ```
@@ -136,7 +162,7 @@ Jetro is a tool for transforming, querying and comparing data in JSON format. --- -```json +``` >/..meows/[4:] ``` @@ -152,3 +178,23 @@ Jetro is a tool for transforming, querying and comparing data in JSON format. ] ```
+ +--- + +``` +>/..foo/..contract/#pick('kind' as 'contract', + See output + + ### result + +```json +{ + "contract": "Furry Purr", + "welcome_message": "Welcome Mio" +} + +``` +
diff --git a/src/context.rs b/src/context.rs index bb38128..6597e0d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -25,9 +25,16 @@ pub enum FormatOp { pub enum PickFilterInner { None, Str(String), - KeyedStr { key: String, alias: String }, - Subpath(Vec), - KeyedSubpath { subpath: Vec, alias: String }, + KeyedStr { + key: String, + alias: String, + }, + Subpath(Vec, bool), + KeyedSubpath { + subpath: Vec, + alias: String, + reverse: bool, + }, } #[derive(Debug, PartialEq)] @@ -42,6 +49,9 @@ pub enum Filter { ArrayFrom(usize), ArrayTo(usize), Slice(usize, usize), + All, + Len, + Sum, } #[allow(dead_code)] @@ -52,6 +62,7 @@ struct StackItem<'a> { } pub struct Context<'a> { + root: Rc, stack: Rc>>>, pub results: Rc>>>, } @@ -168,7 +179,7 @@ macro_rules! match_value { } impl Filter { - fn pick(&self, obj: &Value) -> Option { + fn pick(&self, obj: &Value, ctx: Option<&Context>) -> Option { let target_key = self.key(); let descendant_key = target_key.unwrap_or("descendant".to_string()); match &self { @@ -202,10 +213,31 @@ impl Filter { PickFilterInner::KeyedSubpath { subpath: ref some_subpath, alias: ref some_alias, - } => match_value!(obj, new_map, some_alias, some_subpath), + reverse, + } => { + match_value!( + if *reverse && ctx.is_some() { + ctx.unwrap().root.as_ref() + } else { + obj + }, + new_map, + some_alias, + some_subpath + ); + } - PickFilterInner::Subpath(ref some_subpath) => { - match_value!(obj, new_map, descendant_key, some_subpath) + PickFilterInner::Subpath(ref some_subpath, reverse) => { + match_value!( + if *reverse && ctx.is_some() { + ctx.unwrap().root.as_ref() + } else { + obj + }, + new_map, + descendant_key, + some_subpath + ) } _ => {} @@ -246,10 +278,13 @@ impl Path { } impl PathResult { - pub fn shove(&mut self, index: usize) -> T { + pub fn from_index(&mut self, index: usize) -> Option { let final_value: &Value = &*self.0.borrow_mut().remove(index); - serde_json::from_value(final_value.clone().take()).unwrap() + match serde_json::from_value(final_value.clone().take()) { + Ok(result) => Some(result), + _ => None, + } } } @@ -284,7 +319,11 @@ impl<'a> Context<'a> { .borrow_mut() .push(StackItem::new(rv.clone(), filters, Rc::clone(&stack))); - Self { stack, results } + Self { + root: rv.clone(), + stack, + results, + } } pub fn collect(&mut self) { @@ -303,6 +342,127 @@ impl<'a> Context<'a> { self.stack.clone(), )), + (Filter::Len, Some(tail)) => match *current.value { + Value::Object(ref obj) => { + push_to_stack_or_produce!( + self.results, + self.stack, + tail, + Value::Number(serde_json::Number::from(obj.len())) + ); + } + Value::Array(ref array) => { + push_to_stack_or_produce!( + self.results, + self.stack, + tail, + Value::Number(serde_json::Number::from(array.len())) + ); + } + _ => {} + }, + + (Filter::Sum, Some(tail)) => match *current.value { + Value::Object(ref _obj) => {} + Value::Array(ref array) => { + let mut sum: i64 = 0; + let values = self.results.to_owned(); + self.results = Rc::new(RefCell::new(Vec::new())); + for value in values.borrow().clone() { + match *value.as_ref() { + Value::Array(ref inner_array) => { + for v in inner_array { + if v.is_number() { + sum += v.as_i64().unwrap(); + } + } + } + Value::Number(ref num) => { + sum += num.as_i64().unwrap(); + } + _ => {} + } + } + for value in array { + if value.is_number() { + sum += value.as_i64().unwrap(); + } + } + push_to_stack_or_produce!( + self.results, + self.stack, + tail, + Value::Number(serde_json::Number::from(sum)) + ); + } + _ => {} + }, + + (Filter::All, Some(tail)) => match *current.value { + Value::Object(ref _obj) => {} + Value::Bool(ref value) => { + let mut all = true; + let values = self.results.to_owned(); + self.results = Rc::new(RefCell::new(Vec::new())); + for value in values.borrow().clone() { + match *value.as_ref() { + Value::Array(ref inner_array) => { + for v in inner_array { + if v.is_boolean() { + all = all & v.as_bool().unwrap() == true; + } + } + } + Value::Bool(ref value) => { + all = *value == false; + } + _ => {} + } + } + + all = all & (*value == true); + + push_to_stack_or_produce!( + self.results, + self.stack, + tail, + Value::Bool(all) + ); + } + Value::Array(ref array) => { + let mut all = true; + let values = self.results.to_owned(); + self.results = Rc::new(RefCell::new(Vec::new())); + for value in values.borrow().clone() { + match *value.as_ref() { + Value::Array(ref inner_array) => { + for v in inner_array { + if v.is_boolean() { + all = v.as_bool().unwrap() == false; + } + } + } + Value::Bool(ref value) => { + all = *value == false; + } + _ => {} + } + } + for value in array { + if value.is_boolean() { + all = value.as_bool().unwrap() == false; + } + } + push_to_stack_or_produce!( + self.results, + self.stack, + tail, + Value::Bool(all) + ); + } + _ => {} + }, + (Filter::Format(ref target_format), Some(tail)) => match *current.value { Value::Object(ref obj) => { let FormatOp::FormatString { @@ -437,10 +597,17 @@ impl<'a> Context<'a> { (Filter::Pick(_), Some(tail)) => match *current.value { Value::Object(_) => { - let new_map = current.filters[0].pick(¤t.value).unwrap(); + let new_map = current.filters[0] + .pick(¤t.value, Some(&self)) + .unwrap(); push_to_stack_or_produce!(self.results, self.stack, tail, new_map); } - Value::Array(_) => {} + Value::Array(_) => { + let new_array = current.filters[0] + .pick(¤t.value, Some(&self)) + .unwrap(); + push_to_stack_or_produce!(self.results, self.stack, tail, new_array); + } _ => {} }, @@ -471,6 +638,14 @@ impl<'a> Context<'a> { } _ => {} }, + Value::Number(ref num) => { + push_to_stack_or_produce!( + self.results, + self.stack, + tail, + Value::Number(num.clone()) + ); + } _ => {} }, @@ -540,7 +715,7 @@ mod test { PickFilterInner::Str("b".to_string()), ]); let v = v.get("obj".to_string()).unwrap(); - let result = f.pick(&v); + let result = f.pick(&v, None); assert_eq!(result.is_some(), true); assert_eq!( @@ -596,26 +771,23 @@ mod test { #[test] fn test_pick() { - let data = r#" - { - "name": "mr snuggle", - "some_entry": { - "some_obj": { - "obj": { - "a": "object_a", - "b": "object_b", - "c": "object_c", - "d": "object_d" - } + let data = serde_json::json!( + { + "name": "mr snuggle", + "some_entry": { + "some_obj": { + "obj": { + "a": "object_a", + "b": "object_b", + "c": "object_c", + "d": "object_d" + } } } } - "#; + ); - let mut values = Path::collect( - serde_json::from_str(&data).unwrap(), - ">/..obj/pick('a','b')", - ); + let mut values = Path::collect(data, ">/..obj/#pick('a','b')"); #[derive(Serialize, Deserialize)] pub struct Output { @@ -623,7 +795,10 @@ mod test { pub b: String, } - let output: Output = values.shove(0); + let result: Option = values.from_index(0); + assert_eq!(result.is_some(), true); + + let output = result.unwrap(); assert_eq!(*&output.a, "object_a".to_string()); assert_eq!(*&output.b, "object_b".to_string()); } @@ -648,7 +823,7 @@ mod test { let values = Path::collect( serde_json::from_str(&data).unwrap(), - ">/..obj/pick('a', >/..with_nested/pick('object'))", + ">/..obj/#pick('a', >/..with_nested/#pick('object'))", ); assert_eq!( @@ -679,7 +854,7 @@ mod test { let values = Path::collect( serde_json::from_str(&data).unwrap(), - ">/..obj/pick('a' as 'foo', >/..object)", + ">/..obj/#pick('a' as 'foo', >/..object)", ); assert_eq!( @@ -695,7 +870,8 @@ mod test { let data = r#" { "name": "mr snuggle", - "values": [1,2,3,4,5,6] + "values": [1,2,3,4,5,6, {"isEnabled": true}], + "some_value": {"isEnabled": true} } "#; @@ -711,7 +887,7 @@ mod test { fn test_format_impl() { let data = serde_json::json!({ "name": "mr snuggle", - "alias": "jetro" + "alias": "jetro", }); let keys = vec!["name".to_string(), "alias".to_string()]; diff --git a/src/grammar.pest b/src/grammar.pest index 5e118a2..31835e4 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -6,13 +6,23 @@ special_charaters = _{ "_" | "-" | "\\" } whitespc = _{ " "* } lparen = _{"("} rparen = _{")"} +at = _{ "@" } path = { ">" } +reverse_path = { "<" } asterisk = _{ "*" } slash = { "/" } double_dot = _{ ".." } ident = { (alpha | digit | special_charaters)+ } _as = _{ "as" } as = { (" ")* ~ _as ~ (" ")* ~ literal } +sharp = _{ "#" } +dot = _{ "." } +greater = { ">" } +less = { "<" } +greater_equal = { ">=" } +less_equal = { "<=" } +equal = { "==" } +cmp = { equal | greater | less | greater_equal | less_equal } child = { slash ~ ident } any_child = { slash ~ asterisk } @@ -21,19 +31,36 @@ array_index = { slash ~ "[" ~ number ~ "]" } slice = { slash ~ "[" ~ number ~ ":" ~ number ~ "]" } array_to = { slash ~ "[:" ~ number ~ "]" } array_from = { slash ~ "[" ~ number ~ ":]" } -pick = { "pick" ~ (" ")* ~ "(" ~ (literal_keyed | sub_expression_keyed ) ~ ((",") ~ (" ")* ~ (literal_keyed | sub_expression_keyed ))* ~ (" ")* ~ ")" } -squash = { "squash" ~ "(" ~ ")" } +pick = { + sharp ~ "pick" ~ (" ")* ~ + "(" ~ (literal_keyed | (sub_expression_keyed | sub_expression_keyed_reversed) ) ~ + ((",") ~ (" ")* ~ (literal_keyed | (sub_expression_keyed | sub_expression_keyed_reversed) ))* ~ + (" ")* ~ ")" +} +squash = { sharp ~ "squash" ~ "(" ~ ")" } +sum = { sharp ~ "sum" } +len = { sharp ~ "len" } +all = { sharp ~ "all" } +allFn = { slash ~ all } +lenFn = { slash ~ len } +sumFn = { slash ~ sum } squashFn = { slash ~ squash } pickFn = { slash ~ pick } -formats = { "formats" ~ whitespc ~ lparen ~ literal ~ ((",")* ~ whitespc ~ literal)+ ~ whitespc ~ rparen ~ whitespc ~ as} +formats = { sharp ~ "formats" ~ whitespc ~ lparen ~ literal ~ ((",")* ~ whitespc ~ literal)+ ~ whitespc ~ rparen ~ whitespc ~ as} formatsFn = { slash ~ formats } +filter = { sharp ~ "filter" ~ lparen ~ whitespc ~ ( filter_elem ~ whitespc ~ cmp ~ whitespc ~ literal ) ~ whitespc ~ rparen } +filter_elem = { literal } +filterFn = { slash ~ filter } -sub_expression = { path ~ (formatsFn | pickFn | child | any_child | descendant_child | array_index | slice | array_to | array_from)* } +sub_expression = { path ~ (formatsFn | lenFn | allFn | pickFn | sumFn | filterFn | child | any_child | descendant_child | array_index | slice | array_to | array_from)* } +sub_expression_reversed = { reverse_path ~ (formatsFn | lenFn | allFn | pickFn | sumFn | filterFn | child | any_child | descendant_child | array_index | slice | array_to | array_from)* } sub_expression_keyed = { sub_expression ~ as? } -string = { (alpha | digit | special_charaters | " " | "%" | "{" | "}" | "\\" )+ } +sub_expression_keyed_reversed = { sub_expression_reversed ~ as? } +string = { (alpha | digit | special_charaters | " " | "%" | "{" | "}" | "\\" )+ ~ (whitespc ~ string)* } literal = { "'" ~ string ~ "'" } literal_keyed = { literal ~ as? } + array = { "[" ~ (" ")* ~ @@ -47,7 +74,7 @@ array = { } expression = { - path ~ - (formatsFn | pickFn | child | any_child | descendant_child | array_index | slice | array_to | array_from)* ~ + (path|reverse_path) ~ + (formatsFn | lenFn | allFn | pickFn | sumFn | filterFn | child | any_child | descendant_child | array_index | slice | array_to | array_from)* ~ EOI } \ No newline at end of file diff --git a/src/parser.rs b/src/parser.rs index e46f2b7..af56668 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -13,7 +13,10 @@ pub fn parse<'a>(input: &'a str) -> Vec { let mut actions: Vec = vec![]; for token in root.into_inner() { match token.as_rule() { - Rule::path => actions.push(Filter::Root), + Rule::path | Rule::reverse_path => actions.push(Filter::Root), + Rule::allFn => actions.push(Filter::All), + Rule::lenFn => actions.push(Filter::Len), + Rule::sumFn => actions.push(Filter::Sum), Rule::formatsFn => { let mut arguments: Vec = vec![]; let mut elem = token.into_inner().nth(1).unwrap().into_inner(); @@ -89,7 +92,8 @@ pub fn parse<'a>(input: &'a str) -> Vec { .unwrap() .into_inner() .map(|v| match &v.as_rule() { - Rule::sub_expression_keyed => { + Rule::sub_expression_keyed | Rule::sub_expression_keyed_reversed => { + let reverse = *&v.as_rule() == Rule::sub_expression_keyed_reversed; let mut l = v.into_inner(); let subexpr = l.next().unwrap().as_str(); let alias: Option = match l.next() { @@ -108,15 +112,19 @@ pub fn parse<'a>(input: &'a str) -> Vec { return PickFilterInner::KeyedSubpath { subpath: parse(subexpr), alias, + reverse, }; } None => { - return PickFilterInner::Subpath(parse(subexpr)); + return PickFilterInner::Subpath(parse(subexpr), reverse); } } } Rule::sub_expression => { - return PickFilterInner::Subpath(parse(v.as_str())); + return PickFilterInner::Subpath(parse(v.as_str()), false); + } + Rule::sub_expression_reversed => { + return PickFilterInner::Subpath(parse(v.as_str()), true); } Rule::literal_keyed => { let mut l = v.into_inner(); @@ -205,7 +213,7 @@ mod test { #[test] fn test_three() { - let actions = parse(">/obj/some/..descendant/pick('a', 'b', 'c', 'd')"); + let actions = parse(">/obj/some/..descendant/#pick('a', 'b', 'c', 'd')"); assert_eq!( actions, vec![ @@ -225,7 +233,7 @@ mod test { #[test] fn test_with_keyed_literal() { - let actions = parse(">/obj/some/..descendant/pick('f' as 'foo', 'b' as 'bar')"); + let actions = parse(">/obj/some/..descendant/#pick('f' as 'foo', 'b' as 'bar')"); assert_eq!( actions, vec![ @@ -249,7 +257,36 @@ mod test { #[test] fn test_with_sub_expression_keyed() { - let actions = parse(">/obj/some/..descendant/pick('f' as 'foo', >/some/branch as 'path')"); + let actions = parse(">/obj/some/..descendant/#pick('f' as 'foo', >/some/branch as 'path')"); + assert_eq!( + actions, + vec![ + Filter::Root, + Filter::Child("obj".to_string()), + Filter::Child("some".to_string()), + Filter::Descendant("descendant".to_string()), + Filter::Pick(vec![ + PickFilterInner::KeyedStr { + key: "f".to_string(), + alias: "foo".to_string() + }, + PickFilterInner::KeyedSubpath { + subpath: vec![ + Filter::Root, + Filter::Child("some".to_string()), + Filter::Child("branch".to_string()), + ], + alias: "path".to_string(), + reverse: false, + }, + ]), + ] + ); + } + + #[test] + fn test_with_sub_expression_keyed_reverse() { + let actions = parse(">/obj/some/..descendant/#pick('f' as 'foo', /formats('{}{}', 'name', 'alias') as 'some_key'"); + let actions = parse(">/#formats('{}{}', 'name', 'alias') as 'some_key'"); assert_eq!( actions, vec![