Browse Source

Initial code commit

master
Johann Schmitz 5 years ago
parent
commit
28b3dfddc7
Signed by: ercpe GPG Key ID: A084064277C501ED
  1. 2
      .gitignore
  2. 3
      requirements.txt
  3. 2
      src/phylter/__init__.py
  4. 51
      src/phylter/conditions.py
  5. 83
      src/phylter/parser.py
  6. 108
      tests/test_parser.py

2
.gitignore

@ -59,3 +59,5 @@ docs/_build/
target/
# PyCharm
.idea
src/test.py

3
requirements.txt

@ -1 +1,2 @@
# Project dependencies
# Project dependencies
pyparsing

2
src/phylter/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-

51
src/phylter/conditions.py

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
class Condition(object):
def __init__(self, left, right):
self.left = left
self.right = right
class EqualsCondition(Condition):
def __str__(self):
return "%s == %s" % (self.left, self.right)
class GreaterThanCondition(Condition):
def __str__(self):
return "%s > %s" % (self.left, self.right)
class GreaterThanOrEqualCondition(Condition):
def __str__(self):
return "%s >= %s" % (self.left, self.right)
class LessThanCondition(Condition):
def __str__(self):
return "%s < %s" % (self.left, self.right)
class LessThanOrEqualCondition(Condition):
def __str__(self):
return "%s <= %s" % (self.left, self.right)
class Operator(object):
def __init__(self, left, right):
self.left = left
self.right = right
class AndOperator(Operator):
pass
class OrOperator(Operator):
pass

83
src/phylter/parser.py

@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
import pyparsing
from phylter.conditions import EqualsCondition, GreaterThanCondition, LessThanCondition, GreaterThanOrEqualCondition, \
LessThanOrEqualCondition
field = pyparsing.Word(pyparsing.alphanums)
operator = pyparsing.oneOf(('==', '!=', '>', '<', '>=', '<='))
value = pyparsing.quotedString | pyparsing.Word(pyparsing.alphanums)
#and_or = pyparsing.oneOf(['and', 'or'], caseless=True)
field_op_value = field + operator + value
#andor_field_op_value = and_or + field_op_value
pattern = field_op_value #+ pyparsing.Optional(pyparsing.OneOrMore(andor_field_op_value))
class ConsumableIter(object):
def __init__(self, iterable):
self.iterable = iterable or []
self.length = len(self.iterable)
self.pos = 0
@property
def remaining(self):
return self.length - self.pos
@property
def has_more(self):
return self.remaining > 0
@property
def current(self):
if self.pos >= self.length-1:
return None
return self.iterable[self.pos]
def consume(self, length=1):
if length is None or length < 0 or length > self.length:
raise ValueError("'length' argument must be 0 <= length")
if length > self.remaining:
raise ValueError("Cannot consume more than %s remaining elements" % self.remaining)
if length == 0:
return None
if length == 1:
element = self.iterable[self.pos]
self.pos += length
return element
elements = self.iterable[self.pos:self.pos+length]
self.pos += length
return elements
class Parser(object):
def __init__(self, *args, **kwargs):
pass
def parse(self, query):
chunks = ConsumableIter(pattern.parseString(query, parseAll=True))
while chunks.has_more:
left, operator, right = tuple(chunks.consume(3))
return [self._get_condition_class(operator)(left, right)]
def _get_condition_class(self, operator):
d = {
'==': EqualsCondition,
'>': GreaterThanCondition,
'<': LessThanCondition,
'>=': GreaterThanOrEqualCondition,
'<=': LessThanOrEqualCondition
}
if not operator in d:
raise Exception("Unknown operator '%s'" % operator)
return d[operator]

108
tests/test_parser.py

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
import pytest
from phylter.conditions import EqualsCondition, GreaterThanCondition, GreaterThanOrEqualCondition, LessThanCondition, \
LessThanOrEqualCondition
from phylter.parser import ConsumableIter, Parser
class TestConsumableIter(object):
def test_constructor(self):
for v in (None, []):
ci = ConsumableIter(v)
assert ci.length == 0
assert ci.remaining == 0
ci = ConsumableIter([1, 2, 3])
assert ci.length == 3
assert ci.remaining == 3
def test_current(self):
ci = ConsumableIter([1, 2, 3])
assert ci.current == 1
def test_current_empty(self):
ci = ConsumableIter([])
assert ci.current is None
def test_has_more(self):
ci = ConsumableIter([1, 2, 3])
assert ci.has_more
def test_has_more_empty(self):
ci = ConsumableIter([])
assert not ci.has_more
def test_consume(self):
ci = ConsumableIter([1, 2, 3])
with pytest.raises(ValueError):
ci.consume(None)
ci = ConsumableIter([1, 2, 3])
with pytest.raises(ValueError):
ci.consume(-1)
ci = ConsumableIter([1, 2, 3])
with pytest.raises(ValueError):
ci.consume(100)
ci = ConsumableIter([1, 2, 3])
ci.consume(1)
with pytest.raises(ValueError):
ci.consume(3)
ci = ConsumableIter([1, 2, 3])
assert ci.consume(0) is None
assert ci.consume(1) == 1
assert ci.consume(1) == 2
assert ci.consume(1) == 3
assert ci.remaining == 0
assert not ci.has_more
ci = ConsumableIter([1, 2, 3])
assert ci.consume(3) == [1, 2, 3]
assert ci.remaining == 0
assert not ci.has_more
def test_consume_empty(self):
ci = ConsumableIter([])
with pytest.raises(ValueError):
ci.consume(1)
class TestParser(object):
def test_constructor(self):
p = Parser()
p = Parser('foo')
p = Parser('foo', bar='baz')
def test_get_condition_class(self):
p = Parser()
assert p._get_condition_class('==') == EqualsCondition
assert p._get_condition_class('>') == GreaterThanCondition
assert p._get_condition_class('<') == LessThanCondition
assert p._get_condition_class('>=') == GreaterThanOrEqualCondition
assert p._get_condition_class('<=') == LessThanOrEqualCondition
for x in (None, 'foobar'):
with pytest.raises(Exception) as e:
p._get_condition_class(x)
assert e
def test_parse(self):
for query, left, right, clazz in (
('foo == bar', 'foo', 'bar', EqualsCondition),
('foo > bar', 'foo', 'bar', GreaterThanCondition),
('foo < bar', 'foo', 'bar', LessThanCondition),
('foo >= bar', 'foo', 'bar', GreaterThanOrEqualCondition),
('foo <= bar', 'foo', 'bar', LessThanOrEqualCondition),
):
q = Parser().parse(query)
assert len(q) == 1
assert isinstance(q[0], clazz)
assert q[0].left == left
assert q[0].right == right