Commit 49bfbc25 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'column-ui/master'

Conflicts:
	.travis.yml
	setup.py
parents 69951ef5 7a32e218
......@@ -12,5 +12,6 @@ exclude_lines =
if __name__ == .__main__.:
omit =
topydo/ui/*.py
topydo/commands/ExitCommand.py
topydo/lib/Version.py
......@@ -8,8 +8,9 @@ python:
install:
- "python -m pip install pip --upgrade"
- "pip install ."
- "pip install .[columns]"
- "pip install .[ical]"
- "pip install .[prompt_toolkit]"
- "pip install .[prompt]"
- "pip install .[test]"
- "pip install pylint"
- "pip install codecov"
......
......@@ -36,6 +36,9 @@ Simply install with:
(not supported for PyPy3).
* [prompt-toolkit][6] : For topydo's _prompt_ mode, which offers a shell-like
interface with auto-completion.
* [arrow][8] : Used to turn dates into a human readable version.
* [urwid][12] : For topydo's _columns_ mode, a TUI with columns for
your todo items.
* [backports.shutil_get_terminal_size][9] : Used to determine your terminal
window size. This function was
added to the standard library in
......@@ -61,3 +64,4 @@ Demo
[9]: https://github.com/chrippa/backports.shutil_get_terminal_size
[10]: https://dateutil.readthedocs.org/
[11]: https://github.com/testing-cabal/mock
[12]: https://github.com/urwid/urwid
......@@ -34,8 +34,9 @@ setup(
extras_require = {
':sys_platform=="win32"': ['colorama>=0.2.5'],
':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'],
'columns': ['urwid >= 1.3.0'],
'ical': ['icalendar'],
'prompt_toolkit': ['prompt_toolkit >= 0.53'],
'prompt': ['prompt_toolkit >= 0.53'],
'test': ['coverage', 'freezegun', 'green', ],
'test:python_version=="3.2"': ['mock'],
},
......
......@@ -25,9 +25,14 @@ class CommandTest(TopydoTest):
self.errors = ""
def out(self, p_output):
if p_output:
self.output += escape_ansi(p_output + "\n")
if isinstance(p_output, list) and p_output:
self.output += escape_ansi(
"\n".join([str(s) for s in p_output]) + "\n")
elif p_output:
self.output += str(p_output) + "\n"
def error(self, p_error):
if p_error:
self.errors += escape_ansi(p_error + "\n")
if isinstance(p_error, list) and p_error:
self.errors += escape_ansi(p_error + "\n") + "\n"
elif p_error:
self.errors += str(p_error) + "\n"
[column_keymap]
up = up
<Left> = prev_column
<Esc>d = delete_column
......@@ -52,4 +52,4 @@ def todolist_to_string(p_list):
def print_view(p_view):
printer = PrettyPrinter()
return printer.print_list(p_view.todos)
return "\n".join([str(s) for s in printer.print_list(p_view.todos)])
......@@ -30,8 +30,8 @@ class ArchiveCommandTest(CommandTest):
command = ArchiveCommand(todolist, archive)
command.execute()
self.assertTrue(todolist.is_dirty())
self.assertTrue(archive.is_dirty())
self.assertTrue(todolist.dirty)
self.assertTrue(archive.dirty)
self.assertEqual(todolist.print_todos(), "x Not complete\n(C) Active")
self.assertEqual(archive.print_todos(), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete")
......
......@@ -19,152 +19,159 @@
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Colors import NEUTRAL_COLOR, Colors
from topydo.lib.Color import Color
from topydo.lib.Config import config
from topydo.lib.Todo import Todo
NEUTRAL_COLOR = '\033[0m'
class ColorsTest(TopydoTest):
def test_project_color1(self):
config(p_overrides={('colorscheme', 'project_color'): '2'})
color = Colors().get_project_color()
self.assertEqual(color, '\033[1;38;5;2m')
self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), '\033[1;32m')
def test_project_color2(self):
config(p_overrides={('colorscheme', 'project_color'): 'Foo'})
color = Colors().get_project_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_project_color3(self):
config(p_overrides={('colorscheme', 'project_color'): 'yellow'})
color = Colors().get_project_color()
self.assertEqual(color, '\033[1;33m')
self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), '\033[1;33m')
def test_project_color4(self):
config(p_overrides={('colorscheme', 'project_color'): '686'})
color = Colors().get_project_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_context_color1(self):
config(p_overrides={('colorscheme', 'context_color'): '35'})
color = Colors().get_context_color()
self.assertEqual(color, '\033[1;38;5;35m')
self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), '\033[1;38;5;35m')
def test_context_color2(self):
config(p_overrides={('colorscheme', 'context_color'): 'Bar'})
color = Colors().get_context_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_context_color3(self):
config(p_overrides={('colorscheme', 'context_color'): 'magenta'})
color = Colors().get_context_color()
self.assertEqual(color, '\033[1;35m')
self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), '\033[1;35m')
def test_context_color4(self):
config(p_overrides={('colorscheme', 'context_color'): '392'})
color = Colors().get_context_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_metadata_color1(self):
config(p_overrides={('colorscheme', 'metadata_color'): '128'})
color = Colors().get_metadata_color()
self.assertEqual(color, '\033[1;38;5;128m')
self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), '\033[1;38;5;128m')
def test_metadata_color2(self):
config(p_overrides={('colorscheme', 'metadata_color'): 'Baz'})
color = Colors().get_metadata_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_metadata_color3(self):
config(p_overrides={('colorscheme', 'metadata_color'): 'light-red'})
color = Colors().get_metadata_color()
self.assertEqual(color, '\033[1;1;31m')
self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), '\033[1;1;31m')
def test_metadata_color4(self):
config(p_overrides={('colorscheme', 'metadata_color'): '777'})
color = Colors().get_metadata_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_link_color1(self):
config(p_overrides={('colorscheme', 'link_color'): '77'})
color = Colors().get_link_color()
self.assertEqual(color, '\033[4;38;5;77m')
self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), '\033[4;38;5;77m')
def test_link_color2(self):
config(p_overrides={('colorscheme', 'link_color'): 'FooBar'})
color = Colors().get_link_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), NEUTRAL_COLOR)
def test_link_color3(self):
config(p_overrides={('colorscheme', 'link_color'): 'red'})
color = Colors().get_link_color()
self.assertEqual(color, '\033[4;31m')
self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), '\033[4;31m')
def test_link_color4(self):
config(p_overrides={('colorscheme', 'link_color'): '777'})
color = Colors().get_link_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), NEUTRAL_COLOR)
def test_priority_color1(self):
config("test/data/ColorsTest1.conf")
color = Colors().get_priority_colors()
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
self.assertEqual(color['A'], '\033[0;38;5;1m')
self.assertEqual(color['B'], '\033[0;38;5;2m')
self.assertEqual(color['C'], '\033[0;38;5;3m')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, '\033[0;31m')
self.assertEqual(color_b, '\033[0;32m')
self.assertEqual(color_c, '\033[0;33m')
def test_priority_color2(self):
config("test/data/ColorsTest2.conf")
color = Colors().get_priority_colors()
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color['A'], '\033[0;35m')
self.assertEqual(color['B'], '\033[0;1;36m')
self.assertEqual(color['C'], '\033[0;37m')
self.assertEqual(color_a, '\033[0;35m')
self.assertEqual(color_b, '\033[0;1;36m')
self.assertEqual(color_c, '\033[0;37m')
def test_priority_color3(self):
config("test/data/ColorsTest3.conf")
color = Colors().get_priority_colors()
self.assertEqual(color['A'], '\033[0;35m')
self.assertEqual(color['B'], '\033[0;1;36m')
self.assertEqual(color['Z'], NEUTRAL_COLOR)
self.assertEqual(color['D'], '\033[0;31m')
self.assertEqual(color['C'], '\033[0;38;5;7m')
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_z = Todo('(Z) FooBar')
todo_d = Todo('(D) Baz')
todo_c = Todo('(C) FooBaz')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_z = config().priority_color(todo_z.priority()).as_ansi()
color_d = config().priority_color(todo_d.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, '\033[0;35m')
self.assertEqual(color_b, '\033[0;1;36m')
self.assertEqual(color_z, NEUTRAL_COLOR)
self.assertEqual(color_d, '\033[0;31m')
self.assertEqual(color_c, '\033[0;37m')
def test_priority_color4(self):
config("test/data/ColorsTest4.conf")
color = Colors().get_priority_colors()
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
self.assertEqual(color['A'], NEUTRAL_COLOR)
self.assertEqual(color['B'], NEUTRAL_COLOR)
self.assertEqual(color['C'], NEUTRAL_COLOR)
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, '')
self.assertEqual(color_b, '')
self.assertEqual(color_c, '')
def test_empty_color_values(self):
config("test/data/ColorsTest5.conf")
pri_color = Colors().get_priority_colors()
project_color = Colors().get_project_color()
context_color = Colors().get_context_color()
link_color = Colors().get_link_color()
metadata_color = Colors().get_metadata_color()
self.assertEqual(pri_color['A'], NEUTRAL_COLOR)
self.assertEqual(pri_color['B'], NEUTRAL_COLOR)
self.assertEqual(pri_color['C'], NEUTRAL_COLOR)
project_color = config().project_color().as_ansi(p_decoration='bold')
context_color = config().context_color().as_ansi(p_decoration='bold')
link_color = config().link_color().as_ansi(p_decoration='underline')
metadata_color = config().metadata_color().as_ansi(p_decoration='bold')
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, NEUTRAL_COLOR)
self.assertEqual(color_b, NEUTRAL_COLOR)
self.assertEqual(color_c, NEUTRAL_COLOR)
self.assertEqual(project_color, '')
self.assertEqual(context_color, '')
self.assertEqual(link_color, '')
......@@ -172,19 +179,31 @@ class ColorsTest(TopydoTest):
def test_empty_colorscheme(self):
config("test/data/config1")
pri_color = Colors().get_priority_colors()
project_color = Colors().get_project_color()
context_color = Colors().get_context_color()
link_color = Colors().get_link_color()
metadata_color = Colors().get_metadata_color()
self.assertEqual(pri_color['A'], '\033[0;36m')
self.assertEqual(pri_color['B'], '\033[0;33m')
self.assertEqual(pri_color['C'], '\033[0;34m')
project_color = config().project_color().as_ansi(p_decoration='bold')
context_color = config().context_color().as_ansi(p_decoration='bold')
link_color = config().link_color().as_ansi(p_decoration='underline')
metadata_color = config().metadata_color().as_ansi(p_decoration='bold')
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, '\033[0;36m')
self.assertEqual(color_b, '\033[0;33m')
self.assertEqual(color_c, '\033[0;34m')
self.assertEqual(project_color, '\033[1;31m')
self.assertEqual(context_color, '\033[1;35m')
self.assertEqual(link_color, '\033[4;36m')
self.assertEqual(metadata_color, '\033[1;32m')
def test_neutral_color(self):
color = Color('NEUTRAL')
self.assertEqual(color.as_ansi(), NEUTRAL_COLOR)
if __name__ == '__main__':
unittest.main()
......@@ -45,8 +45,7 @@ class ConfigTest(TopydoTest):
""" Bad colour switch value. """
# boolean settings must first be typecast to integers, because all
# strings evaulate to 'True'
self.assertEqual(config("test/data/ConfigTest4.conf").colors(),
bool(int(config().defaults["topydo"]["colors"])))
self.assertEqual(config("test/data/ConfigTest4.conf").colors(), 16)
def test_config06(self):
""" Bad auto creation date switch value. """
......@@ -83,62 +82,76 @@ class ConfigTest(TopydoTest):
self.assertEqual(config("test/data/ConfigTest4.conf").append_parent_contexts(),
bool(int(config().defaults["dep"]["append_parent_contexts"])))
@skip("Error checking not yet implemented")
def test_config14(self):
""" Bad priority color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").priority_colors(),
config().defaults["colorscheme"]["priority_colors"])
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('A').color, 6)
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('B').color, 3)
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('C').color, 4)
@skip("Error checking not yet implemented")
def test_config15(self):
""" Bad project color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").project_color(),
config().defaults["colorscheme"]["project_color"])
self.assertTrue(config("test/data/ConfigTest4.conf").project_color().is_neutral())
@skip("Error checking not yet implemented")
def test_config16(self):
""" Bad context color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").context_color(),
config().defaults["colorscheme"]["context_color"])
self.assertTrue(config("test/data/ConfigTest4.conf").context_color().is_neutral())
@skip("Error checking not yet implemented")
def test_config17(self):
""" Bad metadata color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").metadata_color(),
config().defaults["colorscheme"]["metadata_color"])
self.assertTrue(config("test/data/ConfigTest4.conf").metadata_color().is_neutral())
@skip("Error checking not yet implemented")
def test_config18(self):
""" Bad link color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").link_color(),
config().defaults["colorscheme"]["link_color"])
self.assertTrue(config("test/data/ConfigTest4.conf").link_color().is_neutral())
@skip("Test not yet implemented")
# the test needs to be of the internal function _str_to_dict
def test_config19(self):
""" No priority color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").priority_colors(),
config().defaults["colorscheme"]["priority_colors"])
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('A').color, 6)
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('B').color, 3)
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('C').color, 4)
def test_config20(self):
""" No project color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").project_color(),
config().defaults["colorscheme"]["project_color"])
self.assertEqual(config("test/data/ConfigTest5.conf").project_color().color, 1)
def test_config21(self):
""" No context color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").context_color(),
config().defaults["colorscheme"]["context_color"])
self.assertEqual(config("test/data/ConfigTest5.conf").context_color().color, 5)
def test_config22(self):
""" No metadata color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").metadata_color(),
config().defaults["colorscheme"]["metadata_color"])
self.assertEqual(config("test/data/ConfigTest5.conf").metadata_color().color, 2)
def test_config23(self):
""" No link color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").link_color(),
config().defaults["colorscheme"]["link_color"])
self.assertEqual(config("test/data/ConfigTest5.conf").link_color().color, 6)
def test_config24(self):
""" column_keymap test. """
keymap, keystates = config("test/data/ConfigTest6.conf").column_keymap()
self.assertEqual(keymap['pp'], 'postpone')
self.assertEqual(keymap['ps'], 'postpone_s')
self.assertEqual(keymap['pr'], 'pri')
self.assertEqual(keymap['pra'], 'cmd pri {} a')
self.assertIn('p', keystates)
self.assertIn('g', keystates)
self.assertIn('pp', keystates)
self.assertIn('ps', keystates)
self.assertIn('pr', keystates)
self.assertEqual(keymap['up'], 'up')
self.assertIn('u', keystates)
self.assertEqual(keymap['<Left>'], 'prev_column')
self.assertNotIn('<Lef', keystates)
self.assertEqual(keymap['<Esc>d'], 'delete_column')
self.assertNotIn('<Esc', keystates)
self.assertIn('<Esc>', keystates)
if __name__ == '__main__':
unittest.main()
......@@ -48,7 +48,7 @@ class DeleteCommandTest(CommandTest):
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Bar")
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -58,7 +58,7 @@ class DeleteCommandTest(CommandTest):
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Bar")
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -68,7 +68,7 @@ class DeleteCommandTest(CommandTest):
_yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.output,
"| 2| Bar p:1\nRemoved: Bar\nRemoved: Foo\n")
......@@ -79,7 +79,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -89,7 +89,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -98,7 +98,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["2"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Foo")
self.assertEqual(self.output, "Removed: Bar p:1\nThe following todo item(s) became active:\n| 1| Foo\n")
self.assertEqual(self.errors, "")
......@@ -107,7 +107,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["99"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -115,7 +115,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["A"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -159,7 +159,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
......@@ -169,7 +169,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: A.\n")
......@@ -181,7 +181,7 @@ class DeleteCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -193,7 +193,7 @@ class DeleteCommandTest(CommandTest):
result = "Removed: a @test with due:2015-06-03\nRemoved: a @test with +project\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -203,7 +203,7 @@ class DeleteCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Removed: a @test with due:2015-06-03\n")
self.assertEqual(self.errors, "")
......@@ -212,7 +212,7 @@ class DeleteCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_del4(self):
""" Remove only relevant todo items. """
......@@ -222,7 +222,7 @@ class DeleteCommandTest(CommandTest):
result = "Foo"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 1)
self.assertEqual(self.todolist.print_todos(), result)
......@@ -232,14 +232,14 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 0)
def test_empty(self):
command = DeleteCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......
......@@ -40,7 +40,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -50,7 +50,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -60,7 +60,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -69,7 +69,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -77,7 +77,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["add", "1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -86,7 +86,7 @@ class DepCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -96,7 +96,7 @@ class DepCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('p', '2'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -106,7 +106,7 @@ class DepCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('p', '2'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -116,7 +116,7 @@ class DepCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -190,7 +190,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(p_args, self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('id', '1'))
self.assertFalse(self.todolist.todo(3).has_tag('p', '1'))
self.assertEqual(self.output, "")
......@@ -213,7 +213,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -222,7 +222,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -230,7 +230,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["rm", "1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -239,7 +239,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 2| Bar p:1\n| 3| Baz p:1\n")
self.assertEqual(self.errors, "")
......@@ -248,7 +248,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -257,7 +257,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -266,7 +266,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -274,7 +274,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["ls", "1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -282,7 +282,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["ls"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -291,7 +291,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -300,7 +300,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertFalse(self.output)
self.assertFalse(self.errors)
self.assertFalse(self.todolist.todo(5).has_tag('p', '99'))
......@@ -317,7 +317,7 @@ class DepCommandTest(CommandTest):
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_no_subsubcommand(self):
command = DepCommand([], self.todolist, self.out, self.error)
......@@ -325,7 +325,7 @@ class DepCommandTest(CommandTest):
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_help(self):
command = DepCommand(["help"], self.todolist, self.out, self.error)
......
......@@ -39,7 +39,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["1"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).priority(), None)
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\n")
self.assertEqual(self.errors, "")
......@@ -48,7 +48,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["2"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.todolist.todo(2).priority(), None)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -57,7 +57,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["Foo"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).priority(), None)
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\n")
self.assertEqual(self.errors, "")
......@@ -67,7 +67,7 @@ class DepriCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).priority(), None)
self.assertEqual(self.todolist.todo(3).priority(), None)
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\nPriority removed.\n| 3| Baz\n")
......@@ -80,7 +80,7 @@ class DepriCommandTest(CommandTest):
result = "Priority removed.\n| 4| a @test with due:2015-06-03\nPriority removed.\n| 5| a @test with +project p:1\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -91,7 +91,7 @@ class DepriCommandTest(CommandTest):
result = "Priority removed.\n| 4| a @test with due:2015-06-03\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -100,7 +100,7 @@ class DepriCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_depri4(self):
""" Don't remove priority from unrelevant todo items. """
......@@ -108,7 +108,7 @@ class DepriCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_depri5(self):
""" Force unprioritizing unrelevant items with additional -x flag. """
......@@ -116,7 +116,7 @@ class DepriCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority removed.\n| 6| Bax id:1\n")
self.assertEqual(self.errors, "")
......@@ -124,7 +124,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["99"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -133,7 +133,7 @@ class DepriCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
......@@ -142,7 +142,7 @@ class DepriCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: FooBar.\n")
......@@ -154,7 +154,7 @@ class DepriCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -163,7 +163,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......
......@@ -61,7 +61,7 @@ class DoCommandTest(CommandTest):
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
......@@ -77,7 +77,7 @@ class DoCommandTest(CommandTest):
for number in [1, 2, 3]:
self.assertTrue(self.todolist.todo(number).is_completed())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertFalse(self.todolist.todo(4).is_completed())
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -89,7 +89,7 @@ class DoCommandTest(CommandTest):
result = "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {} Foo id:1\n".format(self.today)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).is_completed())
self.assertFalse(self.todolist.todo(2).is_completed())
self.assertFalse(self.todolist.todo(3).is_completed())
......@@ -130,7 +130,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(p_flags, self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.count(), 12)
......@@ -162,7 +162,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["99"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -170,7 +170,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["AAA"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -179,7 +179,7 @@ class DoCommandTest(CommandTest):
_yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -209,7 +209,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["5"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.todolist.todo(5).completion_date(),
date(2014, 10, 18))
self.assertFalse(self.output)
......@@ -219,7 +219,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["baz"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
......@@ -230,7 +230,7 @@ class DoCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Completed: x 2014-11-18 Baz p:1\n")
self.assertEqual(self.errors, "")
......@@ -239,7 +239,7 @@ class DoCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x 2014-11-18 Bar p:1\nCompleted: x 2014-11-18 Baz p:1\nCompleted: x 2014-11-18 Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -248,7 +248,7 @@ class DoCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Completed: x 2014-11-18 Baz p:1\n")
self.assertEqual(self.errors, "")
......@@ -257,7 +257,7 @@ class DoCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.errors, "")
......@@ -271,7 +271,7 @@ class DoCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 12| {today} Recurring! rec:1d due:{today}\nCompleted: x {yesterday} Recurring! rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "")
......@@ -285,7 +285,7 @@ class DoCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 12| {today} Recurring! rec:1d due:{today}\nCompleted: x {yesterday} Recurring! rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "")
......@@ -298,7 +298,7 @@ class DoCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 12| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {yesterday} Strict due:2014-01-01 rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "")
......@@ -310,7 +310,7 @@ class DoCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.yesterday))
self.assertEqual(self.errors, "")
......@@ -323,7 +323,7 @@ class DoCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.yesterday))
self.assertEqual(self.errors, "")
......@@ -385,7 +385,7 @@ class DoCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -394,7 +394,7 @@ class DoCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Completed: x {t} a @test with due:2015-06-03\nCompleted: x {t} a @test with +project\n".format(t=self.today))
self.assertEqual(self.errors, "")
......@@ -403,7 +403,7 @@ class DoCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Completed: x {} a @test with due:2015-06-03\n".format(self.today))
self.assertEqual(self.errors, "")
......@@ -412,7 +412,7 @@ class DoCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_do4(self):
""" Don't do anything with unrelevant todo items. """
......@@ -420,7 +420,7 @@ class DoCommandTest(CommandTest):
None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_do5(self):
""" Force marking unrelevant items as done with additional -x flag. """
......@@ -430,7 +430,7 @@ class DoCommandTest(CommandTest):
result = "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {t} Bar p:1\nCompleted: x {t} Baz p:1\nCompleted: x {t} Foo id:1\n".format(t=self.today)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -450,7 +450,7 @@ class DoCommandTest(CommandTest):
command = DoCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......
......@@ -56,7 +56,7 @@ class EditCommandTest(CommandTest):
command.execute()
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.print_todos(), u"Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1")
@mock.patch('topydo.commands.EditCommand._is_edited')
......@@ -72,7 +72,7 @@ class EditCommandTest(CommandTest):
None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat")
......@@ -82,7 +82,7 @@ class EditCommandTest(CommandTest):
None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_edit04(self):
......@@ -91,7 +91,7 @@ class EditCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, "Invalid todo number given: 5.\n")
def test_edit05(self):
......@@ -102,7 +102,7 @@ class EditCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -119,7 +119,7 @@ class EditCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(),
u"Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat")
......@@ -137,7 +137,7 @@ class EditCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, "Editing aborted. Nothing to do.\n")
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nBar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a")
......@@ -158,7 +158,7 @@ class EditCommandTest(CommandTest):
expected = u"| 3| Lazy Cat\n| 4| Lazy Dog\n"
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, expected)
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog")
......
......@@ -43,7 +43,7 @@ class ListCommandTest(CommandTest):
command = ListCommand([""], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -52,7 +52,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -61,7 +61,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -69,7 +69,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.errors, "")
......@@ -78,7 +78,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -87,7 +87,7 @@ class ListCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
......@@ -96,7 +96,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -105,7 +105,7 @@ class ListCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -114,7 +114,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -124,7 +124,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["project"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
......@@ -135,7 +135,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -144,7 +144,7 @@ class ListCommandTest(CommandTest):
self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.errors, "")
......@@ -154,7 +154,7 @@ class ListCommandTest(CommandTest):
command = ListCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " | 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n | 4| (C) Drink beer @ home\n | 5| (C) 13 + 29 = 42\n | 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -162,7 +162,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["p:<10"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -172,7 +172,7 @@ class ListCommandTest(CommandTest):
command = ListCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "|t5c| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n|wa5| (C) Drink beer @ home\n|z63| (C) 13 + 29 = 42\n|mfg| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -181,7 +181,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output,
"| 3| (C) Baz @Context1 +Project1 key:value\n")
self.assertEqual(self.errors, "")
......@@ -191,7 +191,7 @@ class ListCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
def test_list19(self):
......@@ -202,7 +202,7 @@ class ListCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 id:1 key:value\n| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
......@@ -210,7 +210,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-f text"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -219,7 +219,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -231,7 +231,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, '| 1| Foo.\n')
def test_list31(self):
......@@ -406,7 +406,7 @@ class ListCommandUnicodeTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
expected = u"| 1| (C) And some sp\u00e9cial tag:\u25c4\n"
......@@ -420,7 +420,7 @@ class ListCommandJsonTest(CommandTest):
command = ListCommand(["-f", "json"], todolist, self.out, self.error)
command.execute()
self.assertFalse(todolist.is_dirty())
self.assertFalse(todolist.dirty)
jsontext = ""
with codecs.open('test/data/ListCommandTest.json', 'r',
......@@ -436,7 +436,7 @@ class ListCommandJsonTest(CommandTest):
command = ListCommand(["-f", "json"], todolist, self.out, self.error)
command.execute()
self.assertFalse(todolist.is_dirty())
self.assertFalse(todolist.dirty)
jsontext = ""
with codecs.open('test/data/ListCommandUnicodeTest.json', 'r',
......@@ -466,7 +466,7 @@ class ListCommandIcalTest(CommandTest):
self.error)
command.execute()
self.assertTrue(todolist.is_dirty())
self.assertTrue(todolist.dirty)
icaltext = ""
with codecs.open('test/data/ListCommandTest.ics', 'r',
......@@ -483,7 +483,7 @@ class ListCommandIcalTest(CommandTest):
command = ListCommand(["-f", "ical"], todolist, self.out, self.error)
command.execute()
self.assertTrue(todolist.is_dirty())
self.assertTrue(todolist.dirty)
icaltext = ""
with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r',
......
......@@ -678,7 +678,26 @@ C -
"""
self.assertEqual(self.output, result)
def test_list_format45(self):
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format45(self, mock_terminal_size):
""" Colorblocks should not affect truncating or right_alignment. """
self.maxDiff = None
mock_terminal_size.return_value = self.terminal_size(100, 25)
config(p_overrides={('ls', 'list_format'): '%z|%I| %x %p %S %k\\t%{(}h{)}'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u""" | 1| D Bar @Context1 +Project2 (due a month ago, started a month ago)
| 2| Z Lorem ipsum dolorem sit amet. Red @fox +jumpe... lazy:bar (due in 2 days, starts in a day)
| 3| C Foo @Context2 Not@Context +Project1 Not+Project
| 4| C Baz @Context1 +Project1 key:value
| 5| Drink beer @ home
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
def test_list_format46(self):
command = ListCommand(["-x", "-F", "%r"], self.todolist, self.out, self.error)
command.execute()
......
......@@ -49,7 +49,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -61,7 +61,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -73,7 +73,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -85,7 +85,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), self.start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -97,7 +97,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
start = self.start + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103
self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -109,7 +109,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 4| Past due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -121,7 +121,7 @@ class PostponeCommandTest(CommandTest):
due = self.future + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), self.future_start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -134,7 +134,7 @@ class PostponeCommandTest(CommandTest):
due = self.future + timedelta(7)
start = self.future_start + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -144,7 +144,7 @@ class PostponeCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid date pattern given.\n")
......@@ -153,7 +153,7 @@ class PostponeCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -162,7 +162,7 @@ class PostponeCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -170,7 +170,7 @@ class PostponeCommandTest(CommandTest):
command = PostponeCommand(["1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -181,7 +181,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -193,7 +193,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo due:{}\n| 2| Bar due:{}\n".format(due.isoformat(), due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -204,7 +204,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo due:{}\n| 2| Bar due:{}\n".format(due.isoformat(), due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -216,7 +216,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
start = self.start + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103
self.assertEqual(self.output, "| 2| Bar due:{}\n| 3| Baz due:{} t:{}\n".format(due.isoformat(), due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -226,7 +226,7 @@ class PostponeCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid date pattern given.\n")
......@@ -235,7 +235,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 123.\n")
......@@ -244,7 +244,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: Zoo.\nInvalid todo number given: 99.\nInvalid todo number given: 123.\n")
......@@ -254,7 +254,7 @@ class PostponeCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -267,7 +267,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(14)
result = "| 2| Bar due:{d}\n| 3| Baz due:{d} t:{s}\n".format(d=due.isoformat(), s=self.start.isoformat())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -282,7 +282,7 @@ class PostponeCommandTest(CommandTest):
result = "| 3| Baz due:{} t:{}\n".format(due.isoformat(),
self.start.isoformat())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -291,7 +291,7 @@ class PostponeCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_postpone4(self):
""" Don't postpone unrelevant todo items. """
......@@ -299,7 +299,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_postpone5(self):
""" Force postponing unrelevant items with additional -x flag. """
......@@ -310,7 +310,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
result = "| 6| FutureStart t:{} due:{}\n".format(self.future.isoformat(), due.isoformat())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......
......@@ -39,7 +39,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.errors, "")
......@@ -49,7 +49,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority set to Z.\n| 2| (Z) Bar\n")
self.assertEqual(self.errors, "")
......@@ -58,7 +58,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.errors, "")
......@@ -68,7 +68,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (A) Foo\n")
self.assertEqual(self.errors, "")
......@@ -77,7 +77,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "")
......@@ -87,7 +87,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "")
......@@ -97,7 +97,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "")
......@@ -108,7 +108,7 @@ class PriorityCommandTest(CommandTest):
result = "Priority changed from B to C\n| 3| (C) a @test with due:2015-06-03\nPriority set to C.\n| 4| (C) a @test with +project p:1\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -119,7 +119,7 @@ class PriorityCommandTest(CommandTest):
result = "Priority changed from B to C\n| 3| (C) a @test with due:2015-06-03\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -129,7 +129,7 @@ class PriorityCommandTest(CommandTest):
None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_prio4(self):
""" Don't prioritize unrelevant todo items. """
......@@ -137,7 +137,7 @@ class PriorityCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_prio5(self):
""" Force prioritizing unrelevant items with additional -x flag. """
......@@ -145,7 +145,7 @@ class PriorityCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Priority set to D.\n| 5| (D) Baz id:1\n")
self.assertEqual(self.errors, "")
......@@ -155,7 +155,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -164,7 +164,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
......@@ -173,7 +173,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 98.\nInvalid todo number given: 99.\n")
......@@ -182,7 +182,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid priority given.\n")
......@@ -190,7 +190,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand(["A"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -198,7 +198,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand(["1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -210,7 +210,7 @@ class PriorityCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -224,7 +224,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n")
......@@ -237,7 +237,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n")
......@@ -245,7 +245,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2016 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from freezegun import freeze_time
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
from topydo.lib.ProgressColor import progress_color
from topydo.lib.Todo import Todo
def set_256_colors():
config(p_overrides={('topydo', 'colors'): '256'})
@freeze_time('2016, 01, 01')
class ProgressColorTest(TopydoTest):
def test_progress1(self):
""" Test progress of task with no length """
color = progress_color(Todo('Foo'))
self.assertEqual(color.color, 2)
def test_progress2(self):
""" Test progress of task with no length (but with creation date). """
color = progress_color(Todo('2016-02-11 Foo'))
self.assertEqual(color.color, 2)
def test_progress3(self):
""" Test progress of task with no length """
set_256_colors()
color = progress_color(Todo('Foo'))
self.assertEqual(color.color, 22)
def test_progress4(self):
""" Test progress of task with no length (but with creation date). """
set_256_colors()
color = progress_color(Todo('2016-02-11 Foo'))
self.assertEqual(color.color, 22)
def test_progress5(self):
""" Test overdue tasks """
color = progress_color(Todo('Foo due:2015-12-31'))
self.assertEqual(color.color, 1)
def test_progress6(self):
""" Test overdue tasks """
set_256_colors()
color = progress_color(Todo('Foo due:2015-12-31'))
self.assertEqual(color.color, 196)
def test_progress7(self):
""" Due today """
color = progress_color(Todo('Foo due:2016-01-01'))
self.assertEqual(color.color, 3)
def test_progress8(self):
""" Due today (256) """
set_256_colors()
color = progress_color(Todo('Foo due:2016-01-01'))
self.assertEqual(color.color, 202)
def test_progress9(self):
""" Due tomorrow """
color = progress_color(Todo('Foo due:2016-01-02'))
# a length of 14 days is assumed
self.assertEqual(color.color, 3)
def test_progress10(self):
set_256_colors()
color = progress_color(Todo('Foo due:2016-01-02'))
# a length of 14 days is assumed
self.assertEqual(color.color, 208)
def test_progress11(self):
""" Due tomorrow (creation date) """
color = progress_color(Todo('2016-01-01 Foo due:2016-01-02'))
# a length of 14 days is assumed
self.assertEqual(color.color, 2)
def test_progress12(self):
""" Due tomorrow (creation date) """
set_256_colors()
color = progress_color(Todo('2016-01-01 Foo due:2016-01-02'))
self.assertEqual(color.color, 22)
def test_progress13(self):
""" Due tomorrow (recurrence) """
color = progress_color(Todo('Foo due:2016-01-02 rec:1d'))
self.assertEqual(color.color, 2)
def test_progress14(self):
""" Due tomorrow (recurrence) """
set_256_colors()
color = progress_color(Todo('Foo due:2016-01-02 rec:1d'))
self.assertEqual(color.color, 22)
def test_progress15(self):
""" Due tomorrow (creation date + recurrence) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 rec:1d'))
self.assertEqual(color.color, 2)
def test_progress16(self):
""" Due tomorrow (creation date + recurrence) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 rec:1d'))
self.assertEqual(color.color, 22)
def test_progress17(self):
""" Due tomorrow (creation date + recurrence + start date) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 rec:1d t:2016-01-02'))
self.assertEqual(color.color, 2)
def test_progress18(self):
""" Due tomorrow (creation date + recurrence + start date) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 rec:1d t:2016-01-02'))
self.assertEqual(color.color, 22)
def test_progress19(self):
""" Due tomorrow (creation date + strict recurrence + start date) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 rec:+1d t:2016-01-02'))
self.assertEqual(color.color, 2)
def test_progress20(self):
""" Due tomorrow (creation date + strict recurrence + start date) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 rec:+1d t:2016-01-02'))
self.assertEqual(color.color, 22)
def test_progress21(self):
""" Due tomorrow (creation date + start date) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 t:2016-01-02'))
self.assertEqual(color.color, 2)
def test_progress22(self):
""" Due tomorrow (creation date + start date) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 t:2016-01-02'))
self.assertEqual(color.color, 22)
def test_progress23(self):
""" Due tomorrow (creation date + start date) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 t:2015-12-31'))
self.assertEqual(color.color, 10)
def test_progress24(self):
""" Due tomorrow (creation date + start date) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 t:2015-12-31'))
self.assertEqual(color.color, 118)
def test_progress25(self):
""" Start date after due date """
color = progress_color(Todo('Foo due:2016-01-02 t:2016-01-03'))
# a length of 14 days is assumed
self.assertEqual(color.color, 3)
def test_progress26(self):
""" Start date after due date """
set_256_colors()
color = progress_color(Todo('Foo due:2016-01-02 t:2016-01-03'))
# a length of 14 days is assumed
self.assertEqual(color.color, 208)
def test_progress27(self):
""" Creation date after due date """
set_256_colors()
color = progress_color(Todo('2016-01-03 Foo due:2016-01-02'))
# a length of 14 days is assumed
self.assertEqual(color.color, 208)
if __name__ == '__main__':
unittest.main()
......@@ -42,7 +42,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22")
self.assertEqual(self.output, "| 1| Foo due:2014-10-22\n")
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_add_tag2(self):
command = TagCommand(["Foo", "due", "2014-10-22"], self.todolist,
......@@ -52,7 +52,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22")
self.assertEqual(self.output, "| 1| Foo due:2014-10-22\n")
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_add_tag3(self):
command = TagCommand(["-a", "2", "due", "2014-10-19"], self.todolist,
......@@ -64,14 +64,14 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output,
"| 2| Bar due:2014-10-22 due:2014-10-19\n")
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_add_tag4(self):
command = TagCommand(["Foox", "due", "2014-10-22"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number.\n")
......@@ -91,7 +91,7 @@ class TagCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 3| Baz due:2014-10-20\n")
self.assertEqual(self.errors, "")
......@@ -100,7 +100,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "all")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
......@@ -109,7 +109,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "1")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -118,7 +118,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "2")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
......@@ -127,7 +127,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -136,7 +136,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "99")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -145,7 +145,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "99")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
......@@ -156,7 +156,7 @@ class TagCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 3| Baz due:2015-11-19\n")
self.assertEqual(self.errors, "")
......@@ -169,7 +169,7 @@ class TagCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 3| Baz due:2014-10-20 foo:today\n")
self.assertEqual(self.errors, "")
......@@ -177,7 +177,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["1", "due"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo\n")
self.assertEqual(self.errors, "")
......@@ -185,7 +185,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["2", "due"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 2| Bar\n")
self.assertEqual(self.errors, "")
......@@ -194,7 +194,7 @@ class TagCommandTest(CommandTest):
self.error, lambda t: "all")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
" 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord\n")
self.assertEqual(self.errors, "")
......@@ -204,7 +204,7 @@ class TagCommandTest(CommandTest):
lambda t: "1")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -213,7 +213,7 @@ class TagCommandTest(CommandTest):
lambda t: "99")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -222,7 +222,7 @@ class TagCommandTest(CommandTest):
lambda t: "A")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -230,7 +230,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["5", "due"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number.\n")
......@@ -238,7 +238,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["A", "due"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number.\n")
......@@ -247,7 +247,7 @@ class TagCommandTest(CommandTest):
self.error, lambda t: "A")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 4| Fnord\n")
self.assertEqual(self.errors, "")
......@@ -255,7 +255,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["4"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......
......@@ -40,12 +40,12 @@ class TodoListTester(TopydoTest):
def test_contexts(self):
self.assertEqual(set(['Context1', 'Context2']),
self.todolist.contexts())
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_projects(self):
self.assertEqual(set(['Project1', 'Project2']),
self.todolist.projects())
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_add1(self):
text = "(C) Adding a new task @Context3 +Project3"
......@@ -58,7 +58,7 @@ class TodoListTester(TopydoTest):
self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts())
self.assertEqual(self.todolist.number(todo), 6)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_add2(self):
text = str(self.todolist)
......@@ -101,7 +101,7 @@ class TodoListTester(TopydoTest):
self.assertEqual(self.todolist.todo(2).source(),
"(C) Baz @Context1 +Project1 key:value")
self.assertEqual(self.todolist.count(), count - 1)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertRaises(InvalidTodoException, self.todolist.number, todo)
def test_delete2(self):
......@@ -112,7 +112,7 @@ class TodoListTester(TopydoTest):
self.todolist.delete(todo)
self.assertEqual(self.todolist.count(), count)
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_append1(self):
todo = self.todolist.todo(3)
......@@ -122,7 +122,7 @@ class TodoListTester(TopydoTest):
"(C) Baz @Context1 +Project1 key:value @Context3")
self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_append2(self):
todo = self.todolist.todo(3)
......@@ -145,7 +145,7 @@ class TodoListTester(TopydoTest):
self.assertRaises(InvalidTodoException, self.todolist.todo,
count + 100)
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_count(self):
""" Test that empty lines are not counted. """
......@@ -167,26 +167,26 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo(1)
self.todolist.set_todo_completed(todo)
self.assertTrue(self.todolist.todo(1).is_completed())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_todo_priority1(self):
todo = self.todolist.todo(1)
self.todolist.set_priority(todo, 'F')
self.assertEqual(self.todolist.todo(1).priority(), 'F')
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_todo_priority2(self):
todo = self.todolist.todo(1)
self.todolist.set_priority(todo, 'C')
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_erase(self):
self.todolist.erase()
self.assertEqual(self.todolist.count(), 0)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_regex1(self):
""" Multiple hits should result in None. """
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Utils import translate_key_to_config
class UtilsTest(TopydoTest):
def test_key_to_cfg(self):
ctrl_s = translate_key_to_config('ctrl s')
meta_d = translate_key_to_config('meta d')
esc = translate_key_to_config('esc')
f4 = translate_key_to_config('f4')
self.assertEqual(ctrl_s, '<C-s>')
self.assertEqual(meta_d, '<M-d>')
self.assertEqual(esc, '<Esc>')
self.assertEqual(f4, '<F4>')
if __name__ == '__main__':
unittest.main()
......@@ -4,7 +4,7 @@
default_command = ls
; filename = todo.txt
; archive_filename = done.txt
colors = 1
colors = auto
; identifiers can be 'linenumber' or 'text'
identifiers = linenumber
backup_count = 5
......@@ -42,6 +42,8 @@ append_parent_contexts = 0
; [light-]gray, darkgray or numbers from 0 to 255. When number is specified color
; is matched from Xterm color chart available here:
; http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg
; When using values between 16 and 256, make sure to set colors = 256 in the
; [topydo] section.
; priority_colors = A:cyan,B:yellow,C:blue
; project_color = red
......@@ -63,3 +65,32 @@ append_parent_contexts = 0
;listcon = lscon
;listcontext = lscon
;listcontexts = lscon
[column_keymap]
; Keymap configuration for column-mode
gg = home
G = end
j = down
k = up
d = cmd del {}
e = cmd edit {}
u = cmd revert
x = cmd do {}
pp = postpone
ps = postpone_s
pr = pri
m = mark
0 = first_column
$ = last_column
h = prev_column
l = next_column
A = append_column
I = insert_column
E = edit_column
D = delete_column
Y = copy_column
L = swap_left
R = swap_right
<Left> = prev_column
<Right> = next_column
<Esc> = reset
......@@ -22,7 +22,10 @@ I/O on the command-line.
import getopt
import sys
MAIN_OPTS = "ac:d:ht:v"
from topydo.lib.Color import AbstractColor, Color
from topydo.lib.TopydoString import TopydoString
MAIN_OPTS = "ac:C:d:ht:v"
READ_ONLY_COMMANDS = ('List', 'ListContext', 'ListProject')
......@@ -30,12 +33,14 @@ def usage():
""" Prints the command-line usage of topydo. """
print("""\
Synopsis: topydo [-a] [-c <config>] [-d <archive>] [-t <todo.txt>] subcommand [help|args]
Synopsis: topydo [-a] [-c <config>] [-C <colormode>] [-d <archive>] [-t <todo.txt>] subcommand [help|args]
topydo -h
topydo -v
-a : Do not archive todo items on completion.
-c : Specify an alternative configuration file.
-C : Specify color mode (0 = disable, 1 = enable 16 colors,
16 = enable 16 colors, 256 = enable 256 colors, auto (default))
-d : Specify an alternative archive file (done.txt)
-h : This help text
-t : Specify an alternative todo file
......@@ -62,20 +67,59 @@ Available commands:
Run `topydo help <subcommand>` for command-specific help.\
""")
def write(p_file, p_string):
"""
Write p_string to file p_file, trailed by a newline character.
ANSI codes are removed when the file is not a TTY.
ANSI codes are removed when the file is not a TTY (and colors are
automatically determined).
"""
if not p_file.isatty():
if not config().colors(p_file.isatty()):
p_string = escape_ansi(p_string)
if p_string:
p_file.write(p_string + "\n")
def lookup_color(p_color):
"""
Converts an AbstractColor to a normal Color. Returns the Color itself
when a normal color is passed.
"""
if not lookup_color.colors:
lookup_color.colors[AbstractColor.NEUTRAL] = Color('NEUTRAL')
lookup_color.colors[AbstractColor.PROJECT] = config().project_color()
lookup_color.colors[AbstractColor.CONTEXT] = config().context_color()
lookup_color.colors[AbstractColor.META] = config().metadata_color()
lookup_color.colors[AbstractColor.LINK] = config().link_color()
try:
return lookup_color.colors[p_color]
except KeyError:
return p_color
lookup_color.colors = {}
def insert_ansi(p_string):
""" Returns a string with color information at the right positions. """
result = p_string.data
for pos, color in sorted(p_string.colors.items(), reverse=True):
color = lookup_color(color)
result = result[:pos] + color.as_ansi() + result[pos:]
return result
def output(p_string):
if isinstance(p_string, list):
p_string = "\n".join([insert_ansi(s) for s in p_string])
elif isinstance(p_string, TopydoString):
# convert color codes to ANSI
p_string = insert_ansi(p_string)
write(sys.stdout, p_string)
def error(p_string):
""" Writes an error on the standard error. """
write(sys.stderr, p_string)
......@@ -140,6 +184,9 @@ class CLIApplicationBase(object):
self.do_archive = False
elif opt == "-c":
alt_config_path = value
elif opt == "-C":
overrides[('topydo', 'force_colors')] = '1'
overrides[('topydo', 'colors')] = value
elif opt == "-t":
overrides[('topydo', 'filename')] = value
elif opt == "-d":
......@@ -174,7 +221,7 @@ class CLIApplicationBase(object):
command = ArchiveCommand(self.todolist, archive)
command.execute()
if archive.is_dirty():
if archive.dirty:
archive_file.write(archive.print_todos())
def _help(self, args):
......@@ -189,21 +236,24 @@ class CLIApplicationBase(object):
READ_ONLY_COMMANDS)
return p_command.__module__.endswith(read_only_commands)
def _execute(self, p_command, p_args):
"""
Execute a subcommand with arguments. p_command is a class (not an
object).
"""
def _backup(self, p_command, p_args):
if config().backup_count() > 0 and p_command and not self.is_read_only(p_command):
call = [p_command.__module__.lower()[16:-7]] + p_args # strip "topydo.commands" and "Command"
from topydo.lib.ChangeSet import ChangeSet
self.backup = ChangeSet(self.todolist, p_call=call)
def _execute(self, p_command, p_args):
"""
Execute a subcommand with arguments. p_command is a class (not an
object).
"""
self._backup(p_command, p_args)
command = p_command(
p_args,
self.todolist,
lambda o: write(sys.stdout, o),
output,
error,
input)
......@@ -219,7 +269,7 @@ class CLIApplicationBase(object):
to the todo.txt file.
"""
if self.todolist.is_dirty():
if self.todolist.dirty:
# do not archive when the value of the filename is an empty string
# (i.e. explicitly left empty in the configuration
if self.do_archive and config().archive():
......@@ -233,6 +283,7 @@ class CLIApplicationBase(object):
self.backup.save(self.todolist)
self.todofile.write(self.todolist.print_todos())
self.todolist.dirty = False
self.backup = None
......
......@@ -101,6 +101,7 @@ class PromptApplication(CLIApplicationBase):
continue
except ValueError as verr:
error('Error: ' + str(verr))
continue
mtime_after = _todotxt_mtime()
......
......@@ -45,6 +45,12 @@ def main():
PromptApplication().run()
except ImportError:
error("You have to install prompt-toolkit to run prompt mode.")
elif args[0] == 'columns':
try:
from topydo.ui.Main import UIApplication
UIApplication().run()
except ImportError:
error("You have to install urwid to run column mode.")
else:
CLIApplication().run()
except IndexError:
......
......@@ -69,7 +69,7 @@ class PostponeCommand(MultiCommand):
# pylint: disable=E1103
todo.set_tag(config().tag_due(), new_due.isoformat())
self.todolist.set_dirty()
self.todolist.dirty = True
self.out(self.printer.print_todo(todo))
else:
self.error("Invalid date pattern given.")
......
......@@ -106,7 +106,7 @@ class TagCommand(Command):
self.todo.set_tag(self.tag, self.value, self.force_add, p_old_value)
if old_src != self.todo.source():
self.todolist.set_dirty()
self.todolist.dirty = True
def _set(self):
if len(self.current_values) > 1 and not self.force_add:
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2016 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" This module provides a class that represents a color. """
class AbstractColor:
NEUTRAL = 0
PROJECT = 1
CONTEXT = 2
META = 3
LINK = 4
class Color:
color_names_dict = {
'black': 0,
'red': 1,
'green': 2,
'yellow': 3,
'blue': 4,
'magenta': 5,
'cyan': 6,
'gray': 7,
'darkgray': 8,
'light-red': 9,
'light-green': 10,
'light-yellow': 11,
'light-blue': 12,
'light-magenta': 13,
'light-cyan': 14,
'white': 15,
}
def __init__(self, p_value=None):
""" p_value is user input, be it a word color or an xterm code """
self._value = None
self.color = p_value
@property
def color(self):
return self._value
@color.setter
def color(self, p_value):
try:
if not p_value:
self._value = None
elif p_value in Color.color_names_dict:
self._value = Color.color_names_dict[p_value]
else:
self._value = int(p_value)
# values not in the 256 range are normalized to be neutral
if not 0 <= self._value < 256:
raise ValueError
except ValueError:
# garbage was entered, make it neutral, so at least some
# highlighting may take place
self._value = -1
def is_neutral(self):
"""
A neutral color is the default color on the shell, setting this color
will reset all other attributes (background, foreground, decoration).
"""
return self._value == -1
def is_valid(self):
"""
Whether the color is a valid color.
"""
return self._value is not None
def as_ansi(self, p_decoration='normal', p_background=False):
if not self.is_valid():
return ''
elif self.is_neutral():
return '\033[0m'
is_high_color = 8 <= self._value < 16
is_256 = 16 <= self._value < 255
decoration_dict = {
'normal': '0',
'bold': '1',
'faint': '2',
'italic': '3',
'underline': '4',
}
decoration = decoration_dict[p_decoration]
base = 40 if p_background else 30
if is_high_color:
color = '1;{}'.format(base + self._value - 8)
elif is_256:
color = '{};5;{}'.format(base + 8, self._value)
else:
# it's a low color
color = str(base + self._value)
return '\033[{};{}m'.format(
decoration,
color
)
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" This module serves for managing output colors. """
from topydo.lib.Config import config
NEUTRAL_COLOR = '\033[0m'
class Colors(object):
def __init__(self):
self.priority_colors = config().priority_colors()
self.project_color = config().project_color()
self.context_color = config().context_color()
self.metadata_color = config().metadata_color()
self.link_color = config().link_color()
def _int_to_ansi(self, p_int, p_decorator='normal', p_safe=True):
"""
Returns ansi code for color based on xterm color id (0-255) and
decoration, where decoration can be one of: normal, bold, faint,
italic, or underline. When p_safe is True, resulting ansi code is
constructed in most compatible way, but with support for only base 16
colors.
"""
decoration_dict = {
'normal': '0',
'bold': '1',
'faint': '2',
'italic': '3',
'underline': '4'
}
decoration = decoration_dict[p_decorator]
try:
if p_safe:
if 8 > int(p_int) >= 0:
return '\033[{};3{}m'.format(decoration, str(p_int))
elif 16 > int(p_int):
p_int = int(p_int) - 8
return '\033[{};1;3{}m'.format(decoration, str(p_int))
if 256 > int(p_int) >= 0:
return '\033[{};38;5;{}m'.format(decoration, str(p_int))
else:
return NEUTRAL_COLOR
except ValueError:
return None
def _name_to_int(self, p_color_name):
""" Returns xterm color id from color name. """
color_names_dict = {
'black': 0,
'red': 1,
'green': 2,
'yellow': 3,
'blue': 4,
'magenta': 5,
'cyan': 6,
'gray': 7,
'darkgray': 8,
'light-red': 9,
'light-green': 10,
'light-yellow': 11,
'light-blue': 12,
'light-magenta': 13,
'light-cyan': 14,
'white': 15,
}
try:
return color_names_dict[p_color_name]
except KeyError:
return 404
def _name_to_ansi(self, p_color_name, p_decorator):
""" Returns ansi color code from color name. """
number = self._name_to_int(p_color_name)
return self._int_to_ansi(number, p_decorator)
def _get_ansi(self, p_color, p_decorator):
""" Returns ansi color code from color name or xterm color id. """
if p_color == '':
ansi = ''
else:
ansi = self._int_to_ansi(p_color, p_decorator, False)
if not ansi:
ansi = self._name_to_ansi(p_color, p_decorator)
return ansi
def get_priority_colors(self):
pri_ansi_colors = dict()
for pri in self.priority_colors:
color = self._get_ansi(self.priority_colors[pri], 'normal')
if color == '':
color = NEUTRAL_COLOR
pri_ansi_colors[pri] = color
return pri_ansi_colors
def get_project_color(self):
return self._get_ansi(self.project_color, 'bold')
def get_context_color(self):
return self._get_ansi(self.context_color, 'bold')
def get_metadata_color(self):
return self._get_ansi(self.metadata_color, 'bold')
def get_link_color(self):
return self._get_ansi(self.link_color, 'underline')
......@@ -16,8 +16,16 @@
import configparser
import os
import re
import shlex
from itertools import accumulate
from string import ascii_lowercase
from topydo.lib.Color import Color
def home_config_path(p_filename):
return os.path.join(os.path.expanduser('~'), p_filename)
class ConfigError(Exception):
def __init__(self, p_text):
......@@ -54,7 +62,8 @@ class _Config:
self.defaults = {
'topydo': {
'default_command': 'ls',
'colors': '1',
'colors': 'auto',
'force_colors': '0',
'filename': 'todo.txt',
'archive_filename': 'done.txt',
'identifiers': 'linenumber',
......@@ -107,11 +116,43 @@ class _Config:
'listcontext': 'lscon',
'listcontexts': 'lscon',
},
'column_keymap': {
'gg': 'home',
'G': 'end',
'j': 'down',
'k': 'up',
'd': 'cmd del {}',
'e': 'cmd edit {}',
'u': 'cmd revert',
'x': 'cmd do {}',
'm': 'mark',
'.': 'repeat',
'pp': 'postpone',
'ps': 'postpone_s',
'pr': 'pri',
'0': 'first_column',
'$': 'last_column',
'h': 'prev_column',
'l': 'next_column',
'A': 'append_column',
'I': 'insert_column',
'E': 'edit_column',
'D': 'delete_column',
'Y': 'copy_column',
'L': 'swap_left',
'R': 'swap_right',
'<Left>': 'prev_column',
'<Right>': 'next_column',
'<Esc>': 'reset',
},
}
self.config = {}
self.cp = configparser.RawConfigParser()
# allow uppercase config keys
self.cp.optionxform = lambda option: option
for section in self.defaults:
self.cp.add_section(section)
......@@ -121,7 +162,8 @@ class _Config:
files = [
"/etc/topydo.conf",
self._home_config_path(),
home_config_path('.config/topydo/config'),
home_config_path('.topydo'),
".topydo",
"topydo.conf",
"topydo.ini",
......@@ -144,17 +186,44 @@ class _Config:
if not self.cp.has_section(section):
self.cp.add_section(section)
def _home_config_path(self):
return os.path.join(os.path.expanduser('~'), '.topydo')
def default_command(self):
return self.cp.get('topydo', 'default_command')
def colors(self):
def colors(self, p_hint_possible=True):
"""
Returns 0, 16 or 256 representing the number of colors that should be
used in the output.
A hint can be passed whether the device that will output the text
supports colors.
"""
lookup = {
'false': 0,
'no': 0,
'0': 0,
'1': 16,
'true': 16,
'yes': 16,
'16': 16,
'256': 256,
}
try:
return self.cp.getboolean('topydo', 'colors')
forced = self.cp.get('topydo', 'force_colors') == '1'
except ValueError:
return self.defaults['topydo']['colors'] == '1'
forced = self.defaults['topydo']['force_colors'] == '1'
try:
colors = lookup[self.cp.get('topydo', 'colors').lower()] # pylint: disable=no-member
except ValueError:
colors = lookup[self.defaults['topydo']['colors'].lower()] # pylint: disable=no-member
except KeyError:
# for invalid values or 'auto'
colors = 16 if p_hint_possible else 0
# disable colors when no colors are enforced on the commandline and
# color support is determined automatically
return 0 if not forced and not p_hint_possible else colors
def todotxt(self):
return os.path.expanduser(self.cp.get('topydo', 'filename'))
......@@ -237,53 +306,53 @@ class _Config:
return [] if hidden_tags == '' else [tag.strip() for tag in
hidden_tags.split(',')]
def priority_colors(self):
def priority_color(self, p_priority):
"""
Returns a dict with priorities as keys and color numbers as value.
"""
pri_colors_str = self.cp.get('colorscheme', 'priority_colors')
def _str_to_dict(p_string):
pri_colors_dict = dict()
for pri_color in p_string.split(','):
pri, color = pri_color.split(':')
pri_colors_dict[pri] = color
pri_colors_dict[pri] = Color(color)
return pri_colors_dict
try:
pri_colors_str = self.cp.get('colorscheme', 'priority_colors')
if pri_colors_str == '':
pri_colors_dict = {'A': '', 'B': '', 'C': ''}
pri_colors_dict = _str_to_dict('A:-1,B:-1,C:-1')
else:
pri_colors_dict = _str_to_dict(pri_colors_str)
except ValueError:
pri_colors_dict = _str_to_dict(self.defaults['colorscheme']['priority_colors'])
return pri_colors_dict
return pri_colors_dict[p_priority] if p_priority in pri_colors_dict else Color('NEUTRAL')
def project_color(self):
try:
return self.cp.get('colorscheme', 'project_color')
return Color(self.cp.getint('colorscheme', 'project_color'))
except ValueError:
return int(self.defaults['colorscheme']['project_color'])
return Color(self.cp.get('colorscheme', 'project_color'))
def context_color(self):
try:
return self.cp.get('colorscheme', 'context_color')
return Color(self.cp.getint('colorscheme', 'context_color'))
except ValueError:
return int(self.defaults['colorscheme']['context_color'])
return Color(self.cp.get('colorscheme', 'context_color'))
def metadata_color(self):
try:
return self.cp.get('colorscheme', 'metadata_color')
return Color(self.cp.getint('colorscheme', 'metadata_color'))
except ValueError:
return int(self.defaults['colorscheme']['metadata_color'])
return Color(self.cp.get('colorscheme', 'metadata_color'))
def link_color(self):
try:
return self.cp.get('colorscheme', 'link_color')
return Color(self.cp.getint('colorscheme', 'link_color'))
except ValueError:
return int(self.defaults['colorscheme']['link_color'])
return Color(self.cp.get('colorscheme', 'link_color'))
def auto_creation_date(self):
try:
......@@ -314,6 +383,28 @@ class _Config:
""" Returns the list format used by `ls` """
return self.cp.get('ls', 'list_format')
def column_keymap(self):
""" Returns keymap and keystates used in column mode """
keystates = set()
shortcuts = self.cp.items('column_keymap')
keymap_dict = dict(shortcuts)
for combo, action in shortcuts:
# add all possible prefixes to keystates
combo_as_list = re.split('(<[A-Z].+?>|.)', combo)[1::2]
if len(combo_as_list) > 1:
keystates |= set(accumulate(combo_as_list[:-1]))
if action in ['pri', 'postpone', 'postpone_s']:
keystates.add(combo)
if action == 'pri':
for c in ascii_lowercase:
keymap_dict[combo + c] = 'cmd pri {} ' + c
return (keymap_dict, keystates)
def config(p_path=None, p_overrides=None):
"""
......
......@@ -45,40 +45,12 @@ class ExpressionCommand(Command):
def _filters(self):
filters = []
def arg_filters():
result = []
if self.last_argument:
args = self.args[:-1]
else:
args = self.args
for arg in args:
# when a word starts with -, it should be negated
is_negated = len(arg) > 1 and arg[0] == '-'
arg = arg[1:] if is_negated else arg
argfilter = None
for match, _filter in Filter.MATCHES:
if re.match(match, arg):
argfilter = _filter(arg)
break
if not argfilter:
argfilter = Filter.GrepFilter(arg)
if is_negated:
argfilter = Filter.NegationFilter(argfilter)
result.append(argfilter)
return result
if not self.show_all:
filters.append(Filter.DependencyFilter(self.todolist))
filters.append(Filter.RelevanceFilter())
filters += arg_filters()
args = self.args[:-1] if self.last_argument else self.args
filters += Filter.get_filter_list(args)
if not self.show_all:
filters.append(Filter.LimitFilter(self.limit))
......
......@@ -318,3 +318,32 @@ MATCHES = [
(_ORDINAL_TAG_MATCH, OrdinalTagFilter),
(_PRIORITY_MATCH, PriorityFilter),
]
def get_filter_list(p_expression):
"""
Returns a list of GrepFilters, OrdinalTagFilters or NegationFilters based
on the given filter expression.
The filter expression is a list of strings.
"""
result = []
for arg in p_expression:
# when a word starts with -, it should be negated
is_negated = len(arg) > 1 and arg[0] == '-'
arg = arg[1:] if is_negated else arg
argfilter = None
for match, _filter in MATCHES:
if re.match(match, arg):
argfilter = _filter(arg)
break
if not argfilter:
argfilter = GrepFilter(arg)
if is_negated:
argfilter = NegationFilter(argfilter)
result.append(argfilter)
return result
......@@ -112,7 +112,7 @@ class IcalPrinter(Printer):
if not uid:
uid = generate_uid()
p_todo.set_tag('ical', uid)
self.todolist.set_dirty()
self.todolist.dirty = True
return uid
......
......@@ -20,7 +20,8 @@ import arrow
import re
from topydo.lib.Config import config
from topydo.lib.Utils import get_terminal_size
from topydo.lib.ProgressColor import progress_color
from topydo.lib.Utils import get_terminal_size, escape_ansi
MAIN_PATTERN = (r'^({{(?P<before>.+?)}})?'
r'(?P<placeholder>{ph}|\[{ph}\])'
......@@ -107,7 +108,7 @@ def _truncate(p_str, p_repl):
Place of the truncation is calculated depending on p_max_width.
"""
# 4 is for '...' and an extra space at the end
text_lim = _columns() - len(p_str) - 4
text_lim = _columns() - len(escape_ansi(p_str)) - 4
truncated_str = re.sub(re.escape(p_repl), p_repl[:text_lim] + '...', p_str)
return truncated_str
......@@ -119,7 +120,7 @@ def _right_align(p_str):
Right alignment is done using proper number of spaces calculated from
'line_width' attribute.
"""
to_fill = _columns() - len(p_str)
to_fill = _columns() - len(escape_ansi(p_str))
if to_fill > 0:
p_str = re.sub('\t', ' '*to_fill, p_str)
......@@ -128,6 +129,12 @@ def _right_align(p_str):
return p_str
def color_block(p_todo):
return '{} {}'.format(
progress_color(p_todo).as_ansi(p_background=True),
config().priority_color(p_todo.priority()).as_ansi(),
)
class ListFormatParser(object):
""" Parser of format string. """
def __init__(self, p_todolist, p_format=None):
......@@ -196,6 +203,8 @@ class ListFormatParser(object):
# relative completion date
'X': lambda t: 'x ' + humanize_date(t.completion_date()) if t.is_completed() else '',
'z': lambda t: color_block(t) if config().colors() else ' ',
}
self.format_list = self._preprocess_format()
......@@ -272,7 +281,7 @@ class ListFormatParser(object):
parsed_str = _unescape_percent_sign(''.join(parsed_list))
parsed_str = _remove_redundant_spaces(parsed_str)
if self.one_line and len(parsed_str) >= _columns():
if self.one_line and len(escape_ansi(parsed_str)) >= _columns():
parsed_str = _truncate(parsed_str, repl_trunc)
if re.search('.*\t', parsed_str):
......
......@@ -16,6 +16,7 @@
from topydo.lib.prettyprinters.Colors import PrettyPrinterColorFilter
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.TopydoString import TopydoString
class Printer(object):
......@@ -29,11 +30,8 @@ class Printer(object):
raise NotImplementedError
def print_list(self, p_todos):
"""
Given a list of todo items, pretty print it and return a list of
formatted strings.
"""
return "\n".join([self.print_todo(todo) for todo in p_todos])
for todo in p_todos:
self.print_todo(todo)
class PrettyPrinter(Printer):
......@@ -68,7 +66,15 @@ class PrettyPrinter(Printer):
for ppf in self.filters:
todo_str = ppf.filter(todo_str, p_todo)
return todo_str
return TopydoString(todo_str)
def print_list(self, p_todos):
"""
Given a list of todo items, pretty print it and return a list of
formatted TopydoStrings. The output function in the UI should convert
the colors inside properly.
"""
return [self.print_todo(todo) for todo in p_todos]
def pretty_printer_factory(p_todolist, p_additional_filters=None):
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2016 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
from topydo.lib.Color import Color
from topydo.lib.Config import config
from topydo.lib.Recurrence import relative_date_to_date
# when a todo item has not enough information to determine the length, assume
# this length
ASSUMED_TODO_LENGTH = 14 # days
def progress_color(p_todo):
color16_range = [
2, # green
10, # light green
3, # yellow
1, # red
]
# https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
# a gradient from green to yellow to red
color256_range = \
[22, 28, 34, 40, 46, 82, 118, 154, 190, 226, 220, 214, 208, 202, 196]
def get_length():
"""
Returns the length of the p_todo item in days, based on the recurrence
period + due date, or the start/due date.
"""
result = 0
def diff_days(p_start, p_end):
"""
Returns the difference in days between p_start and p_end, where
start is before due.
"""
diff = p_end - p_start
return diff.days
does_recur = p_todo.has_tag('rec')
start_date = p_todo.start_date()
due_date = p_todo.due_date()
creation_date = p_todo.creation_date()
if does_recur and due_date and not start_date:
# add negation, offset is based on due date
recurrence_pattern = p_todo.tag_value('rec')
neg_recurrence_pattern = re.sub('^\+?', '-', recurrence_pattern)
start = relative_date_to_date(neg_recurrence_pattern, due_date)
result = diff_days(start, due_date)
elif due_date and not start_date and not creation_date:
result = ASSUMED_TODO_LENGTH
elif due_date and start_date and due_date < start_date:
result = ASSUMED_TODO_LENGTH
elif due_date and not start_date and creation_date and due_date < creation_date:
result = ASSUMED_TODO_LENGTH
else:
result = p_todo.length()
# a todo item is at least one day long
return max(1, result)
def get_progress():
"""
Returns a value from 0 to 1 where we are today in a date range. Returns
a value >1 when a todo item is overdue.
"""
if p_todo.is_overdue():
return 1.1
elif p_todo.due_date():
days_till_due = p_todo.days_till_due()
length = get_length()
return max((length - days_till_due), 0) / length
else:
return 0
color_range = color256_range if config().colors() == 256 else color16_range
progress = get_progress()
# TODO: remove linear scale to exponential scale
if progress > 1:
# overdue, return the last color
return Color(color_range[-1])
else:
# not overdue, calculate position over color range excl. due date
# color
pos = round(progress * (len(color_range) - 2))
return Color(color_range[pos])
......@@ -52,7 +52,7 @@ class TodoListBase(object):
self._id_todo_map = {}
self.add_list(p_todostrings)
self.dirty = False
self._dirty = False
def __iter__(self):
"""
......@@ -227,11 +227,13 @@ class TodoListBase(object):
"""
return View(p_sorter, p_filters, self)
def is_dirty(self):
return self.dirty
@property
def dirty(self):
return self._dirty
def set_dirty(self):
self.dirty = True
@dirty.setter
def dirty(self, p_flag):
self._dirty = p_flag
def todos(self):
return self._todos
......@@ -277,4 +279,4 @@ class TodoListBase(object):
this list.
"""
printer = PrettyPrinter()
return printer.print_list(self._todos)
return "\n".join([str(s) for s in printer.print_list(self._todos)])
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2016 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" This module provides TopydoString to embed colors in a string. """
import collections
class TopydoString(collections.UserString):
"""
Represents a string that also contains color information. A combination of
(position, color) is maintained, where the position is the start position
where a certain color should start.
"""
def __init__(self, p_content, p_metadata=None):
if isinstance(p_content, TopydoString):
# don't nest topydostrings
self.colors = p_content.colors
self.metadata = p_content.metadata
super().__init__(p_content.data)
else:
self.colors = {}
super().__init__(p_content)
# allows clients to pass arbitrary data with this string (e.g. a Todo
# object)
self.metadata = p_metadata
def append(self, p_string, p_color):
"""
Append a string with the given color (normal Color or an
AbstractColor).
"""
self.colors[len(self.data)] = p_color
self.data += p_string
def set_color(self, p_pos, p_color):
""" Start using a color at the given position. """
self.colors[p_pos] = p_color
......@@ -23,12 +23,6 @@ import re
from collections import namedtuple
from datetime import date
# shutil.get_terminal_size was added to the standard library in Python 3.3
try:
from shutil import get_terminal_size as _get_terminal_size # pylint: disable=no-name-in-module
except ImportError:
from backports.shutil_get_terminal_size import get_terminal_size as _get_terminal_size # pylint: disable=import-error
def date_string_to_date(p_date):
"""
......@@ -61,19 +55,57 @@ def escape_ansi(p_string):
escape_ansi.pattern = re.compile(r'\x1b[^m]*m')
def get_terminal_size():
def get_terminal_size(p_getter=None):
"""
Try to determine terminal size at run time. If that is not possible,
returns the default size of 80x24.
By default, the size is determined with provided get_terminal_size by
shutil. Sometimes an UI may want to specify the desired width, then it can
provide a getter that returns a named tuple (columns, lines) with the size.
"""
try:
sz = _get_terminal_size()
except ValueError:
"""
This can result from the 'underlying buffer being detached', which
occurs during running the unittest on Windows (but not on Linux?)
"""
terminal_size = namedtuple('Terminal_Size', 'columns lines')
sz = terminal_size(80, 24)
return sz
return get_terminal_size.getter()
except AttributeError:
if p_getter:
get_terminal_size.getter = p_getter
else:
def inner():
try:
# shutil.get_terminal_size was added to the standard
# library in Python 3.3
try:
from shutil import get_terminal_size as _get_terminal_size # pylint: disable=no-name-in-module
except ImportError:
from backports.shutil_get_terminal_size import get_terminal_size as _get_terminal_size # pylint: disable=import-error
sz = _get_terminal_size()
except ValueError:
"""
This can result from the 'underlying buffer being detached', which
occurs during running the unittest on Windows (but not on Linux?)
"""
terminal_size = namedtuple('Terminal_Size', 'columns lines')
sz = terminal_size(80, 24)
return sz
get_terminal_size.getter = inner
return get_terminal_size.getter()
def translate_key_to_config(p_key):
"""
Translates urwid key event to form understandable by topydo config parser.
"""
if len(p_key) > 1:
key = p_key.capitalize()
if key.startswith('Ctrl') or key.startswith('Meta'):
key = key[0] + '-' + key[5:]
key = '<' + key + '>'
else:
key = p_key
return key
......@@ -25,14 +25,14 @@ class View(object):
"""
def __init__(self, p_sorter, p_filters, p_todolist):
self._todolist = p_todolist
self.todolist = p_todolist
self._sorter = p_sorter
self._filters = p_filters
@property
def todos(self):
""" Returns a sorted and filtered list of todos in this view. """
result = self._sorter.sort(self._todolist.todos())
result = self._sorter.sort(self.todolist.todos())
for _filter in self._filters:
result = _filter.filter(result)
......
......@@ -18,9 +18,10 @@
import re
from topydo.lib.Colors import NEUTRAL_COLOR, Colors
from topydo.lib.Color import AbstractColor
from topydo.lib.Config import config
from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter
from topydo.lib.TopydoString import TopydoString
class PrettyPrinterColorFilter(PrettyPrinterFilter):
......@@ -33,41 +34,26 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
def filter(self, p_todo_str, p_todo):
""" Applies the colors. """
if config().colors():
colorscheme = Colors()
priority_colors = colorscheme.get_priority_colors()
project_color = colorscheme.get_project_color()
context_color = colorscheme.get_context_color()
metadata_color = colorscheme.get_metadata_color()
link_color = colorscheme.get_link_color()
p_todo_str = TopydoString(p_todo_str, p_todo)
priority_color = NEUTRAL_COLOR
try:
priority_color = priority_colors[p_todo.priority()]
except KeyError:
pass
priority_color = config().priority_color(p_todo.priority())
# color projects / contexts
p_todo_str = re.sub(
r'\B(\+|@)(\S*\w)',
lambda m: (
context_color if m.group(0)[0] == "@"
else project_color) + m.group(0) + priority_color,
p_todo_str)
colors = [
(r'\B@(\S*\w)', AbstractColor.CONTEXT),
(r'\B\+(\S*\w)', AbstractColor.PROJECT),
(r'\b\S+:[^/\s]\S*\b', AbstractColor.META),
(r'(^|\s)(\w+:){1}(//\S+)', AbstractColor.LINK),
]
# tags
p_todo_str = re.sub(r'\b\S+:[^/\s]\S*\b',
metadata_color + r'\g<0>' + priority_color,
p_todo_str)
for pattern, color in colors:
for match in re.finditer(pattern, p_todo_str.data):
p_todo_str.set_color(match.start(), color)
p_todo_str.set_color(match.end(), priority_color)
# add link_color to any valid URL specified outside of the tag.
p_todo_str = re.sub(r'(^|\s)(\w+:){1}(//\S+)',
r'\1' + link_color + r'\2\3' + priority_color,
p_todo_str)
p_todo_str += NEUTRAL_COLOR
p_todo_str.append('', AbstractColor.NEUTRAL)
# color by priority
p_todo_str = priority_color + p_todo_str
p_todo_str.set_color(0, priority_color)
return p_todo_str
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import configparser
from topydo.lib.Config import home_config_path, config
def columns():
"""
Returns list with complete column configuration dicts.
"""
def _get_column_dict(p_cp, p_column):
column_dict = dict()
column_dict['title'] = p_cp.get(p_column, 'title')
column_dict['filterexpr'] = p_cp.get(p_column, 'filterexpr')
column_dict['sortexpr'] = p_cp.get(p_column, 'sortexpr')
column_dict['show_all'] = p_cp.getboolean(p_column, 'show_all')
return column_dict
defaults = {
'title': 'Yet another column',
'filterexpr': '',
'sortexpr': config().sort_string(),
'show_all': '0',
}
cp = configparser.RawConfigParser(defaults)
files = [
"topydo_columns.ini",
"topydo_columns.conf",
".topydo_columns",
home_config_path('.topydo_columns'),
home_config_path('.config/topydo/columns'),
"/etc/topydo_columns.conf",
]
for filename in files:
if cp.read(filename):
break
column_list = []
for column in cp.sections():
column_list.append(_get_column_dict(cp, column))
return column_list
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urwid
class CommandLineWidget(urwid.Edit):
def __init__(self, *args, **kwargs):
self.history = []
self.history_pos = None
# temporary history storage for edits before cmd execution
self.history_tmp = []
super().__init__(*args, **kwargs)
urwid.register_signal(CommandLineWidget, ['blur', 'execute_command'])
def clear(self):
self.set_edit_text("")
def _blur(self):
self.clear()
urwid.emit_signal(self, 'blur')
def _emit_command(self):
if len(self.edit_text) > 0:
urwid.emit_signal(self, 'execute_command', self.edit_text)
self._save_to_history()
self.clear()
def _save_to_history(self):
if len(self.edit_text) > 0:
self.history.append(self.edit_text)
self.history_pos = len(self.history)
self.history_tmp = self.history[:] # sync temporary storage with real history
self.history_tmp.append('')
def _history_move(self, p_step):
"""
Changes current value of the command-line to the value obtained from
history_tmp list with index calculated by addition of p_step to the
current position in the command history (history_pos attribute).
Also saves value of the command-line (before changing it) to history_tmp
for potential later access.
"""
if len(self.history) > 0:
# don't pollute real history - use temporary storage
self.history_tmp[self.history_pos] = self.edit_text
self.history_pos = self.history_pos + p_step
self.set_edit_text(self.history_tmp[self.history_pos])
def _history_next(self):
if self.history_pos != len(self.history):
self._history_move(1)
def _history_back(self):
if self.history_pos != 0:
self._history_move(-1)
def keypress(self, p_size, p_key):
dispatch = {
'enter': self._emit_command,
'esc': self._blur,
'up': self._history_back,
'down': self._history_next
}
try:
dispatch[p_key]()
except KeyError:
super().keypress(p_size, p_key)
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urwid
from topydo.lib.Color import AbstractColor
from topydo.lib.Todo import Todo
from topydo.lib.TopydoString import TopydoString
from topydo.ui.Utils import PaletteItem
PALETTE_LOOKUP = {
# omitting AbstractColor.NEUTRAL on purpose, so a text without any
# attribute will be added to the markup
AbstractColor.PROJECT: PaletteItem.PROJECT,
AbstractColor.CONTEXT: PaletteItem.CONTEXT,
AbstractColor.META: PaletteItem.METADATA,
AbstractColor.LINK: PaletteItem.LINK,
}
def topydostringToMarkup(p_string):
markup = []
color_positions = sorted(p_string.colors.items())
# in case no color positions are available, at least set something at the
# start position
if not color_positions:
color_positions = [(0, None)]
for i, (start_pos, color) in enumerate(color_positions):
# color starts at indicated position
start = start_pos
# color ends at next color indication. if missing, run until the end of
# the string
try:
end = color_positions[i+1][0]
except IndexError:
end = len(str(p_string))
text = str(p_string)[start:end]
if color in PALETTE_LOOKUP:
markup.append((PALETTE_LOOKUP[color], text))
else:
# a plain text without any attribute set (including
# AbstractColor.NEUTRAL)
markup.append(text)
color_at_start = color_positions and color_positions[0][0] == 0
# priority color should appear at the start if present, build a nesting
# markup
if color_at_start and isinstance(p_string.metadata, Todo):
priority = p_string.metadata.priority()
markup = ('pri_' + priority, markup)
return markup
class ConsoleWidget(urwid.LineBox):
def __init__(self, p_text=""):
urwid.register_signal(ConsoleWidget, ['close'])
self.width = 0
self.pile = urwid.Pile([])
super().__init__(self.pile)
def keypress(self, p_size, p_key):
if p_key == 'enter' or p_key == 'q' or p_key == 'esc':
urwid.emit_signal(self, 'close')
# don't return the key, 'enter', 'escape', 'q' or ':' are your only
# escape. ':' will reenter to the cmdline.
elif p_key == ':':
urwid.emit_signal(self, 'close', True)
def render(self, p_size, focus):
"""
This override intercepts the width of the widget such that it can be
stored. The width is used for rendering `ls` output.
"""
self.width = p_size[0]
return super().render(p_size, focus)
def selectable(self):
return True
def print_text(self, p_text):
if isinstance(p_text, list):
for text in p_text:
self.print_text(text)
return
elif isinstance(p_text, TopydoString):
text = urwid.Text(topydostringToMarkup(p_text))
else:
text = urwid.Text(p_text)
self.pile.contents.append((text, ('pack', None)))
def clear(self):
self.pile.contents = []
def console_width(self):
# return the last known width (last render)
return self.width
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urwid
class KeystateWidget(urwid.Text):
def __init__(self):
super().__init__('', align='right')
def selectable(self):
return False
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import shlex
import time
import urwid
from collections import namedtuple
from string import ascii_uppercase
from topydo.cli.CLIApplicationBase import CLIApplicationBase
from topydo.Commands import get_subcommand
from topydo.ui.CommandLineWidget import CommandLineWidget
from topydo.ui.ConsoleWidget import ConsoleWidget
from topydo.ui.KeystateWidget import KeystateWidget
from topydo.ui.TodoListWidget import TodoListWidget
from topydo.ui.Utils import PaletteItem, to_urwid_color
from topydo.ui.ViewWidget import ViewWidget
from topydo.ui.ColumnLayout import columns
from topydo.lib.Config import config, ConfigError
from topydo.lib.Sorter import Sorter
from topydo.lib.Filter import get_filter_list, RelevanceFilter, DependencyFilter
from topydo.lib.Utils import get_terminal_size
from topydo.lib.View import View
from topydo.lib import TodoFile
from topydo.lib import TodoList
COLUMN_WIDTH = 40
class UIView(View):
"""
A subclass of view holding user input data that constructed the view (i.e.
the sort expression and the filter expression, etc.)
"""
def __init__(self, p_sorter, p_filter, p_todolist, p_data):
super().__init__(p_sorter, p_filter, p_todolist)
self.data = p_data
_APPEND_COLUMN = 1
_EDIT_COLUMN = 2
_COPY_COLUMN = 3
_INSERT_COLUMN = 4
class MainPile(urwid.Pile):
"""
This subclass of Pile doesn't change focus on cursor up/down / mouse press
events. The implementation was taken from its base class.
"""
def __init__(self, p_widget_list, p_focus_item=None):
urwid.register_signal(MainPile, ['blur_console'])
super().__init__(p_widget_list, p_focus_item)
def mouse_event(self, p_size, p_event, p_button, p_col, p_row, p_focus):
if self.focus_position != 2:
urwid.emit_signal(self, 'blur_console')
return super().mouse_event(p_size, p_event, p_button, p_col, p_row, p_focus) # pylint: disable=E1102
def keypress(self, p_size, p_key):
if not self.contents:
return p_key
item_rows = None
if len(p_size) == 2:
item_rows = self.get_item_rows(p_size, focus=True)
i = self.focus_position
if self.selectable():
tsize = self.get_item_size(p_size, i, True, item_rows)
key = self.focus.keypress(tsize, p_key)
if self._command_map[key] not in ('cursor up', 'cursor down'):
return key
class UIApplication(CLIApplicationBase):
def __init__(self):
super().__init__()
self._process_flags()
self.todofile = TodoFile.TodoFile(config().todotxt())
self.todolist = TodoList.TodoList(self.todofile.read())
self.marked_todos = []
self.columns = urwid.Columns([], dividechars=0, min_width=COLUMN_WIDTH)
self.commandline = CommandLineWidget('topydo> ')
self.keystate_widget = KeystateWidget()
self.status_line = urwid.Columns([
('weight', 1, urwid.Filler(self.commandline)),
])
self.keymap = config().column_keymap()
self._alarm = None
self._last_cmd = None
# console widget
self.console = ConsoleWidget()
get_terminal_size(self._console_width)
urwid.connect_signal(self.commandline, 'blur', self._blur_commandline)
urwid.connect_signal(self.commandline, 'execute_command',
self._execute_handler)
def hide_console(p_focus_commandline=False):
self._console_visible = False
if p_focus_commandline:
self._focus_commandline()
urwid.connect_signal(self.console, 'close', hide_console)
# view widget
self.viewwidget = ViewWidget(self.todolist)
urwid.connect_signal(self.viewwidget, 'save',
lambda: self._update_view(self.viewwidget.data))
def hide_viewwidget():
self._viewwidget_visible = False
self._blur_commandline()
urwid.connect_signal(self.viewwidget, 'close', hide_viewwidget)
self.mainwindow = MainPile([
('weight', 1, self.columns),
(1, self.status_line),
])
urwid.connect_signal(self.mainwindow, 'blur_console', hide_console)
# the columns should have keyboard focus
self._blur_commandline()
self._screen = urwid.raw_display.Screen()
if config().colors():
self._screen.register_palette(self._create_color_palette())
else:
self._screen.register_palette(self._create_mono_palette())
self._screen.set_terminal_properties(256)
self.mainloop = urwid.MainLoop(
self.mainwindow,
screen=self._screen,
unhandled_input=self._handle_input,
pop_ups=True
)
self.column_mode = _APPEND_COLUMN
self._set_alarm_for_next_midnight_update()
def _create_color_palette(self):
project_color = to_urwid_color(config().project_color())
context_color = to_urwid_color(config().context_color())
metadata_color = to_urwid_color(config().metadata_color())
link_color = to_urwid_color(config().link_color())
palette = [
(PaletteItem.PROJECT, '', '', '', project_color, ''),
(PaletteItem.PROJECT_FOCUS, '', 'light gray', '', project_color, None),
(PaletteItem.CONTEXT, '', '', '', context_color, ''),
(PaletteItem.CONTEXT_FOCUS, '', 'light gray', '', context_color, None),
(PaletteItem.METADATA, '', '', '', metadata_color, ''),
(PaletteItem.METADATA_FOCUS, '', 'light gray', '', metadata_color, None),
(PaletteItem.LINK, '', '', '', link_color, ''),
(PaletteItem.LINK_FOCUS, '', 'light gray', '', link_color, None),
(PaletteItem.DEFAULT_FOCUS, 'black', 'light gray'),
(PaletteItem.MARKED, '', 'light blue'),
]
for C in ascii_uppercase:
pri_color_cfg = config().priority_color(C)
pri_color = to_urwid_color(pri_color_cfg)
pri_color_focus = pri_color if not pri_color_cfg.is_neutral() else 'black'
palette.append((
'pri_' + C, '', '', '', pri_color, ''
))
palette.append((
'pri_' + C + '_focus', '', 'light gray', '', pri_color_focus, None
))
return palette
def _create_mono_palette(self):
palette = [
(PaletteItem.DEFAULT_FOCUS, 'black', 'light gray'),
(PaletteItem.PROJECT_FOCUS, PaletteItem.DEFAULT_FOCUS),
(PaletteItem.CONTEXT_FOCUS, PaletteItem.DEFAULT_FOCUS),
(PaletteItem.METADATA_FOCUS, PaletteItem.DEFAULT_FOCUS),
(PaletteItem.LINK_FOCUS, PaletteItem.DEFAULT_FOCUS),
(PaletteItem.MARKED, 'default,underline,bold', 'default'),
]
for C in ascii_uppercase:
palette.append(
('pri_' + C + '_focus', PaletteItem.DEFAULT_FOCUS)
)
return palette
def _set_alarm_for_next_midnight_update(self):
def callback(p_loop, p_data):
self._update_all_columns()
self._set_alarm_for_next_midnight_update()
tomorrow = datetime.datetime.now() + datetime.timedelta(days=1)
# turn it into midnight
tomorrow = tomorrow.replace(hour=0, minute=0, second=0, microsecond=0)
self.mainloop.set_alarm_at(time.mktime(tomorrow.timetuple()), callback)
def _output(self, p_text):
self._print_to_console(p_text)
def _execute_handler(self, p_command, p_todo_id=None, p_output=None):
"""
Executes a command, given as a string.
"""
p_output = p_output or self._output
self._last_cmd = (p_command, p_output == self._output)
if '{}' in p_command:
if self._has_marked_todos():
p_todo_id = ' '.join(self.marked_todos)
p_command = p_command.format(p_todo_id)
try:
p_command = shlex.split(p_command)
except ValueError as verr:
self._print_to_console('Error: ' + str(verr))
return
try:
(subcommand, args) = get_subcommand(p_command)
except ConfigError as cerr:
self._print_to_console(
'Error: {}. Check your aliases configuration.'.format(cerr))
return
self._backup(subcommand, args)
try:
command = subcommand(
args,
self.todolist,
p_output,
self._output,
self._input,
)
if command.execute() != False:
self._post_execute()
except TypeError:
# TODO: show error message
pass
def _update_all_columns(self):
for column, _ in self.columns.contents:
column.update()
column.keystate = None
def _post_execute(self):
# store dirty flag because base _post_execute will reset it after flush
dirty = self.todolist.dirty
super()._post_execute()
if dirty or self.marked_todos:
self._reset_state()
def _repeat_last_cmd(self, p_todo_id=None):
try:
cmd, verbosity = self._last_cmd
except TypeError:
return
self._execute_handler(cmd, p_todo_id,
self._output if verbosity else lambda _: None)
def _reset_state(self):
self.marked_todos = []
self._update_all_columns()
def _blur_commandline(self):
self.mainwindow.focus_item = 0
def _focus_commandline(self):
self.mainwindow.focus_item = 1
def _focus_first_column(self):
self.columns.focus_position = 0
def _focus_last_column(self):
end_pos = len(self.columns.contents) - 1
self.columns.focus_position = end_pos
def _focus_next_column(self):
size = len(self.columns.contents)
if self.columns.focus_position < size -1:
self.columns.focus_position += 1
def _focus_previous_column(self):
if self.columns.focus_position > 0:
self.columns.focus_position -= 1
def _append_column(self):
self.viewwidget.reset()
self.column_mode = _APPEND_COLUMN
self._viewwidget_visible = True
def _insert_column(self):
self.viewwidget.reset()
self.column_mode = _INSERT_COLUMN
self._viewwidget_visible = True
def _edit_column(self):
self.viewwidget.data = self.columns.focus.view.data
self.column_mode = _EDIT_COLUMN
self._viewwidget_visible = True
def _delete_column(self):
try:
focus = self.columns.focus_position
del self.columns.contents[focus]
if self.columns.contents:
self.columns.focus_position = focus
else:
self._focus_commandline()
except IndexError:
# no columns
pass
def _copy_column(self):
self.viewwidget.data = self.columns.focus.view.data
self.column_mode = _COPY_COLUMN
self._viewwidget_visible = True
def _column_action_handler(self, p_action):
dispatch = {
'first_column': self._focus_first_column,
'last_column': self._focus_last_column,
'prev_column': self._focus_previous_column,
'next_column': self._focus_next_column,
'append_column': self._append_column,
'insert_column': self._insert_column,
'edit_column': self._edit_column,
'delete_column': self._delete_column,
'copy_column': self._copy_column,
'swap_left': self._swap_column_left,
'swap_right': self._swap_column_right,
'reset': self._reset_state,
}
dispatch[p_action]()
def _handle_input(self, p_input):
dispatch = {
':': self._focus_commandline,
}
try:
dispatch[p_input]()
except KeyError:
# the key is unknown, ignore
pass
def _viewdata_to_view(self, p_data):
"""
Converts a dictionary describing a view to an actual UIView instance.
"""
sorter = Sorter(p_data['sortexpr'])
filters = []
if not p_data['show_all']:
filters.append(DependencyFilter(self.todolist))
filters.append(RelevanceFilter())
filters += get_filter_list(p_data['filterexpr'].split())
return UIView(sorter, filters, self.todolist, p_data)
def _update_view(self, p_data):
""" Creates a view from the data entered in the view widget. """
view = self._viewdata_to_view(p_data)
if self.column_mode == _APPEND_COLUMN or self.column_mode == _COPY_COLUMN:
self._add_column(view)
elif self.column_mode == _INSERT_COLUMN:
self._add_column(view, self.columns.focus_position)
elif self.column_mode == _EDIT_COLUMN:
current_column = self.columns.focus
current_column.title = p_data['title']
current_column.view = view
self._viewwidget_visible = False
self._blur_commandline()
def _add_column(self, p_view, p_pos=None):
"""
Given an UIView, adds a new column widget with the todos in that view.
When no position is given, it is added to the end, otherwise inserted
before that position.
"""
def execute_silent(p_cmd, p_todo_id=None):
self._execute_handler(p_cmd, p_todo_id, lambda _: None)
todolist = TodoListWidget(p_view, p_view.data['title'], self.keymap)
urwid.connect_signal(todolist, 'execute_command_silent',
execute_silent)
urwid.connect_signal(todolist, 'execute_command', self._execute_handler)
urwid.connect_signal(todolist, 'repeat_cmd', self._repeat_last_cmd)
urwid.connect_signal(todolist, 'refresh', self.mainloop.screen.clear)
urwid.connect_signal(todolist, 'add_pending_action', self._set_alarm)
urwid.connect_signal(todolist, 'remove_pending_action', self._remove_alarm)
urwid.connect_signal(todolist, 'column_action', self._column_action_handler)
urwid.connect_signal(todolist, 'show_keystate', self._print_keystate)
urwid.connect_signal(todolist, 'toggle_mark',
self._process_mark_toggle)
options = self.columns.options(
width_type='given',
width_amount=COLUMN_WIDTH,
box_widget=True
)
item = (todolist, options)
if p_pos == None:
p_pos = len(self.columns.contents)
self.columns.contents.insert(p_pos, item)
self.columns.focus_position = p_pos
self._blur_commandline()
def _print_keystate(self, p_keystate):
self.keystate_widget.set_text(p_keystate)
self._keystate_visible = len(p_keystate) > 0
def _set_alarm(self, p_callback):
""" Sets alarm to execute p_action specified in 0.5 sec. """
self._alarm = self.mainloop.set_alarm_in(0.5, p_callback)
def _remove_alarm(self):
""" Removes pending action alarm stored in _alarm attribute. """
self.mainloop.remove_alarm(self._alarm)
self._alarm = None
def _swap_column_left(self):
pos = self.columns.focus_position
if pos > 0:
_columns = self.columns.contents
_columns[pos], _columns[pos - 1] = _columns[pos - 1], _columns[pos]
self.columns.focus_position -= 1
def _swap_column_right(self):
pos = self.columns.focus_position
_columns = self.columns.contents
if pos < len(_columns) - 1:
_columns[pos], _columns[pos + 1] = _columns[pos + 1], _columns[pos]
self.columns.focus_position += 1
@property
def _console_visible(self):
contents = self.mainwindow.contents
return len(contents) == 3 and isinstance(contents[2][0], ConsoleWidget)
@_console_visible.setter
def _console_visible(self, p_enabled):
contents = self.mainwindow.contents
if p_enabled == True and len(contents) == 2:
contents.append((self.console, ('pack', None)))
self.mainwindow.focus_position = 2
elif p_enabled == False and self._console_visible:
self.console.clear()
del contents[2]
self.mainwindow.focus_position = 0
@property
def _keystate_visible(self):
contents = self.status_line.contents
return len(contents) == 2 and isinstance(contents[1][0].original_widget,
KeystateWidget)
@_keystate_visible.setter
def _keystate_visible(self, p_enabled):
contents = self.status_line.contents
if p_enabled and len(contents) == 1:
contents.append((urwid.Filler(self.keystate_widget),
('weight', 1, True)))
elif not p_enabled and self._keystate_visible:
del contents[1]
@property
def _viewwidget_visible(self):
contents = self.mainwindow.contents
return len(contents) == 3 and isinstance(contents[2][0], ViewWidget)
@_viewwidget_visible.setter
def _viewwidget_visible(self, p_enabled):
contents = self.mainwindow.contents
if p_enabled == True and len(contents) == 2:
contents.append((self.viewwidget, ('pack', None)))
self.mainwindow.focus_position = 2
elif p_enabled == False and self._viewwidget_visible:
del contents[2]
def _print_to_console(self, p_text):
self._console_visible = True
self.console.print_text(p_text)
def _input(self, p_question):
self._print_to_console(p_question)
# don't wait for the event loop to enter idle, there is a command
# waiting for input right now, so already go ahead and draw the
# question on screen.
self.mainloop.draw_screen()
user_input = self.mainloop.screen.get_input()
self._console_visible = False
return user_input[0]
def _console_width(self):
terminal_size = namedtuple('Terminal_Size', 'columns lines')
width = self.console.console_width() - 2
sz = terminal_size(width, 1)
return sz
def _has_marked_todos(self):
return len(self.marked_todos) > 0
def _process_mark_toggle(self, p_todo_id):
"""
Adds p_todo_id to marked_todos attribute and returns True if p_todo_id
is not already present. Removes p_todo_id from marked_todos and returns
False otherwise.
"""
if p_todo_id not in self.marked_todos:
self.marked_todos.append(p_todo_id)
return True
else:
self.marked_todos.remove(p_todo_id)
return False
def run(self):
layout = columns()
if len(layout) > 0:
for column in layout:
self._add_column(self._viewdata_to_view(column))
else:
dummy = {
"title": "All tasks",
"sortexpr": "desc:prio",
"filterexpr": "",
"show_all": True,
}
self._add_column(self._viewdata_to_view(dummy))
# make sure that the first column is focused on startup
self.columns.focus_position = 0
self.mainloop.run()
if __name__ == '__main__':
UIApplication().run()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urwid
from topydo.ui.TodoWidget import TodoWidget
from topydo.lib.Utils import translate_key_to_config
def get_execute_signal(p_prefix):
if p_prefix == 'cmdv':
signal = 'execute_command'
else:
signal = 'execute_command_silent'
return signal
class TodoListWidget(urwid.LineBox):
def __init__(self, p_view, p_title, p_keymap):
self._view = None
self.keymap = p_keymap
# store a state for multi-key shortcuts (e.g. 'gg')
self.keystate = None
# store offset length for postpone command (e.g. '3' for 'p3w')
self._pp_offset = None
self._title_widget = urwid.Text(p_title, align='center')
self.todolist = urwid.SimpleFocusListWalker([])
self.listbox = urwid.ListBox(self.todolist)
self.view = p_view
pile = urwid.Pile([
(1, urwid.Filler(self._title_widget)),
(1, urwid.Filler(urwid.Divider('\u2500'))),
('weight', 1, self.listbox),
])
pile.focus_position = 2
super().__init__(pile)
urwid.register_signal(TodoListWidget, ['execute_command_silent',
'execute_command',
'refresh',
'add_pending_action',
'remove_pending_action',
'repeat_cmd',
'column_action',
'show_keystate',
'toggle_mark',
])
@property
def view(self):
return self._view
@view.setter
def view(self, p_view):
self._view = p_view
self.update()
@property
def title(self):
return self._title_widget.text
@title.setter
def title(self, p_title):
self._title_widget.set_text(p_title)
def update(self):
"""
Updates the todo list according to the todos in the view associated
with this list.
"""
old_focus_position = self.todolist.focus
del self.todolist[:]
for todo in self.view.todos:
todowidget = TodoWidget(todo, self.view.todolist.number(todo))
self.todolist.append(todowidget)
self.todolist.append(urwid.Divider('-'))
if old_focus_position:
try:
self.todolist.set_focus(old_focus_position)
except IndexError:
# scroll to the bottom if the last item disappeared from column
# -2 for the same reason as in self._scroll_to_bottom()
self.todolist.set_focus(len(self.todolist) - 2)
def _scroll_to_top(self, p_size):
self.listbox.set_focus(0)
# see comment at _scroll_to_bottom
self.listbox.calculate_visible(p_size)
def _scroll_to_bottom(self, p_size):
# -2 because the last Divider shouldn't be focused.
end_pos = len(self.listbox.body) - 2
self.listbox.set_focus(end_pos)
# for some reason, set_focus doesn't rerender the list.
# calculate_visible is the only public method (besides keypress) that
# deals with pending focus changes.
self.listbox.calculate_visible(p_size)
@property
def keystate(self):
return self._keystate
@keystate.setter
def keystate(self, p_keystate):
self._keystate = p_keystate
keystate_to_show = p_keystate if p_keystate else ''
urwid.emit_signal(self, 'show_keystate', keystate_to_show)
def keypress(self, p_size, p_key):
urwid.emit_signal(self, 'remove_pending_action')
requires_further_input = ['postpone', 'postpone_s', 'pri']
keymap, keystates = self.keymap
shortcut = self.keystate or ''
shortcut += translate_key_to_config(p_key)
try:
action = keymap[shortcut]
except KeyError:
action = None
if action:
if shortcut in keystates:
# Supplied key-shortcut matches keystate and action. Save the
# keystate in case user will hit another key and add an action
# waiting for execution if user won't type anything further.
self.keystate = shortcut
if action not in requires_further_input:
self._add_pending_action(action, p_size)
else:
# Only action is matched. Handle it and reset keystate.
self.resolve_action(action, p_size)
self.keystate = None
return
else:
if shortcut in keystates:
self.keystate = shortcut
else:
try:
# Check whether current keystate matches built-in 'postpone'
# action.
mode = keymap[self.keystate]
if mode in ['postpone', 'postpone_s']:
if self._postpone_selected(p_key, mode) is not None:
self.keystate = None
else:
urwid.emit_signal(self, 'show_keystate',
self.keystate + self._pp_offset)
else:
self.keystate = None
return
except KeyError:
if not self.keystate:
# Single key that is not described in keymap config.
return self.listbox.keypress(p_size, p_key)
self.keystate = None
return
def mouse_event(self, p_size, p_event, p_button, p_column, p_row, p_focus):
if p_event == 'mouse press':
if p_button == 4: # up
self.listbox.keypress(p_size, 'up')
return
elif p_button == 5: # down:
self.listbox.keypress(p_size, 'down')
return
return super().mouse_event(p_size, # pylint: disable=E1102
p_event,
p_button,
p_column,
p_row,
p_focus)
def selectable(self):
return True
def _toggle_marked_status(self):
try:
todo = self.listbox.focus.todo
todo_id = str(self.view.todolist.number(todo))
if urwid.emit_signal(self, 'toggle_mark', todo_id):
self.listbox.focus.mark()
else:
self.listbox.focus.unmark()
except AttributeError:
# No todo item selected
pass
def _execute_on_selected(self, p_cmd_str, p_execute_signal):
"""
Executes command specified by p_cmd_str on selected todo item.
p_cmd_str should be a string with one replacement field ('{}') which
will be substituted by id of the selected todo item.
p_execute_signal is the signal name passed to the main loop. It should
be one of 'execute_command' or 'execute_command_silent'.
"""
try:
todo = self.listbox.focus.todo
todo_id = str(self.view.todolist.number(todo))
urwid.emit_signal(self, p_execute_signal, p_cmd_str, todo_id)
# force screen redraw after editing
if p_cmd_str.startswith('edit'):
urwid.emit_signal(self, 'refresh')
except AttributeError:
# No todo item selected
pass
def resolve_action(self, p_action_str, p_size=None):
"""
Checks whether action specified in p_action_str is "built-in" or
contains topydo command (i.e. starts with 'cmd') and forwards it to
proper executing methods.
p_size should be specified for some of the builtin actions like 'up' or
'home' as they can interact with urwid.ListBox.keypress or
urwid.ListBox.calculate_visible.
"""
if p_action_str.startswith(('cmd ', 'cmdv ')):
prefix, cmd = p_action_str.split(' ', 1)
execute_signal = get_execute_signal(prefix)
if '{}' in cmd:
self._execute_on_selected(cmd, execute_signal)
else:
urwid.emit_signal(self, execute_signal, cmd)
else:
self.execute_builtin_action(p_action_str, p_size)
def execute_builtin_action(self, p_action_str, p_size=None):
"""
Executes built-in action specified in p_action_str.
Currently supported actions are: 'up', 'down', 'home', 'end',
'first_column', 'last_column', 'prev_column', 'next_column',
'append_column', 'insert_column', 'edit_column', 'delete_column',
'copy_column', swap_right', 'swap_left', 'postpone', 'postpone_s',
'pri', 'mark', 'reset' and 'repeat'.
"""
column_actions = ['first_column',
'last_column',
'prev_column',
'next_column',
'append_column',
'insert_column',
'edit_column',
'delete_column',
'copy_column',
'swap_left',
'swap_right',
'reset',
]
if p_action_str in column_actions:
urwid.emit_signal(self, 'column_action', p_action_str)
elif p_action_str in ['up', 'down']:
self.listbox.keypress(p_size, p_action_str)
elif p_action_str == 'home':
self._scroll_to_top(p_size)
elif p_action_str == 'end':
self._scroll_to_bottom(p_size)
elif p_action_str in ['postpone', 'postpone_s']:
pass
elif p_action_str == 'pri':
pass
elif p_action_str == 'mark':
self._toggle_marked_status()
elif p_action_str == 'repeat':
self._repeat_cmd()
def _add_pending_action(self, p_action, p_size):
"""
Creates action waiting for execution and forwards it to the mainloop.
"""
def generate_callback():
def callback(*args):
self.resolve_action(p_action, p_size)
self.keystate = None
return callback
urwid.emit_signal(self, 'add_pending_action', generate_callback())
def _postpone_selected(self, p_pattern, p_mode):
"""
Postpones selected todo item by <COUNT><PERIOD>.
Returns True after 'postpone' command is called (i.e. p_pattern is valid
<PERIOD>), False when p_pattern is invalid and None if p_pattern is
digit (i.e. part of <COUNT>).
p_pattern accepts digit (<COUNT>) or one of the <PERIOD> letters:
'd'(ay), 'w'(eek), 'm'(onth), 'y'(ear). If digit is specified, it is
appended to _pp_offset attribute. If p_pattern contains one of the
<PERIOD> letters, 'postpone' command is forwarded to execution with
value of _pp_offset attribute used as <COUNT>. If _pp_offset is None,
<COUNT> is set to 1.
p_mode should be one of 'postpone_s' or 'postpone'. It decides whether
'postpone' command should be called with or without '-s' flag.
"""
if p_pattern.isdigit():
if not self._pp_offset:
self._pp_offset = ''
self._pp_offset += p_pattern
result = None
else:
if p_pattern in ['d', 'w', 'm', 'y', 'b']:
offset = self._pp_offset or '1'
if p_mode == 'postpone':
pp_cmd = 'cmd postpone {} '
else:
pp_cmd = 'cmd postpone -s {} '
pp_cmd += offset + p_pattern
self.resolve_action(pp_cmd)
result = True
self._pp_offset = None
result = False
return result
def _repeat_cmd(self):
try:
todo = self.listbox.focus.todo
todo_id = str(self.view.todolist.number(todo))
except AttributeError:
todo_id = None
urwid.emit_signal(self, 'repeat_cmd', todo_id)
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import urwid
from topydo.lib.Config import config
from topydo.lib.ListFormat import ListFormatParser
from topydo.lib.ProgressColor import progress_color
from topydo.ui.Utils import PaletteItem, to_urwid_color
# pass a None todo list, since we won't use %i or %I here
PRIO_FORMATTER = ListFormatParser(None, "%{(}p{)}")
TEXT_FORMATTER = ListFormatParser(None, "%s %k\n%h")
PRJ_CON_PATTERN = r'\B(?:\+|@)(?:\S*\w)'
TAG_PATTERN = r'\b\S+:[^/\s]\S*\b'
URL_PATTERN = r'(?:^|\s)(?:\w+:){1}(?://\S+)'
def _markup(p_todo, p_focus):
"""
Returns an attribute spec for the colors that correspond to the given todo
item.
"""
pri = p_todo.priority()
pri = 'pri_' + pri if pri else PaletteItem.DEFAULT
if not p_focus:
attr_dict = {None: pri}
else:
# use '_focus' palette entries instead of standard ones
attr_dict = {None: pri + '_focus'}
attr_dict[PaletteItem.PROJECT] = PaletteItem.PROJECT_FOCUS
attr_dict[PaletteItem.CONTEXT] = PaletteItem.CONTEXT_FOCUS
attr_dict[PaletteItem.METADATA] = PaletteItem.METADATA_FOCUS
attr_dict[PaletteItem.LINK] = PaletteItem.LINK_FOCUS
return attr_dict
class TodoWidget(urwid.WidgetWrap):
def __init__(self, p_todo, p_number):
# clients use this to associate this widget with the given todo item
self.todo = p_todo
todo_text = TEXT_FORMATTER.parse(p_todo)
priority_text = PRIO_FORMATTER.parse(p_todo)
# split todo_text at each occurrence of tag/project/context/url
txt_pattern = r'|'.join([PRJ_CON_PATTERN, TAG_PATTERN, URL_PATTERN])
txt_pattern = r'(' + txt_pattern + r')'
txt_splitted = re.split(txt_pattern, todo_text)
txt_markup = []
# Examine each substring and apply relevant palette entry if needed
for substring in txt_splitted:
# re.split can generate empty strings when capturing group is used
if not substring:
continue
if re.match(TAG_PATTERN, substring):
txt_markup.append((PaletteItem.METADATA, substring))
elif re.match(URL_PATTERN, substring):
txt_markup.append((PaletteItem.LINK, substring))
elif re.match(PRJ_CON_PATTERN, substring):
if substring.startswith('+'):
txt_markup.append((PaletteItem.PROJECT, substring))
else:
txt_markup.append((PaletteItem.CONTEXT, substring))
else:
txt_markup.append(substring)
id_widget = urwid.Text(str(p_number), align='right')
priority_widget = urwid.Text(priority_text)
self.text_widget = urwid.Text(txt_markup)
progress = to_urwid_color(progress_color(p_todo)) if config().colors() else PaletteItem.DEFAULT
progress_bar = urwid.AttrMap(
urwid.SolidFill(' '),
urwid.AttrSpec(PaletteItem.DEFAULT, progress, 256),
urwid.AttrSpec(PaletteItem.DEFAULT, progress, 256),
)
self.columns = urwid.Columns(
[
(1, progress_bar),
(4, id_widget),
(3, priority_widget),
('weight', 1, self.text_widget),
],
dividechars=1,
box_columns=[0] # the progress bar adapts its height to the rest
)
self.widget = urwid.AttrMap(
self.columns,
_markup(p_todo, p_focus=False),
_markup(p_todo, p_focus=True)
)
super().__init__(self.widget)
def keypress(self, p_size, p_key):
"""
Override keypress to prevent the wrapped Columns widget to
receive any key.
"""
return p_key
def selectable(self):
# make sure that ListBox will highlight this widget
return True
def mark(self):
attr_map = {
None: PaletteItem.MARKED,
PaletteItem.LINK: PaletteItem.MARKED,
PaletteItem.CONTEXT: PaletteItem.MARKED,
PaletteItem.PROJECT: PaletteItem.MARKED,
PaletteItem.METADATA: PaletteItem.MARKED,
}
self.widget.set_attr_map(attr_map)
def unmark(self):
self.widget.set_attr_map(_markup(self.todo, False))
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2016 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
class PaletteItem:
PROJECT = 'project'
PROJECT_FOCUS = 'project_focus'
CONTEXT = 'context'
CONTEXT_FOCUS = 'context_focus'
METADATA = 'metadata'
METADATA_FOCUS = 'metadata_focus'
LINK = 'link'
LINK_FOCUS = 'link_focus'
DEFAULT = 'default'
DEFAULT_FOCUS = 'default_focus'
MARKED = 'marked'
def to_urwid_color(p_color):
"""
Given a Color object, transform it to a color that urwid understands.
"""
if not p_color.is_valid():
return 'black'
elif p_color.is_neutral():
return 'default'
else:
return 'h{}'.format(p_color.color)
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urwid
from topydo.lib.Config import config
class ViewWidget(urwid.LineBox):
def __init__(self, p_todolist):
self._todolist = p_todolist
self.titleedit = urwid.Edit("Title: ", "")
self.sortedit = urwid.Edit("Sort expression: ", "")
self.filteredit = urwid.Edit("Filter expression: ", "")
group = []
self.relevantradio = urwid.RadioButton(group, "Only show relevant todo items", True)
self.allradio = urwid.RadioButton(group, "Show all todo items")
self.pile = urwid.Pile([
self.filteredit,
self.titleedit,
self.sortedit,
self.relevantradio,
self.allradio,
urwid.Button("Save", lambda _: urwid.emit_signal(self, 'save')),
urwid.Button("Cancel", lambda _: self.close()),
])
self.reset()
super().__init__(self.pile)
urwid.register_signal(ViewWidget, ['save', 'close'])
@property
def data(self):
return {
'title': self.titleedit.edit_text or self.filteredit.edit_text,
'sortexpr': self.sortedit.edit_text or config().sort_string(),
'filterexpr': self.filteredit.edit_text,
'show_all': self.allradio.state,
}
@data.setter
def data(self, p_data):
self.titleedit.edit_text = p_data['title']
self.sortedit.edit_text = p_data['sortexpr']
self.filteredit.edit_text = p_data['filterexpr']
self.relevantradio.set_state(not p_data['show_all'])
self.allradio.set_state(p_data['show_all'])
def reset(self):
""" Resets the form. """
self.titleedit.set_edit_text("")
self.sortedit.set_edit_text("")
self.filteredit.set_edit_text("")
self.relevantradio.set_state(True)
self.pile.focus_item = 0
def close(self):
urwid.emit_signal(self, 'close')
def keypress(self, p_size, p_key):
if p_key == 'esc':
self.close()
else:
return super().keypress(p_size, p_key) # pylint: disable=E1102
[all]
title = All tasks
filterexpr =
[today]
title = Due today
filterexpr = due:tod
[overdue]
title = Overdue tasks
filterexpr = due:<tod
sortexpr = desc:due
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