Commit 9c2024ea authored by Bram Schoenmakers's avatar Bram Schoenmakers

Add filters for completion date and creation date

Addresses issue #86 which requested filters on completion date. The
following tags are recognized:

* create
* created
* creation
* complete
* completed
* completion

They are specific ordinal tag filters, except that these tags don't
exist in a todo item.

Filtering on completion date does not work when completed items are
automatically archived. Archived items are not read when invoking `ls`,
so the done.txt file should be read as the main file instead:

    topydo -t done.txt ls complete:today

Also in this commit, decouple ExpressionCommand from Filter class a bit.

The expression class shouldn't bother which expressions belong to which
filter classes, let the Filter module handle that.
parent fb445f42
...@@ -379,6 +379,84 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -379,6 +379,84 @@ class OrdinalTagFilterTest(TopydoTest):
self.assertEqual(result[0].source(), self.todo3) self.assertEqual(result[0].source(), self.todo3)
class CreationFilterTest(TopydoTest):
def setUp(self):
super(CreationFilterTest, self).setUp()
self.todo1 = "2015-12-19 With creation date."
self.todo2 = "Without creation date."
self.todos = [Todo(self.todo1), Todo(self.todo2)]
def test_filter1(self):
cf = Filter.CreationFilter('create:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter2(self):
cf = Filter.CreationFilter('creation:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter3(self):
cf = Filter.CreationFilter('created:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
class CompletionFilterTest(TopydoTest):
def setUp(self):
super(CompletionFilterTest, self).setUp()
self.todo1 = "2015-12-19 With creation date."
self.todo2 = "x 2015-12-19 2015-12-18 Without creation date."
self.todo3 = "x 2015-12-18 Without creation date."
self.todos = [Todo(self.todo1), Todo(self.todo2), Todo(self.todo3)]
def test_filter1(self):
cf = Filter.CompletionFilter('complete:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter2(self):
cf = Filter.CompletionFilter('completed:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter3(self):
cf = Filter.CompletionFilter('completion:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter4(self):
cf = Filter.CompletionFilter('completion:<=2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 2)
self.assertEqual(result[0].source(), self.todo2)
self.assertEqual(result[1].source(), self.todo3)
class PriorityFilterTest(TopydoTest): class PriorityFilterTest(TopydoTest):
def setUp(self): def setUp(self):
super(PriorityFilterTest, self).setUp() super(PriorityFilterTest, self).setUp()
......
...@@ -58,11 +58,12 @@ class ExpressionCommand(Command): ...@@ -58,11 +58,12 @@ class ExpressionCommand(Command):
is_negated = len(arg) > 1 and arg[0] == '-' is_negated = len(arg) > 1 and arg[0] == '-'
arg = arg[1:] if is_negated else arg arg = arg[1:] if is_negated else arg
if re.match(Filter.ORDINAL_TAG_MATCH, arg): argfilter = None
argfilter = Filter.OrdinalTagFilter(arg) for match, _filter in Filter.MATCHES:
elif re.match(Filter.PRIORITY_MATCH, arg): if re.match(match, arg):
argfilter = Filter.PriorityFilter(arg) argfilter = _filter(arg)
else:
if not argfilter:
argfilter = Filter.GrepFilter(arg) argfilter = Filter.GrepFilter(arg)
if is_negated: if is_negated:
......
...@@ -160,7 +160,7 @@ class LimitFilter(Filter): ...@@ -160,7 +160,7 @@ class LimitFilter(Filter):
def filter(self, p_todos): def filter(self, p_todos):
return p_todos[:self.limit] if self.limit >= 0 else p_todos return p_todos[:self.limit] if self.limit >= 0 else p_todos
OPERATOR_MATCH = r"(?P<operator><=?|=|>=?|!)?" _OPERATOR_MATCH = r"(?P<operator><=?|=|>=?|!)?"
class OrdinalFilter(Filter): class OrdinalFilter(Filter):
...@@ -200,12 +200,13 @@ class OrdinalFilter(Filter): ...@@ -200,12 +200,13 @@ class OrdinalFilter(Filter):
return False return False
ORDINAL_TAG_MATCH = r"(?P<key>[^:]*):" + OPERATOR_MATCH + r"(?P<value>\S+)" _VALUE_MATCH = r"(?P<value>\S+)"
_ORDINAL_TAG_MATCH = r"(?P<key>[^:]*):" + _OPERATOR_MATCH + _VALUE_MATCH
class OrdinalTagFilter(OrdinalFilter): class OrdinalTagFilter(OrdinalFilter):
def __init__(self, p_expression): def __init__(self, p_expression):
super(OrdinalTagFilter, self).__init__(p_expression, ORDINAL_TAG_MATCH) super(OrdinalTagFilter, self).__init__(p_expression, _ORDINAL_TAG_MATCH)
def match(self, p_todo): def match(self, p_todo):
""" """
...@@ -243,12 +244,55 @@ class OrdinalTagFilter(OrdinalFilter): ...@@ -243,12 +244,55 @@ class OrdinalTagFilter(OrdinalFilter):
return self.compare_operands(operand1, operand2) return self.compare_operands(operand1, operand2)
PRIORITY_MATCH = r"\(" + OPERATOR_MATCH + r"(?P<value>[A-Z]{1})\)"
class _DateAttributeFilter(OrdinalFilter):
def __init__(self, p_expression, p_match, p_getter):
super(_DateAttributeFilter, self).__init__(p_expression, p_match)
self.getter = p_getter
def match(self, p_todo):
operand1 = self.getter(p_todo)
operand2 = relative_date_to_date(self.value)
if not operand2:
operand2 = date_string_to_date(self.value)
if operand1 and operand2:
return self.compare_operands(operand1, operand2)
else:
return False
_CREATED_MATCH = r'creat(ion|ed?):' + _OPERATOR_MATCH + _VALUE_MATCH
class CreationFilter(_DateAttributeFilter):
def __init__(self, p_expression):
super(CreationFilter, self).__init__(
p_expression,
_CREATED_MATCH,
lambda t: t.creation_date() # pragma: no branch
)
_COMPLETED_MATCH = r'complet(ed?|ion):' + _OPERATOR_MATCH + _VALUE_MATCH
class CompletionFilter(_DateAttributeFilter):
def __init__(self, p_expression):
super(CompletionFilter, self).__init__(
p_expression,
_COMPLETED_MATCH,
lambda t: t.completion_date() # pragma: no branch
)
_PRIORITY_MATCH = r"\(" + _OPERATOR_MATCH + r"(?P<value>[A-Z]{1})\)"
class PriorityFilter(OrdinalFilter): class PriorityFilter(OrdinalFilter):
def __init__(self, p_expression): def __init__(self, p_expression):
super(PriorityFilter, self).__init__(p_expression, PRIORITY_MATCH) super(PriorityFilter, self).__init__(p_expression, _PRIORITY_MATCH)
def match(self, p_todo): def match(self, p_todo):
""" """
...@@ -265,3 +309,10 @@ class PriorityFilter(OrdinalFilter): ...@@ -265,3 +309,10 @@ class PriorityFilter(OrdinalFilter):
operand2 = p_todo.priority() or 'ZZ' operand2 = p_todo.priority() or 'ZZ'
return self.compare_operands(operand1, operand2) return self.compare_operands(operand1, operand2)
MATCHES = [
(_CREATED_MATCH, CreationFilter),
(_COMPLETED_MATCH, CompletionFilter),
(_ORDINAL_TAG_MATCH, OrdinalTagFilter),
(_PRIORITY_MATCH, PriorityFilter),
]
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