Commit 34a568a2 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'master' of github.com:bram85/topydo

parents 771b7aae e216d6e9
......@@ -28,10 +28,10 @@ Simply install with:
### Optional dependencies
* icalendar : To print your todo.txt file as an iCalendar file
(not supported for Python 3.2).
* prompt-toolkit : For topydo's _prompt_ mode, which offers a shell-like
interface with auto-completion.
* [icalendar][7] : To print your todo.txt file as an iCalendar file
(not supported for Python 3.2).
* [prompt-toolkit][6] : For topydo's _prompt_ mode, which offers a shell-like
interface with auto-completion.
Demo
----
......@@ -44,3 +44,5 @@ Demo
[3]: https://github.com/bram85/todo.txt-tools
[4]: https://github.com/bram85/topydo/wiki
[5]: https://raw.githubusercontent.com/bram85/topydo/stable/doc/topydo.gif
[6]: https://github.com/jonathanslenders/python-prompt-toolkit
[7]: https://github.com/collective/icalendar
......@@ -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()
......@@ -135,9 +135,9 @@ class DepCommand(Command):
def help(self):
return """\
* add : Adds a dependency. Using 1 before 2 creates a dependency
from todo item 2 to 1.
* rm (alias: del) : Removes a dependency.
* ls : Lists all dependencies to or from a certain todo.
* clean (alias: gc): Removes redundant id or p tags.
* add : Adds a dependency. Using 1 before 2 creates a dependency
from todo item 2 to 1.
* rm (alias: del) : Removes a dependency.
* ls : Lists all dependencies to or from a certain todo.
* clean (alias: gc) : Removes redundant id or p tags.
"""
......@@ -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