Commit 15874b24 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge pull request #53 from mruwek/filter-priorities

Much more powerful filtering of priorities
parents c1615907 8e2e4fd1
......@@ -374,6 +374,105 @@ class OrdinalTagFilterTest(TopydoTest):
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo3)
class PriorityFilterTest(TopydoTest):
def setUp(self):
super(PriorityFilterTest, self).setUp()
self.todo1 = "(A) Foo"
self.todo2 = "(B) Bar"
self.todo3 = "(C) Baz"
self.todo4 = "(Z) FooBar"
self.todo5 = "FooBaz"
self.todos = [
Todo(self.todo1),
Todo(self.todo2),
Todo(self.todo3),
Todo(self.todo4),
Todo(self.todo5),
]
def test_filter1(self):
pf = Filter.PriorityFilter('(A)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter1a(self):
pf = Filter.PriorityFilter('(=A)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter2(self):
pf = Filter.PriorityFilter('(>B)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter3(self):
pf = Filter.PriorityFilter('(>=C)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 3)
self.assertEqual(result[0].source(), self.todo1)
self.assertEqual(result[1].source(), self.todo2)
self.assertEqual(result[2].source(), self.todo3)
def test_filter4(self):
pf = Filter.PriorityFilter('(<A)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 4)
self.assertEqual(result[0].source(), self.todo2)
self.assertEqual(result[1].source(), self.todo3)
self.assertEqual(result[2].source(), self.todo4)
self.assertEqual(result[3].source(), self.todo5)
def test_filter5(self):
pf = Filter.PriorityFilter('(<=C)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 3)
self.assertEqual(result[0].source(), self.todo3)
self.assertEqual(result[1].source(), self.todo4)
self.assertEqual(result[2].source(), self.todo5)
def test_filter6(self):
pf = Filter.PriorityFilter('(!B)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 4)
self.assertEqual(result[0].source(), self.todo1)
self.assertEqual(result[1].source(), self.todo3)
self.assertEqual(result[2].source(), self.todo4)
self.assertEqual(result[3].source(), self.todo5)
def test_filter7(self):
pf = Filter.PriorityFilter('(>A)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 0)
def test_filter8(self):
pf = Filter.PriorityFilter('(<Z)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo5)
if __name__ == '__main__':
unittest.main()
......@@ -53,6 +53,8 @@ class ExpressionCommand(Command):
for arg in args:
if re.match(Filter.ORDINAL_TAG_MATCH, arg):
argfilter = Filter.OrdinalTagFilter(arg)
elif re.match(Filter.PRIORITY_MATCH, arg):
argfilter = Filter.PriorityFilter(arg)
elif len(arg) > 1 and arg[0] == '-':
# when a word starts with -, exclude it
argfilter = Filter.GrepFilter(arg[1:])
......
......@@ -151,20 +151,52 @@ class LimitFilter(Filter):
def filter(self, p_todos):
return p_todos[:self.limit] if self.limit >= 0 else p_todos
ORDINAL_TAG_MATCH = r"(?P<key>[^:]*):(?P<operator><=?|=|>=?|!)?(?P<value>\S+)"
OPERATOR_MATCH = r"(?P<operator><=?|=|>=?|!)?"
class OrdinalTagFilter(Filter):
def __init__(self, p_expression):
super(OrdinalTagFilter, self).__init__()
class OrdinalFilter(Filter):
"""
Base class for ordinal filters.
"""
def __init__(self, p_expression, p_pattern):
super(OrdinalFilter, self).__init__()
self.expression = p_expression
match = re.match(ORDINAL_TAG_MATCH, self.expression)
match = re.match(p_pattern, self.expression)
if match:
self.key = match.group('key')
try:
self.key = match.group('key')
except IndexError:
pass
self.operator = match.group('operator') or '='
self.value = match.group('value')
def compare_operands(self, p_operand1, p_operand2):
"""
Returns True if conditional constructed from both operands and
self.operator is valid. Returns False otherwise.
"""
if self.operator == '<':
return p_operand1 < p_operand2
elif self.operator == '<=':
return p_operand1 <= p_operand2
elif self.operator == '=':
return p_operand1 == p_operand2
elif self.operator == '>=':
return p_operand1 >= p_operand2
elif self.operator == '>':
return p_operand1 > p_operand2
elif self.operator == '!':
return p_operand1 != p_operand2
return False
ORDINAL_TAG_MATCH = r"(?P<key>[^:]*):" + OPERATOR_MATCH + r"(?P<value>\S+)"
class OrdinalTagFilter(OrdinalFilter):
def __init__(self, p_expression):
super(OrdinalTagFilter, self).__init__(p_expression, ORDINAL_TAG_MATCH)
def match(self, p_todo):
"""
Performs a match on a key:value tag in the todo.
......@@ -199,18 +231,26 @@ class OrdinalTagFilter(Filter):
grep = GrepFilter(self.expression)
return grep.match(p_todo)
if self.operator == '<':
return operand1 < operand2
elif self.operator == '<=':
return operand1 <= operand2
elif self.operator == '=':
return operand1 == operand2
elif self.operator == '>=':
return operand1 >= operand2
elif self.operator == '>':
return operand1 > operand2
elif self.operator == '!':
return operand1 != operand2
return self.compare_operands(operand1, operand2)
return False
PRIORITY_MATCH = r"\(" + OPERATOR_MATCH + r"(?P<value>[A-Z]{1})\)"
class PriorityFilter(OrdinalFilter):
def __init__(self, p_expression):
super(PriorityFilter, self).__init__(p_expression, PRIORITY_MATCH)
def match(self, p_todo):
"""
Performs a match on a priority in the todo.
It gets priority from p_todo and compares it with user-entered
expression based on the given operator (default ==). It does that however
in reversed order to obtain more intuitive result. Example: (>B) will
match todos with priority (A).
Items without priority are designated with corresponding operand set to
'ZZ', because python doesn't allow NoneType() and str() comparisons.
"""
operand1 = self.value
operand2 = p_todo.priority() or 'ZZ'
return self.compare_operands(operand1, operand2)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment