From 488a209f19ceac90317092f34e06bdd569ff980f Mon Sep 17 00:00:00 2001 From: Barnaby Shearer Date: Fri, 18 Oct 2019 14:31:14 +0000 Subject: [PATCH] Allow use of maths symbols for operators (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow use of maths symbols for operators ≤≠≥∈∉ map to existing operators ⊆⊇∩ non-strict subset, superset, and intersection provides new functionality; resolves #1 --- boolrule/boolrule.py | 19 +++++++++++------ tests/test_boolrule.py | 48 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/boolrule/boolrule.py b/boolrule/boolrule.py index f56f331..6ff3a76 100644 --- a/boolrule/boolrule.py +++ b/boolrule/boolrule.py @@ -48,7 +48,8 @@ def __repr__(self): rparen = Suppress(')') binaryOp = oneOf( - "= == != < > >= <= eq ne lt le gt ge in notin is isnot", caseless=True + "= == != < > >= <= eq ne lt le gt ge in notin is isnot " + "≠ ≤ ≥ ∈ ∉ ⊆ ⊇ ∩", caseless=True )('operator') E = CaselessLiteral("E") @@ -178,24 +179,30 @@ def _test_tokens(self, tokens, context): if operator in ('=', '==', 'eq'): passed = lval == rval - elif operator in ('!=', 'ne'): + elif operator in ('!=', 'ne', '≠'): passed = lval != rval elif operator in ('>', 'gt'): passed = lval > rval - elif operator in ('>=', 'ge'): + elif operator in ('>=', 'ge', '≥'): passed = lval >= rval elif operator in ('<', 'lt'): passed = lval < rval - elif operator in ('<=', 'le'): + elif operator in ('<=', 'le', '≤'): passed = lval <= rval - elif operator == 'in': + elif operator in ('in', '∈'): passed = lval in rval - elif operator == 'notin': + elif operator in ('notin', '∉'): passed = lval not in rval elif operator == 'is': passed = lval is rval elif operator == 'isnot': passed = lval is not rval + elif operator == '⊆': + passed = all((False for x in lval if x not in rval)) + elif operator == '⊇': + passed = all((False for x in rval if x not in lval)) + elif operator == '∩': + passed = any((True for x in lval if x in rval)) else: raise UnknownOperatorException( "Unknown operator '{}'".format(operator) diff --git a/tests/test_boolrule.py b/tests/test_boolrule.py index 70518c2..d8e79f0 100644 --- a/tests/test_boolrule.py +++ b/tests/test_boolrule.py @@ -9,9 +9,26 @@ @pytest.mark.parametrize('s,expected', [ ('5 > 3', True), ('5 < 3', False), + ('5 > 5', False), + ('3 >= 5', False), + ('5 >= 3', True), + ('5 >= 5', True), + ('5 <= 3', False), + ('3 <= 5', True), + ('3 <= 5', True), + ('5 ≥ 3', True), + ('5 ≥ 5', True), + ('3 ≤ 3', True), + ('3 ≤ 5', True), ('7 == true', False), ('true == true', True), ('None is None', True), + ('1 != 2', True), + ('1 != 1', False), + ('2 != true', True), + ('1 ≠ 2', True), + ('1 ≠ 1', False), + ('2 ≠ true', True), ]) def test_simple_comparisons(s, expected): boolrule = BoolRule(s) @@ -37,7 +54,6 @@ def test_nested_logical_combinations(s, expected): assert boolrule.test() == expected - @pytest.mark.parametrize('s,context,expected', [ ('foo = "bar" AND baz > 10', {'foo': 'bar', 'baz': 20}, True), ('foo = "bar" AND baz > 10', {'foo': 'bar', 'baz': 9}, False), @@ -56,12 +72,41 @@ def test_subsitution_values(s, context, expected): ('x in (5, 6, 7)', {'x': 5}, True), ('x in (5, 6, 7)', {'x': 8}, False), ('x in (5, 6, 7, y)', {'x': 99, 'y': 99}, True), + ('x ∈ (5, 6, 7)', {'x': 5}, True), + ('x ∈ (5, 6, 7)', {'x': 8}, False), + ('x ∈ (5, 6, 7, y)', {'x': 99, 'y': 99}, True), + ('x ∉ (5, 6, 7)', {'x': 5}, False), + ('x ∉ (5, 6, 7)', {'x': 8}, True), + ('x ∉ (5, 6, 7, y)', {'x': 99, 'y': 99}, False), ]) def test_list_membership(s, context, expected): boolrule = BoolRule(s) assert boolrule.test(context) == expected +@pytest.mark.parametrize('s,expected', [ + ('(1, 2, 3) ⊆ (1, 2, 3)', True), + ('(1, 2, 3) ⊇ (1, 2, 3)', True), + ('(1, 2, 3) ⊆ (1, 2, 3, 4)', True), + ('(1, 2, 3, 4) ⊇ (1, 2, 3)', True), + ('(1, 2, 3) ⊆ (1, 2)', False), + ('(1, 2) ⊇ (1, 2, 3)', False), +]) +def test_subset(s, expected): + boolrule = BoolRule(s) + assert boolrule.test() == expected + + +@pytest.mark.parametrize('s,expected', [ + ('(1, 2, 3) ∩ (1, 2, 3)', True), + ('(4) ∩ (3, 4, 5)', True), + ('(1, 2, 3) ∩ (4, 5, 6)', False), +]) +def test_intersects(s, expected): + boolrule = BoolRule(s) + assert boolrule.test() == expected + + @pytest.mark.parametrize('s,context', [ ('foo < bar', None), ('foo < bar', {}), @@ -85,4 +130,3 @@ def test_missing_vars_raises_exception(s, context): # with self.assertRaises(ParseException): # rule = BoolRule(query[0]) # rule.test(query[1]) -