From 5d132b93f296b9f8e5d549225f51f9d8995215cd Mon Sep 17 00:00:00 2001 From: Nico Wagner Date: Fri, 7 Jul 2023 11:22:09 +0200 Subject: [PATCH] Add `!^` and `!$` operator (#644) --- CHANGELOG.md | 1 + README.md | 9 ++-- pica-matcher/src/common.rs | 32 ++++++++++---- pica-matcher/src/subfield_matcher.rs | 44 +++++++++++++++++-- ...tion-matcher-curly-starts-not-with-f.stdin | 1 + ...ation-matcher-curly-starts-not-with-f.toml | 5 +++ ...tion-matcher-curly-starts-not-with-t.stdin | 1 + ...ion-matcher-curly-starts-not-with-t.stdout | 1 + ...ation-matcher-curly-starts-not-with-t.toml | 4 ++ ...ion-matcher-simple-starts-not-with-f.stdin | 1 + ...tion-matcher-simple-starts-not-with-f.toml | 4 ++ ...ion-matcher-simple-starts-not-with-t.stdin | 1 + ...on-matcher-simple-starts-not-with-t.stdout | 1 + ...tion-matcher-simple-starts-not-with-t.toml | 5 +++ 14 files changed, 93 insertions(+), 17 deletions(-) create mode 120000 tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-f.stdin create mode 100644 tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-f.toml create mode 120000 tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.stdin create mode 120000 tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.stdout create mode 100644 tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.toml create mode 120000 tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-f.stdin create mode 100644 tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-f.toml create mode 120000 tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.stdin create mode 120000 tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.stdout create mode 100644 tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 6955d5032..80b6f0b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #637 Stabilize `print` command * #641 Stabilize `sample` command * #642 Add `--squash` and `--merge` option +* #644 Add `!^` and `!$` operator ### Changed diff --git a/README.md b/README.md index e967c5cfd..fc77e888f 100644 --- a/README.md +++ b/README.md @@ -126,10 +126,11 @@ between `/01` and `/10`. Simple subfield filter consists of the subfield code (single alpha-numerical character, ex `0`) a comparison operator (equal `==`, -not equal `!=` not equal, starts with prefix `=^`, ends with suffix -`=$`, regex `=~`/`!~`, `in` and `not in`) and a value enclosed in single -quotes. These simple subfield expressions can be grouped in parentheses -and combined with boolean connectives (ex. `(0 == 'abc' || 0 == 'def')`). +not equal `!=` not equal, starts with prefix `=^`, starts not with +prefix `!^`, ends with suffix `=$`, regex `=~`/`!~`, `in` and `not in`) +and a value enclosed in single quotes. These simple subfield expressions +can be grouped in parentheses and combined with boolean connectives (ex. +`(0 == 'abc' || 0 == 'def')`). A special existence operator can be used to check if a given field (`012A/00?`) or a subfield (`002@.0?` or `002@$0?`) exists. To test for diff --git a/pica-matcher/src/common.rs b/pica-matcher/src/common.rs index 54d13e71c..e38d46b4c 100644 --- a/pica-matcher/src/common.rs +++ b/pica-matcher/src/common.rs @@ -29,15 +29,17 @@ where /// Relational Operator #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum RelationalOp { - Eq, // equal, "==" - Ne, // not equal, "!=" - Gt, // greater than, ">" - Ge, // greater than or equal, ">=" - Lt, // less than, "<" - Le, // less than or equal, "<=" - StartsWith, // starts with, "=^" - EndsWith, // ends with, "=$" - Similar, // similar, "=*" + Eq, // equal, "==" + Ne, // not equal, "!=" + Gt, // greater than, ">" + Ge, // greater than or equal, ">=" + Lt, // less than, "<" + Le, // less than or equal, "<=" + StartsWith, // starts with, "=^" + StartsNotWith, // starts not with, "!^" + EndsWith, // ends with, "=$" + EndsNotWith, // ends not with, "!$" + Similar, // similar, "=*" } impl Display for RelationalOp { @@ -50,7 +52,9 @@ impl Display for RelationalOp { RelationalOp::Lt => write!(f, "<"), RelationalOp::Le => write!(f, "<="), RelationalOp::StartsWith => write!(f, "=^"), + RelationalOp::StartsNotWith => write!(f, "!^"), RelationalOp::EndsWith => write!(f, "=$"), + RelationalOp::EndsNotWith => write!(f, "!$"), RelationalOp::Similar => write!(f, "=*"), } } @@ -64,7 +68,9 @@ pub(crate) fn parse_relational_op_str( value(RelationalOp::Eq, tag("==")), value(RelationalOp::Ne, tag("!=")), value(RelationalOp::StartsWith, tag("=^")), + value(RelationalOp::StartsNotWith, tag("!^")), value(RelationalOp::EndsWith, tag("=$")), + value(RelationalOp::EndsNotWith, tag("!$")), value(RelationalOp::Similar, tag("=*")), ))(i) } @@ -216,10 +222,18 @@ mod tests { parse_relational_op_str(b"=^"), RelationalOp::StartsWith ); + assert_finished_and_eq!( + parse_relational_op_str(b"!^"), + RelationalOp::StartsNotWith + ); assert_finished_and_eq!( parse_relational_op_str(b"=$"), RelationalOp::EndsWith ); + assert_finished_and_eq!( + parse_relational_op_str(b"!$"), + RelationalOp::EndsNotWith + ); assert_finished_and_eq!( parse_relational_op_str(b"=*"), RelationalOp::Similar diff --git a/pica-matcher/src/subfield_matcher.rs b/pica-matcher/src/subfield_matcher.rs index 95e94ba02..f57ea36f5 100644 --- a/pica-matcher/src/subfield_matcher.rs +++ b/pica-matcher/src/subfield_matcher.rs @@ -211,12 +211,19 @@ impl RelationMatcher { &self, value: &[u8], options: &MatcherOptions, + invert: bool, ) -> bool { - if options.case_ignore { + let mut result = if options.case_ignore { value.to_lowercase().starts_with(&self.value.to_lowercase()) } else { value.starts_with(&self.value) + }; + + if invert { + result = !result } + + result } /// Returns `true` if the given values is a suffix of the matcher's @@ -226,12 +233,19 @@ impl RelationMatcher { &self, value: &[u8], options: &MatcherOptions, + invert: bool, ) -> bool { - if options.case_ignore { + let mut result = if options.case_ignore { value.to_lowercase().ends_with(&self.value.to_lowercase()) } else { value.ends_with(&self.value) + }; + + if invert { + result = !result; } + + result } /// Returns `true` if the given value is similar to the matcher's @@ -279,10 +293,16 @@ impl Matcher for RelationMatcher { RelationalOp::Eq => self.compare(value, options), RelationalOp::Ne => !self.compare(value, options), RelationalOp::StartsWith => { - self.starts_with(value, options) + self.starts_with(value, options, false) + } + RelationalOp::StartsNotWith => { + self.starts_with(value, options, true) } RelationalOp::EndsWith => { - self.ends_with(value, options) + self.ends_with(value, options, false) + } + RelationalOp::EndsNotWith => { + self.ends_with(value, options, true) } RelationalOp::Similar => { self.is_similar(value, options) @@ -877,6 +897,14 @@ mod tests { value: "abc".into() } ); + assert_finished_and_eq!( + parse_relation_matcher(b"0 !^ 'T'"), + RelationMatcher { + codes: vec!['0'], + op: RelationalOp::StartsNotWith, + value: "T".into() + } + ); assert_finished_and_eq!( parse_relation_matcher(b"0 =$ 'abc'"), RelationMatcher { @@ -885,6 +913,14 @@ mod tests { value: "abc".into() } ); + assert_finished_and_eq!( + parse_relation_matcher(b"0 !$ 'z'"), + RelationMatcher { + codes: vec!['0'], + op: RelationalOp::EndsNotWith, + value: "z".into() + } + ); assert_finished_and_eq!( parse_relation_matcher(b"0 =* 'abc'"), RelationMatcher { diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-f.stdin b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-f.stdin new file mode 120000 index 000000000..fcb85a602 --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-f.stdin @@ -0,0 +1 @@ +../data/algebra.dat \ No newline at end of file diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-f.toml b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-f.toml new file mode 100644 index 000000000..80d75275f --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-f.toml @@ -0,0 +1,5 @@ +bin.name = "pica" +args = "filter \"002@{ 0 !^ 'T' }\"" +status = "success" +stdout = "" +stderr = "" diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.stdin b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.stdin new file mode 120000 index 000000000..fcb85a602 --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.stdin @@ -0,0 +1 @@ +../data/algebra.dat \ No newline at end of file diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.stdout b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.stdout new file mode 120000 index 000000000..fcb85a602 --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.stdout @@ -0,0 +1 @@ +../data/algebra.dat \ No newline at end of file diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.toml b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.toml new file mode 100644 index 000000000..31c47cd77 --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-curly-starts-not-with-t.toml @@ -0,0 +1,4 @@ +bin.name = "pica" +args = "filter \"002@{ 0 !^ 'A' }\"" +status = "success" +stderr = "" diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-f.stdin b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-f.stdin new file mode 120000 index 000000000..fcb85a602 --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-f.stdin @@ -0,0 +1 @@ +../data/algebra.dat \ No newline at end of file diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-f.toml b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-f.toml new file mode 100644 index 000000000..1886a9348 --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-f.toml @@ -0,0 +1,4 @@ +bin.name = "pica" +args = "filter \"002@.0 !^ 'A'\"" +status = "success" +stderr = "" diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.stdin b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.stdin new file mode 120000 index 000000000..fcb85a602 --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.stdin @@ -0,0 +1 @@ +../data/algebra.dat \ No newline at end of file diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.stdout b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.stdout new file mode 120000 index 000000000..fcb85a602 --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.stdout @@ -0,0 +1 @@ +../data/algebra.dat \ No newline at end of file diff --git a/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.toml b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.toml new file mode 100644 index 000000000..be64c597a --- /dev/null +++ b/tests/snapshot/filter/0113-filter-relation-matcher-simple-starts-not-with-t.toml @@ -0,0 +1,5 @@ +bin.name = "pica" +args = "filter \"002@.0 !^ 'T'\"" +status = "success" +stdout = "" +stderr = ""