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 = ...@@ -12,5 +12,6 @@ exclude_lines =
if __name__ == .__main__.: if __name__ == .__main__.:
omit = omit =
topydo/ui/*.py
topydo/commands/ExitCommand.py topydo/commands/ExitCommand.py
topydo/lib/Version.py topydo/lib/Version.py
...@@ -8,8 +8,9 @@ python: ...@@ -8,8 +8,9 @@ python:
install: install:
- "python -m pip install pip --upgrade" - "python -m pip install pip --upgrade"
- "pip install ." - "pip install ."
- "pip install .[columns]"
- "pip install .[ical]" - "pip install .[ical]"
- "pip install .[prompt_toolkit]" - "pip install .[prompt]"
- "pip install .[test]" - "pip install .[test]"
- "pip install pylint" - "pip install pylint"
- "pip install codecov" - "pip install codecov"
......
...@@ -36,6 +36,9 @@ Simply install with: ...@@ -36,6 +36,9 @@ Simply install with:
(not supported for PyPy3). (not supported for PyPy3).
* [prompt-toolkit][6] : For topydo's _prompt_ mode, which offers a shell-like * [prompt-toolkit][6] : For topydo's _prompt_ mode, which offers a shell-like
interface with auto-completion. 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 * [backports.shutil_get_terminal_size][9] : Used to determine your terminal
window size. This function was window size. This function was
added to the standard library in added to the standard library in
...@@ -61,3 +64,4 @@ Demo ...@@ -61,3 +64,4 @@ Demo
[9]: https://github.com/chrippa/backports.shutil_get_terminal_size [9]: https://github.com/chrippa/backports.shutil_get_terminal_size
[10]: https://dateutil.readthedocs.org/ [10]: https://dateutil.readthedocs.org/
[11]: https://github.com/testing-cabal/mock [11]: https://github.com/testing-cabal/mock
[12]: https://github.com/urwid/urwid
...@@ -34,8 +34,9 @@ setup( ...@@ -34,8 +34,9 @@ setup(
extras_require = { extras_require = {
':sys_platform=="win32"': ['colorama>=0.2.5'], ':sys_platform=="win32"': ['colorama>=0.2.5'],
':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'], ':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'],
'columns': ['urwid >= 1.3.0'],
'ical': ['icalendar'], 'ical': ['icalendar'],
'prompt_toolkit': ['prompt_toolkit >= 0.53'], 'prompt': ['prompt_toolkit >= 0.53'],
'test': ['coverage', 'freezegun', 'green', ], 'test': ['coverage', 'freezegun', 'green', ],
'test:python_version=="3.2"': ['mock'], 'test:python_version=="3.2"': ['mock'],
}, },
......
...@@ -25,9 +25,14 @@ class CommandTest(TopydoTest): ...@@ -25,9 +25,14 @@ class CommandTest(TopydoTest):
self.errors = "" self.errors = ""
def out(self, p_output): def out(self, p_output):
if p_output: if isinstance(p_output, list) and p_output:
self.output += escape_ansi(p_output + "\n") 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): def error(self, p_error):
if p_error: if isinstance(p_error, list) and p_error:
self.errors += escape_ansi(p_error + "\n") 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): ...@@ -52,4 +52,4 @@ def todolist_to_string(p_list):
def print_view(p_view): def print_view(p_view):
printer = PrettyPrinter() 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): ...@@ -30,8 +30,8 @@ class ArchiveCommandTest(CommandTest):
command = ArchiveCommand(todolist, archive) command = ArchiveCommand(todolist, archive)
command.execute() command.execute()
self.assertTrue(todolist.is_dirty()) self.assertTrue(todolist.dirty)
self.assertTrue(archive.is_dirty()) self.assertTrue(archive.dirty)
self.assertEqual(todolist.print_todos(), "x Not complete\n(C) Active") 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") self.assertEqual(archive.print_todos(), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete")
......
...@@ -19,152 +19,159 @@ ...@@ -19,152 +19,159 @@
import unittest import unittest
from test.topydo_testcase import TopydoTest 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.Config import config
from topydo.lib.Todo import Todo
NEUTRAL_COLOR = '\033[0m'
class ColorsTest(TopydoTest): class ColorsTest(TopydoTest):
def test_project_color1(self): def test_project_color1(self):
config(p_overrides={('colorscheme', 'project_color'): '2'}) config(p_overrides={('colorscheme', 'project_color'): '2'})
color = Colors().get_project_color() self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), '\033[1;32m')
self.assertEqual(color, '\033[1;38;5;2m')
def test_project_color2(self): def test_project_color2(self):
config(p_overrides={('colorscheme', 'project_color'): 'Foo'}) config(p_overrides={('colorscheme', 'project_color'): 'Foo'})
color = Colors().get_project_color() self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
self.assertEqual(color, NEUTRAL_COLOR)
def test_project_color3(self): def test_project_color3(self):
config(p_overrides={('colorscheme', 'project_color'): 'yellow'}) config(p_overrides={('colorscheme', 'project_color'): 'yellow'})
color = Colors().get_project_color() self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), '\033[1;33m')
self.assertEqual(color, '\033[1;33m')
def test_project_color4(self): def test_project_color4(self):
config(p_overrides={('colorscheme', 'project_color'): '686'}) config(p_overrides={('colorscheme', 'project_color'): '686'})
color = Colors().get_project_color() self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
self.assertEqual(color, NEUTRAL_COLOR)
def test_context_color1(self): def test_context_color1(self):
config(p_overrides={('colorscheme', 'context_color'): '35'}) config(p_overrides={('colorscheme', 'context_color'): '35'})
color = Colors().get_context_color() self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), '\033[1;38;5;35m')
self.assertEqual(color, '\033[1;38;5;35m')
def test_context_color2(self): def test_context_color2(self):
config(p_overrides={('colorscheme', 'context_color'): 'Bar'}) config(p_overrides={('colorscheme', 'context_color'): 'Bar'})
color = Colors().get_context_color() self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
self.assertEqual(color, NEUTRAL_COLOR)
def test_context_color3(self): def test_context_color3(self):
config(p_overrides={('colorscheme', 'context_color'): 'magenta'}) config(p_overrides={('colorscheme', 'context_color'): 'magenta'})
color = Colors().get_context_color() self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), '\033[1;35m')
self.assertEqual(color, '\033[1;35m')
def test_context_color4(self): def test_context_color4(self):
config(p_overrides={('colorscheme', 'context_color'): '392'}) config(p_overrides={('colorscheme', 'context_color'): '392'})
color = Colors().get_context_color() self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
self.assertEqual(color, NEUTRAL_COLOR)
def test_metadata_color1(self): def test_metadata_color1(self):
config(p_overrides={('colorscheme', 'metadata_color'): '128'}) config(p_overrides={('colorscheme', 'metadata_color'): '128'})
color = Colors().get_metadata_color() self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), '\033[1;38;5;128m')
self.assertEqual(color, '\033[1;38;5;128m')
def test_metadata_color2(self): def test_metadata_color2(self):
config(p_overrides={('colorscheme', 'metadata_color'): 'Baz'}) config(p_overrides={('colorscheme', 'metadata_color'): 'Baz'})
color = Colors().get_metadata_color() self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
self.assertEqual(color, NEUTRAL_COLOR)
def test_metadata_color3(self): def test_metadata_color3(self):
config(p_overrides={('colorscheme', 'metadata_color'): 'light-red'}) config(p_overrides={('colorscheme', 'metadata_color'): 'light-red'})
color = Colors().get_metadata_color() self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), '\033[1;1;31m')
self.assertEqual(color, '\033[1;1;31m')
def test_metadata_color4(self): def test_metadata_color4(self):
config(p_overrides={('colorscheme', 'metadata_color'): '777'}) config(p_overrides={('colorscheme', 'metadata_color'): '777'})
color = Colors().get_metadata_color() self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
self.assertEqual(color, NEUTRAL_COLOR)
def test_link_color1(self): def test_link_color1(self):
config(p_overrides={('colorscheme', 'link_color'): '77'}) config(p_overrides={('colorscheme', 'link_color'): '77'})
color = Colors().get_link_color() self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), '\033[4;38;5;77m')
self.assertEqual(color, '\033[4;38;5;77m')
def test_link_color2(self): def test_link_color2(self):
config(p_overrides={('colorscheme', 'link_color'): 'FooBar'}) config(p_overrides={('colorscheme', 'link_color'): 'FooBar'})
color = Colors().get_link_color() self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), NEUTRAL_COLOR)
self.assertEqual(color, NEUTRAL_COLOR)
def test_link_color3(self): def test_link_color3(self):
config(p_overrides={('colorscheme', 'link_color'): 'red'}) config(p_overrides={('colorscheme', 'link_color'): 'red'})
color = Colors().get_link_color() self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), '\033[4;31m')
self.assertEqual(color, '\033[4;31m')
def test_link_color4(self): def test_link_color4(self):
config(p_overrides={('colorscheme', 'link_color'): '777'}) config(p_overrides={('colorscheme', 'link_color'): '777'})
color = Colors().get_link_color() self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), NEUTRAL_COLOR)
self.assertEqual(color, NEUTRAL_COLOR)
def test_priority_color1(self): def test_priority_color1(self):
config("test/data/ColorsTest1.conf") 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') color_a = config().priority_color(todo_a.priority()).as_ansi()
self.assertEqual(color['B'], '\033[0;38;5;2m') color_b = config().priority_color(todo_b.priority()).as_ansi()
self.assertEqual(color['C'], '\033[0;38;5;3m') 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): def test_priority_color2(self):
config("test/data/ColorsTest2.conf") 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_a, '\033[0;35m')
self.assertEqual(color['B'], '\033[0;1;36m') self.assertEqual(color_b, '\033[0;1;36m')
self.assertEqual(color['C'], '\033[0;37m') self.assertEqual(color_c, '\033[0;37m')
def test_priority_color3(self): def test_priority_color3(self):
config("test/data/ColorsTest3.conf") config("test/data/ColorsTest3.conf")
color = Colors().get_priority_colors() todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
self.assertEqual(color['A'], '\033[0;35m') todo_z = Todo('(Z) FooBar')
self.assertEqual(color['B'], '\033[0;1;36m') todo_d = Todo('(D) Baz')
self.assertEqual(color['Z'], NEUTRAL_COLOR) todo_c = Todo('(C) FooBaz')
self.assertEqual(color['D'], '\033[0;31m')
self.assertEqual(color['C'], '\033[0;38;5;7m') 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): def test_priority_color4(self):
config("test/data/ColorsTest4.conf") 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) color_a = config().priority_color(todo_a.priority()).as_ansi()
self.assertEqual(color['B'], NEUTRAL_COLOR) color_b = config().priority_color(todo_b.priority()).as_ansi()
self.assertEqual(color['C'], NEUTRAL_COLOR) 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): def test_empty_color_values(self):
config("test/data/ColorsTest5.conf") config("test/data/ColorsTest5.conf")
pri_color = Colors().get_priority_colors() project_color = config().project_color().as_ansi(p_decoration='bold')
project_color = Colors().get_project_color() context_color = config().context_color().as_ansi(p_decoration='bold')
context_color = Colors().get_context_color() link_color = config().link_color().as_ansi(p_decoration='underline')
link_color = Colors().get_link_color() metadata_color = config().metadata_color().as_ansi(p_decoration='bold')
metadata_color = Colors().get_metadata_color()
todo_a = Todo('(A) Foo')
self.assertEqual(pri_color['A'], NEUTRAL_COLOR) todo_b = Todo('(B) Bar')
self.assertEqual(pri_color['B'], NEUTRAL_COLOR) todo_c = Todo('(C) FooBar')
self.assertEqual(pri_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, NEUTRAL_COLOR)
self.assertEqual(color_b, NEUTRAL_COLOR)
self.assertEqual(color_c, NEUTRAL_COLOR)
self.assertEqual(project_color, '') self.assertEqual(project_color, '')
self.assertEqual(context_color, '') self.assertEqual(context_color, '')
self.assertEqual(link_color, '') self.assertEqual(link_color, '')
...@@ -172,19 +179,31 @@ class ColorsTest(TopydoTest): ...@@ -172,19 +179,31 @@ class ColorsTest(TopydoTest):
def test_empty_colorscheme(self): def test_empty_colorscheme(self):
config("test/data/config1") config("test/data/config1")
pri_color = Colors().get_priority_colors() project_color = config().project_color().as_ansi(p_decoration='bold')
project_color = Colors().get_project_color() context_color = config().context_color().as_ansi(p_decoration='bold')
context_color = Colors().get_context_color() link_color = config().link_color().as_ansi(p_decoration='underline')
link_color = Colors().get_link_color() metadata_color = config().metadata_color().as_ansi(p_decoration='bold')
metadata_color = Colors().get_metadata_color()
todo_a = Todo('(A) Foo')
self.assertEqual(pri_color['A'], '\033[0;36m') todo_b = Todo('(B) Bar')
self.assertEqual(pri_color['B'], '\033[0;33m') todo_c = Todo('(C) FooBar')
self.assertEqual(pri_color['C'], '\033[0;34m')
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(project_color, '\033[1;31m')
self.assertEqual(context_color, '\033[1;35m') self.assertEqual(context_color, '\033[1;35m')
self.assertEqual(link_color, '\033[4;36m') self.assertEqual(link_color, '\033[4;36m')
self.assertEqual(metadata_color, '\033[1;32m') 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__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -45,8 +45,7 @@ class ConfigTest(TopydoTest): ...@@ -45,8 +45,7 @@ class ConfigTest(TopydoTest):
""" Bad colour switch value. """ """ Bad colour switch value. """
# boolean settings must first be typecast to integers, because all # boolean settings must first be typecast to integers, because all
# strings evaulate to 'True' # strings evaulate to 'True'
self.assertEqual(config("test/data/ConfigTest4.conf").colors(), self.assertEqual(config("test/data/ConfigTest4.conf").colors(), 16)
bool(int(config().defaults["topydo"]["colors"])))
def test_config06(self): def test_config06(self):
""" Bad auto creation date switch value. """ """ Bad auto creation date switch value. """
...@@ -83,62 +82,76 @@ class ConfigTest(TopydoTest): ...@@ -83,62 +82,76 @@ class ConfigTest(TopydoTest):
self.assertEqual(config("test/data/ConfigTest4.conf").append_parent_contexts(), self.assertEqual(config("test/data/ConfigTest4.conf").append_parent_contexts(),
bool(int(config().defaults["dep"]["append_parent_contexts"]))) bool(int(config().defaults["dep"]["append_parent_contexts"])))
@skip("Error checking not yet implemented")
def test_config14(self): def test_config14(self):
""" Bad priority color value. """ """ Bad priority color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").priority_colors(), self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('A').color, 6)
config().defaults["colorscheme"]["priority_colors"]) 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): def test_config15(self):
""" Bad project color value. """ """ Bad project color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").project_color(), self.assertTrue(config("test/data/ConfigTest4.conf").project_color().is_neutral())
config().defaults["colorscheme"]["project_color"])
@skip("Error checking not yet implemented")
def test_config16(self): def test_config16(self):
""" Bad context color value. """ """ Bad context color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").context_color(), self.assertTrue(config("test/data/ConfigTest4.conf").context_color().is_neutral())
config().defaults["colorscheme"]["context_color"])
@skip("Error checking not yet implemented")
def test_config17(self): def test_config17(self):
""" Bad metadata color value. """ """ Bad metadata color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").metadata_color(), self.assertTrue(config("test/data/ConfigTest4.conf").metadata_color().is_neutral())
config().defaults["colorscheme"]["metadata_color"])
@skip("Error checking not yet implemented")
def test_config18(self): def test_config18(self):
""" Bad link color value. """ """ Bad link color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").link_color(), self.assertTrue(config("test/data/ConfigTest4.conf").link_color().is_neutral())
config().defaults["colorscheme"]["link_color"])
@skip("Test not yet implemented")
# the test needs to be of the internal function _str_to_dict # the test needs to be of the internal function _str_to_dict
def test_config19(self): def test_config19(self):
""" No priority color value. """ """ No priority color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").priority_colors(), self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('A').color, 6)
config().defaults["colorscheme"]["priority_colors"]) 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): def test_config20(self):
""" No project color value. """ """ No project color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").project_color(), self.assertEqual(config("test/data/ConfigTest5.conf").project_color().color, 1)
config().defaults["colorscheme"]["project_color"])
def test_config21(self): def test_config21(self):
""" No context color value. """ """ No context color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").context_color(), self.assertEqual(config("test/data/ConfigTest5.conf").context_color().color, 5)
config().defaults["colorscheme"]["context_color"])
def test_config22(self): def test_config22(self):
""" No metadata color value. """ """ No metadata color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").metadata_color(), self.assertEqual(config("test/data/ConfigTest5.conf").metadata_color().color, 2)
config().defaults["colorscheme"]["metadata_color"])
def test_config23(self): def test_config23(self):
""" No link color value. """ """ No link color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").link_color(), self.assertEqual(config("test/data/ConfigTest5.conf").link_color().color, 6)
config().defaults["colorscheme"]["link_color"])
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__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -48,7 +48,7 @@ class DeleteCommandTest(CommandTest): ...@@ -48,7 +48,7 @@ class DeleteCommandTest(CommandTest):
_no_prompt) _no_prompt)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Bar") self.assertEqual(self.todolist.todo(1).source(), "Bar")
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n") self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -58,7 +58,7 @@ class DeleteCommandTest(CommandTest): ...@@ -58,7 +58,7 @@ class DeleteCommandTest(CommandTest):
_no_prompt) _no_prompt)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Bar") self.assertEqual(self.todolist.todo(1).source(), "Bar")
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n") self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -68,7 +68,7 @@ class DeleteCommandTest(CommandTest): ...@@ -68,7 +68,7 @@ class DeleteCommandTest(CommandTest):
_yes_prompt) _yes_prompt)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 2) self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.output, self.assertEqual(self.output,
"| 2| Bar p:1\nRemoved: Bar\nRemoved: Foo\n") "| 2| Bar p:1\nRemoved: Bar\nRemoved: Foo\n")
...@@ -79,7 +79,7 @@ class DeleteCommandTest(CommandTest): ...@@ -79,7 +79,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt) self.error, _yes_prompt)
command.execute() 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.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n") self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -89,7 +89,7 @@ class DeleteCommandTest(CommandTest): ...@@ -89,7 +89,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt) self.error, _yes_prompt)
command.execute() 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.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n") self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -98,7 +98,7 @@ class DeleteCommandTest(CommandTest): ...@@ -98,7 +98,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["2"], self.todolist, self.out, self.error) command = DeleteCommand(["2"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Foo") 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.output, "Removed: Bar p:1\nThe following todo item(s) became active:\n| 1| Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -107,7 +107,7 @@ class DeleteCommandTest(CommandTest): ...@@ -107,7 +107,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["99"], self.todolist, self.out, self.error) command = DeleteCommand(["99"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -115,7 +115,7 @@ class DeleteCommandTest(CommandTest): ...@@ -115,7 +115,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["A"], self.todolist, self.out, self.error) command = DeleteCommand(["A"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -159,7 +159,7 @@ class DeleteCommandTest(CommandTest): ...@@ -159,7 +159,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt) self.error, _yes_prompt)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
...@@ -169,7 +169,7 @@ class DeleteCommandTest(CommandTest): ...@@ -169,7 +169,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt) self.error, _yes_prompt)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: A.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: A.\n")
...@@ -181,7 +181,7 @@ class DeleteCommandTest(CommandTest): ...@@ -181,7 +181,7 @@ class DeleteCommandTest(CommandTest):
self.out, self.error, None) self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n") u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
...@@ -193,7 +193,7 @@ class DeleteCommandTest(CommandTest): ...@@ -193,7 +193,7 @@ class DeleteCommandTest(CommandTest):
result = "Removed: a @test with due:2015-06-03\nRemoved: a @test with +project\n" 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.todolist.count(), 2)
self.assertEqual(self.output, result) self.assertEqual(self.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -203,7 +203,7 @@ class DeleteCommandTest(CommandTest): ...@@ -203,7 +203,7 @@ class DeleteCommandTest(CommandTest):
self.todolist, self.out, self.error, None) self.todolist, self.out, self.error, None)
command.execute() 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.output, "Removed: a @test with due:2015-06-03\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -212,7 +212,7 @@ class DeleteCommandTest(CommandTest): ...@@ -212,7 +212,7 @@ class DeleteCommandTest(CommandTest):
self.todolist, self.out, self.error, None) self.todolist, self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_del4(self): def test_expr_del4(self):
""" Remove only relevant todo items. """ """ Remove only relevant todo items. """
...@@ -222,7 +222,7 @@ class DeleteCommandTest(CommandTest): ...@@ -222,7 +222,7 @@ class DeleteCommandTest(CommandTest):
result = "Foo" result = "Foo"
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 1) self.assertEqual(self.todolist.count(), 1)
self.assertEqual(self.todolist.print_todos(), result) self.assertEqual(self.todolist.print_todos(), result)
...@@ -232,14 +232,14 @@ class DeleteCommandTest(CommandTest): ...@@ -232,14 +232,14 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt) self.error, _yes_prompt)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 0) self.assertEqual(self.todolist.count(), 0)
def test_empty(self): def test_empty(self):
command = DeleteCommand([], self.todolist, self.out, self.error) command = DeleteCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
......
...@@ -40,7 +40,7 @@ class DepCommandTest(CommandTest): ...@@ -40,7 +40,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1')) self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -50,7 +50,7 @@ class DepCommandTest(CommandTest): ...@@ -50,7 +50,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1')) self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -60,7 +60,7 @@ class DepCommandTest(CommandTest): ...@@ -60,7 +60,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -69,7 +69,7 @@ class DepCommandTest(CommandTest): ...@@ -69,7 +69,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -77,7 +77,7 @@ class DepCommandTest(CommandTest): ...@@ -77,7 +77,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["add", "1"], self.todolist, self.out, self.error) command = DepCommand(["add", "1"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
...@@ -86,7 +86,7 @@ class DepCommandTest(CommandTest): ...@@ -86,7 +86,7 @@ class DepCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1')) self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -96,7 +96,7 @@ class DepCommandTest(CommandTest): ...@@ -96,7 +96,7 @@ class DepCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('p', '2')) self.assertTrue(self.todolist.todo(1).has_tag('p', '2'))
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -106,7 +106,7 @@ class DepCommandTest(CommandTest): ...@@ -106,7 +106,7 @@ class DepCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('p', '2')) self.assertTrue(self.todolist.todo(1).has_tag('p', '2'))
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -116,7 +116,7 @@ class DepCommandTest(CommandTest): ...@@ -116,7 +116,7 @@ class DepCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1')) self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -190,7 +190,7 @@ class DepCommandTest(CommandTest): ...@@ -190,7 +190,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(p_args, self.todolist, self.out, self.error) command = DepCommand(p_args, self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('id', '1')) self.assertTrue(self.todolist.todo(1).has_tag('id', '1'))
self.assertFalse(self.todolist.todo(3).has_tag('p', '1')) self.assertFalse(self.todolist.todo(3).has_tag('p', '1'))
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
...@@ -213,7 +213,7 @@ class DepCommandTest(CommandTest): ...@@ -213,7 +213,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -222,7 +222,7 @@ class DepCommandTest(CommandTest): ...@@ -222,7 +222,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -230,7 +230,7 @@ class DepCommandTest(CommandTest): ...@@ -230,7 +230,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["rm", "1"], self.todolist, self.out, self.error) command = DepCommand(["rm", "1"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
...@@ -239,7 +239,7 @@ class DepCommandTest(CommandTest): ...@@ -239,7 +239,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "| 2| Bar p:1\n| 3| Baz p:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -248,7 +248,7 @@ class DepCommandTest(CommandTest): ...@@ -248,7 +248,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -257,7 +257,7 @@ class DepCommandTest(CommandTest): ...@@ -257,7 +257,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "| 1| Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -266,7 +266,7 @@ class DepCommandTest(CommandTest): ...@@ -266,7 +266,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -274,7 +274,7 @@ class DepCommandTest(CommandTest): ...@@ -274,7 +274,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["ls", "1"], self.todolist, self.out, self.error) command = DepCommand(["ls", "1"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
...@@ -282,7 +282,7 @@ class DepCommandTest(CommandTest): ...@@ -282,7 +282,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["ls"], self.todolist, self.out, self.error) command = DepCommand(["ls"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
...@@ -291,7 +291,7 @@ class DepCommandTest(CommandTest): ...@@ -291,7 +291,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
...@@ -300,7 +300,7 @@ class DepCommandTest(CommandTest): ...@@ -300,7 +300,7 @@ class DepCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertFalse(self.errors) self.assertFalse(self.errors)
self.assertFalse(self.todolist.todo(5).has_tag('p', '99')) self.assertFalse(self.todolist.todo(5).has_tag('p', '99'))
...@@ -317,7 +317,7 @@ class DepCommandTest(CommandTest): ...@@ -317,7 +317,7 @@ class DepCommandTest(CommandTest):
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_no_subsubcommand(self): def test_no_subsubcommand(self):
command = DepCommand([], self.todolist, self.out, self.error) command = DepCommand([], self.todolist, self.out, self.error)
...@@ -325,7 +325,7 @@ class DepCommandTest(CommandTest): ...@@ -325,7 +325,7 @@ class DepCommandTest(CommandTest):
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_help(self): def test_help(self):
command = DepCommand(["help"], self.todolist, self.out, self.error) command = DepCommand(["help"], self.todolist, self.out, self.error)
......
...@@ -39,7 +39,7 @@ class DepriCommandTest(CommandTest): ...@@ -39,7 +39,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["1"], self.todolist, self.out, self.error) command = DepriCommand(["1"], self.todolist, self.out, self.error)
command.execute() 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(1).priority(), None)
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\n") self.assertEqual(self.output, "Priority removed.\n| 1| Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -48,7 +48,7 @@ class DepriCommandTest(CommandTest): ...@@ -48,7 +48,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["2"], self.todolist, self.out, self.error) command = DepriCommand(["2"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.todolist.todo(2).priority(), None) self.assertEqual(self.todolist.todo(2).priority(), None)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -57,7 +57,7 @@ class DepriCommandTest(CommandTest): ...@@ -57,7 +57,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["Foo"], self.todolist, self.out, self.error) command = DepriCommand(["Foo"], self.todolist, self.out, self.error)
command.execute() 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(1).priority(), None)
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\n") self.assertEqual(self.output, "Priority removed.\n| 1| Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -67,7 +67,7 @@ class DepriCommandTest(CommandTest): ...@@ -67,7 +67,7 @@ class DepriCommandTest(CommandTest):
self.error) self.error)
command.execute() 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(1).priority(), None)
self.assertEqual(self.todolist.todo(3).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") self.assertEqual(self.output, "Priority removed.\n| 1| Foo\nPriority removed.\n| 3| Baz\n")
...@@ -80,7 +80,7 @@ class DepriCommandTest(CommandTest): ...@@ -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" 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.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -91,7 +91,7 @@ class DepriCommandTest(CommandTest): ...@@ -91,7 +91,7 @@ class DepriCommandTest(CommandTest):
result = "Priority removed.\n| 4| a @test with due:2015-06-03\n" 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.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -100,7 +100,7 @@ class DepriCommandTest(CommandTest): ...@@ -100,7 +100,7 @@ class DepriCommandTest(CommandTest):
self.todolist, self.out, self.error, None) self.todolist, self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_depri4(self): def test_expr_depri4(self):
""" Don't remove priority from unrelevant todo items. """ """ Don't remove priority from unrelevant todo items. """
...@@ -108,7 +108,7 @@ class DepriCommandTest(CommandTest): ...@@ -108,7 +108,7 @@ class DepriCommandTest(CommandTest):
self.error, None) self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_depri5(self): def test_expr_depri5(self):
""" Force unprioritizing unrelevant items with additional -x flag. """ """ Force unprioritizing unrelevant items with additional -x flag. """
...@@ -116,7 +116,7 @@ class DepriCommandTest(CommandTest): ...@@ -116,7 +116,7 @@ class DepriCommandTest(CommandTest):
self.error, None) self.error, None)
command.execute() 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.output, "Priority removed.\n| 6| Bax id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -124,7 +124,7 @@ class DepriCommandTest(CommandTest): ...@@ -124,7 +124,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["99"], self.todolist, self.out, self.error) command = DepriCommand(["99"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -133,7 +133,7 @@ class DepriCommandTest(CommandTest): ...@@ -133,7 +133,7 @@ class DepriCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
...@@ -142,7 +142,7 @@ class DepriCommandTest(CommandTest): ...@@ -142,7 +142,7 @@ class DepriCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: FooBar.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: FooBar.\n")
...@@ -154,7 +154,7 @@ class DepriCommandTest(CommandTest): ...@@ -154,7 +154,7 @@ class DepriCommandTest(CommandTest):
self.out, self.error, None) self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n") u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
...@@ -163,7 +163,7 @@ class DepriCommandTest(CommandTest): ...@@ -163,7 +163,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand([], self.todolist, self.out, self.error) command = DepriCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
......
...@@ -61,7 +61,7 @@ class DoCommandTest(CommandTest): ...@@ -61,7 +61,7 @@ class DoCommandTest(CommandTest):
_no_prompt) _no_prompt)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(3).is_completed()) self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output, self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today)) "Completed: x {} Baz p:1\n".format(self.today))
...@@ -77,7 +77,7 @@ class DoCommandTest(CommandTest): ...@@ -77,7 +77,7 @@ class DoCommandTest(CommandTest):
for number in [1, 2, 3]: for number in [1, 2, 3]:
self.assertTrue(self.todolist.todo(number).is_completed()) 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.assertFalse(self.todolist.todo(4).is_completed())
self.assertEqual(self.output, result) self.assertEqual(self.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -89,7 +89,7 @@ class DoCommandTest(CommandTest): ...@@ -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) 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.assertTrue(self.todolist.todo(1).is_completed())
self.assertFalse(self.todolist.todo(2).is_completed()) self.assertFalse(self.todolist.todo(2).is_completed())
self.assertFalse(self.todolist.todo(3).is_completed()) self.assertFalse(self.todolist.todo(3).is_completed())
...@@ -130,7 +130,7 @@ class DoCommandTest(CommandTest): ...@@ -130,7 +130,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(p_flags, self.todolist, self.out, self.error) command = DoCommand(p_flags, self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.count(), 12) self.assertEqual(self.todolist.count(), 12)
...@@ -162,7 +162,7 @@ class DoCommandTest(CommandTest): ...@@ -162,7 +162,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["99"], self.todolist, self.out, self.error) command = DoCommand(["99"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -170,7 +170,7 @@ class DoCommandTest(CommandTest): ...@@ -170,7 +170,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["AAA"], self.todolist, self.out, self.error) command = DoCommand(["AAA"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -179,7 +179,7 @@ class DoCommandTest(CommandTest): ...@@ -179,7 +179,7 @@ class DoCommandTest(CommandTest):
_yes_prompt) _yes_prompt)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -209,7 +209,7 @@ class DoCommandTest(CommandTest): ...@@ -209,7 +209,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["5"], self.todolist, self.out, self.error) command = DoCommand(["5"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.todolist.todo(5).completion_date(), self.assertEqual(self.todolist.todo(5).completion_date(),
date(2014, 10, 18)) date(2014, 10, 18))
self.assertFalse(self.output) self.assertFalse(self.output)
...@@ -219,7 +219,7 @@ class DoCommandTest(CommandTest): ...@@ -219,7 +219,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["baz"], self.todolist, self.out, self.error) command = DoCommand(["baz"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(3).is_completed()) self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output, self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today)) "Completed: x {} Baz p:1\n".format(self.today))
...@@ -230,7 +230,7 @@ class DoCommandTest(CommandTest): ...@@ -230,7 +230,7 @@ class DoCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "Completed: x 2014-11-18 Baz p:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -239,7 +239,7 @@ class DoCommandTest(CommandTest): ...@@ -239,7 +239,7 @@ class DoCommandTest(CommandTest):
self.error, _yes_prompt) self.error, _yes_prompt)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -248,7 +248,7 @@ class DoCommandTest(CommandTest): ...@@ -248,7 +248,7 @@ class DoCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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.output, "Completed: x 2014-11-18 Baz p:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -257,7 +257,7 @@ class DoCommandTest(CommandTest): ...@@ -257,7 +257,7 @@ class DoCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today)) "Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -271,7 +271,7 @@ class DoCommandTest(CommandTest): ...@@ -271,7 +271,7 @@ class DoCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -285,7 +285,7 @@ class DoCommandTest(CommandTest): ...@@ -285,7 +285,7 @@ class DoCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -298,7 +298,7 @@ class DoCommandTest(CommandTest): ...@@ -298,7 +298,7 @@ class DoCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -310,7 +310,7 @@ class DoCommandTest(CommandTest): ...@@ -310,7 +310,7 @@ class DoCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.yesterday)) "Completed: x {} Baz p:1\n".format(self.yesterday))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -323,7 +323,7 @@ class DoCommandTest(CommandTest): ...@@ -323,7 +323,7 @@ class DoCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.yesterday)) "Completed: x {} Baz p:1\n".format(self.yesterday))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -385,7 +385,7 @@ class DoCommandTest(CommandTest): ...@@ -385,7 +385,7 @@ class DoCommandTest(CommandTest):
self.out, self.error, None) self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n") u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
...@@ -394,7 +394,7 @@ class DoCommandTest(CommandTest): ...@@ -394,7 +394,7 @@ class DoCommandTest(CommandTest):
self.error, None) self.error, None)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -403,7 +403,7 @@ class DoCommandTest(CommandTest): ...@@ -403,7 +403,7 @@ class DoCommandTest(CommandTest):
self.out, self.error, None) self.out, self.error, None)
command.execute() 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.output, "Completed: x {} a @test with due:2015-06-03\n".format(self.today))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -412,7 +412,7 @@ class DoCommandTest(CommandTest): ...@@ -412,7 +412,7 @@ class DoCommandTest(CommandTest):
self.todolist, self.out, self.error, None) self.todolist, self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_do4(self): def test_expr_do4(self):
""" Don't do anything with unrelevant todo items. """ """ Don't do anything with unrelevant todo items. """
...@@ -420,7 +420,7 @@ class DoCommandTest(CommandTest): ...@@ -420,7 +420,7 @@ class DoCommandTest(CommandTest):
None) None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_do5(self): def test_expr_do5(self):
""" Force marking unrelevant items as done with additional -x flag. """ """ Force marking unrelevant items as done with additional -x flag. """
...@@ -430,7 +430,7 @@ class DoCommandTest(CommandTest): ...@@ -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) 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.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -450,7 +450,7 @@ class DoCommandTest(CommandTest): ...@@ -450,7 +450,7 @@ class DoCommandTest(CommandTest):
command = DoCommand([], self.todolist, self.out, self.error) command = DoCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
......
...@@ -56,7 +56,7 @@ class EditCommandTest(CommandTest): ...@@ -56,7 +56,7 @@ class EditCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.errors, "") 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") 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') @mock.patch('topydo.commands.EditCommand._is_edited')
...@@ -72,7 +72,7 @@ class EditCommandTest(CommandTest): ...@@ -72,7 +72,7 @@ class EditCommandTest(CommandTest):
None) None)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat") 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): ...@@ -82,7 +82,7 @@ class EditCommandTest(CommandTest):
None) None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_edit04(self): def test_edit04(self):
...@@ -91,7 +91,7 @@ class EditCommandTest(CommandTest): ...@@ -91,7 +91,7 @@ class EditCommandTest(CommandTest):
self.error, None) self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, "Invalid todo number given: 5.\n") self.assertEqual(self.errors, "Invalid todo number given: 5.\n")
def test_edit05(self): def test_edit05(self):
...@@ -102,7 +102,7 @@ class EditCommandTest(CommandTest): ...@@ -102,7 +102,7 @@ class EditCommandTest(CommandTest):
self.out, self.error, None) self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n") u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
...@@ -119,7 +119,7 @@ class EditCommandTest(CommandTest): ...@@ -119,7 +119,7 @@ class EditCommandTest(CommandTest):
self.out, self.error, None) self.out, self.error, None)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), self.assertEqual(self.todolist.print_todos(),
u"Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat") u"Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat")
...@@ -137,7 +137,7 @@ class EditCommandTest(CommandTest): ...@@ -137,7 +137,7 @@ class EditCommandTest(CommandTest):
self.error, None) self.error, None)
command.execute() 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.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") 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): ...@@ -158,7 +158,7 @@ class EditCommandTest(CommandTest):
expected = u"| 3| Lazy Cat\n| 4| Lazy Dog\n" expected = u"| 3| Lazy Cat\n| 4| Lazy Dog\n"
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, expected) self.assertEqual(self.output, expected)
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog") 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): ...@@ -43,7 +43,7 @@ class ListCommandTest(CommandTest):
command = ListCommand([""], self.todolist, self.out, self.error) command = ListCommand([""], self.todolist, self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -52,7 +52,7 @@ class ListCommandTest(CommandTest): ...@@ -52,7 +52,7 @@ class ListCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -61,7 +61,7 @@ class ListCommandTest(CommandTest): ...@@ -61,7 +61,7 @@ class ListCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -69,7 +69,7 @@ class ListCommandTest(CommandTest): ...@@ -69,7 +69,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error) command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -78,7 +78,7 @@ class ListCommandTest(CommandTest): ...@@ -78,7 +78,7 @@ class ListCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -87,7 +87,7 @@ class ListCommandTest(CommandTest): ...@@ -87,7 +87,7 @@ class ListCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -96,7 +96,7 @@ class ListCommandTest(CommandTest): ...@@ -96,7 +96,7 @@ class ListCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -105,7 +105,7 @@ class ListCommandTest(CommandTest): ...@@ -105,7 +105,7 @@ class ListCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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.output, "| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -114,7 +114,7 @@ class ListCommandTest(CommandTest): ...@@ -114,7 +114,7 @@ class ListCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -124,7 +124,7 @@ class ListCommandTest(CommandTest): ...@@ -124,7 +124,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["project"], self.todolist, self.out, self.error) command = ListCommand(["project"], self.todolist, self.out, self.error)
command.execute() 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.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -135,7 +135,7 @@ class ListCommandTest(CommandTest): ...@@ -135,7 +135,7 @@ class ListCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -144,7 +144,7 @@ class ListCommandTest(CommandTest): ...@@ -144,7 +144,7 @@ class ListCommandTest(CommandTest):
self.todolist, self.out, self.error) self.todolist, self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -154,7 +154,7 @@ class ListCommandTest(CommandTest): ...@@ -154,7 +154,7 @@ class ListCommandTest(CommandTest):
command = ListCommand([], self.todolist, self.out, self.error) command = ListCommand([], self.todolist, self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -162,7 +162,7 @@ class ListCommandTest(CommandTest): ...@@ -162,7 +162,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["p:<10"], self.todolist, self.out, self.error) command = ListCommand(["p:<10"], self.todolist, self.out, self.error)
command.execute() 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.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -172,7 +172,7 @@ class ListCommandTest(CommandTest): ...@@ -172,7 +172,7 @@ class ListCommandTest(CommandTest):
command = ListCommand([], self.todolist, self.out, self.error) command = ListCommand([], self.todolist, self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -181,7 +181,7 @@ class ListCommandTest(CommandTest): ...@@ -181,7 +181,7 @@ class ListCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"| 3| (C) Baz @Context1 +Project1 key:value\n") "| 3| (C) Baz @Context1 +Project1 key:value\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -191,7 +191,7 @@ class ListCommandTest(CommandTest): ...@@ -191,7 +191,7 @@ class ListCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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") self.assertEqual(self.output, "| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
def test_list19(self): def test_list19(self):
...@@ -202,7 +202,7 @@ class ListCommandTest(CommandTest): ...@@ -202,7 +202,7 @@ class ListCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -210,7 +210,7 @@ class ListCommandTest(CommandTest): ...@@ -210,7 +210,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-f text"], self.todolist, self.out, self.error) command = ListCommand(["-f text"], self.todolist, self.out, self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -219,7 +219,7 @@ class ListCommandTest(CommandTest): ...@@ -219,7 +219,7 @@ class ListCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -231,7 +231,7 @@ class ListCommandTest(CommandTest): ...@@ -231,7 +231,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error) command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, '| 1| Foo.\n') self.assertEqual(self.output, '| 1| Foo.\n')
def test_list31(self): def test_list31(self):
...@@ -406,7 +406,7 @@ class ListCommandUnicodeTest(CommandTest): ...@@ -406,7 +406,7 @@ class ListCommandUnicodeTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
expected = u"| 1| (C) And some sp\u00e9cial tag:\u25c4\n" expected = u"| 1| (C) And some sp\u00e9cial tag:\u25c4\n"
...@@ -420,7 +420,7 @@ class ListCommandJsonTest(CommandTest): ...@@ -420,7 +420,7 @@ class ListCommandJsonTest(CommandTest):
command = ListCommand(["-f", "json"], todolist, self.out, self.error) command = ListCommand(["-f", "json"], todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(todolist.is_dirty()) self.assertFalse(todolist.dirty)
jsontext = "" jsontext = ""
with codecs.open('test/data/ListCommandTest.json', 'r', with codecs.open('test/data/ListCommandTest.json', 'r',
...@@ -436,7 +436,7 @@ class ListCommandJsonTest(CommandTest): ...@@ -436,7 +436,7 @@ class ListCommandJsonTest(CommandTest):
command = ListCommand(["-f", "json"], todolist, self.out, self.error) command = ListCommand(["-f", "json"], todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(todolist.is_dirty()) self.assertFalse(todolist.dirty)
jsontext = "" jsontext = ""
with codecs.open('test/data/ListCommandUnicodeTest.json', 'r', with codecs.open('test/data/ListCommandUnicodeTest.json', 'r',
...@@ -466,7 +466,7 @@ class ListCommandIcalTest(CommandTest): ...@@ -466,7 +466,7 @@ class ListCommandIcalTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(todolist.is_dirty()) self.assertTrue(todolist.dirty)
icaltext = "" icaltext = ""
with codecs.open('test/data/ListCommandTest.ics', 'r', with codecs.open('test/data/ListCommandTest.ics', 'r',
...@@ -483,7 +483,7 @@ class ListCommandIcalTest(CommandTest): ...@@ -483,7 +483,7 @@ class ListCommandIcalTest(CommandTest):
command = ListCommand(["-f", "ical"], todolist, self.out, self.error) command = ListCommand(["-f", "ical"], todolist, self.out, self.error)
command.execute() command.execute()
self.assertTrue(todolist.is_dirty()) self.assertTrue(todolist.dirty)
icaltext = "" icaltext = ""
with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r', with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r',
......
...@@ -678,7 +678,26 @@ C - ...@@ -678,7 +678,26 @@ C -
""" """
self.assertEqual(self.output, result) 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 = ListCommand(["-x", "-F", "%r"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -49,7 +49,7 @@ class PostponeCommandTest(CommandTest): ...@@ -49,7 +49,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"| 1| Foo due:{}\n".format(due.isoformat())) "| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -61,7 +61,7 @@ class PostponeCommandTest(CommandTest): ...@@ -61,7 +61,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"| 2| Bar due:{}\n".format(due.isoformat())) "| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -73,7 +73,7 @@ class PostponeCommandTest(CommandTest): ...@@ -73,7 +73,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"| 2| Bar due:{}\n".format(due.isoformat())) "| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -85,7 +85,7 @@ class PostponeCommandTest(CommandTest): ...@@ -85,7 +85,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) 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.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), self.start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -97,7 +97,7 @@ class PostponeCommandTest(CommandTest): ...@@ -97,7 +97,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) due = self.today + timedelta(7)
start = self.start + timedelta(7) start = self.start + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103 # pylint: disable=E1103
self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), start.isoformat())) self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -109,7 +109,7 @@ class PostponeCommandTest(CommandTest): ...@@ -109,7 +109,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"| 4| Past due:{}\n".format(due.isoformat())) "| 4| Past due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -121,7 +121,7 @@ class PostponeCommandTest(CommandTest): ...@@ -121,7 +121,7 @@ class PostponeCommandTest(CommandTest):
due = self.future + timedelta(7) due = self.future + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103 # pylint: disable=E1103
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), self.future_start.isoformat())) self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), self.future_start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -134,7 +134,7 @@ class PostponeCommandTest(CommandTest): ...@@ -134,7 +134,7 @@ class PostponeCommandTest(CommandTest):
due = self.future + timedelta(7) due = self.future + timedelta(7)
start = self.future_start + timedelta(7) start = self.future_start + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103 # pylint: disable=E1103
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), start.isoformat())) self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -144,7 +144,7 @@ class PostponeCommandTest(CommandTest): ...@@ -144,7 +144,7 @@ class PostponeCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid date pattern given.\n") self.assertEqual(self.errors, "Invalid date pattern given.\n")
...@@ -153,7 +153,7 @@ class PostponeCommandTest(CommandTest): ...@@ -153,7 +153,7 @@ class PostponeCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -162,7 +162,7 @@ class PostponeCommandTest(CommandTest): ...@@ -162,7 +162,7 @@ class PostponeCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -170,7 +170,7 @@ class PostponeCommandTest(CommandTest): ...@@ -170,7 +170,7 @@ class PostponeCommandTest(CommandTest):
command = PostponeCommand(["1"], self.todolist, self.out, self.error) command = PostponeCommand(["1"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
...@@ -181,7 +181,7 @@ class PostponeCommandTest(CommandTest): ...@@ -181,7 +181,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"| 1| Foo due:{}\n".format(due.isoformat())) "| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -193,7 +193,7 @@ class PostponeCommandTest(CommandTest): ...@@ -193,7 +193,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) 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.output, "| 1| Foo due:{}\n| 2| Bar due:{}\n".format(due.isoformat(), due.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -204,7 +204,7 @@ class PostponeCommandTest(CommandTest): ...@@ -204,7 +204,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) 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.output, "| 1| Foo due:{}\n| 2| Bar due:{}\n".format(due.isoformat(), due.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -216,7 +216,7 @@ class PostponeCommandTest(CommandTest): ...@@ -216,7 +216,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) due = self.today + timedelta(7)
start = self.start + timedelta(7) start = self.start + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103 # 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.output, "| 2| Bar due:{}\n| 3| Baz due:{} t:{}\n".format(due.isoformat(), due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -226,7 +226,7 @@ class PostponeCommandTest(CommandTest): ...@@ -226,7 +226,7 @@ class PostponeCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid date pattern given.\n") self.assertEqual(self.errors, "Invalid date pattern given.\n")
...@@ -235,7 +235,7 @@ class PostponeCommandTest(CommandTest): ...@@ -235,7 +235,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 123.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 123.\n")
...@@ -244,7 +244,7 @@ class PostponeCommandTest(CommandTest): ...@@ -244,7 +244,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: Zoo.\nInvalid todo number given: 99.\nInvalid todo number given: 123.\n") 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): ...@@ -254,7 +254,7 @@ class PostponeCommandTest(CommandTest):
self.todolist, self.out, self.error, None) self.todolist, self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n") u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
...@@ -267,7 +267,7 @@ class PostponeCommandTest(CommandTest): ...@@ -267,7 +267,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(14) 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()) 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.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -282,7 +282,7 @@ class PostponeCommandTest(CommandTest): ...@@ -282,7 +282,7 @@ class PostponeCommandTest(CommandTest):
result = "| 3| Baz due:{} t:{}\n".format(due.isoformat(), result = "| 3| Baz due:{} t:{}\n".format(due.isoformat(),
self.start.isoformat()) self.start.isoformat())
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result) self.assertEqual(self.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -291,7 +291,7 @@ class PostponeCommandTest(CommandTest): ...@@ -291,7 +291,7 @@ class PostponeCommandTest(CommandTest):
self.todolist, self.out, self.error, None) self.todolist, self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_postpone4(self): def test_expr_postpone4(self):
""" Don't postpone unrelevant todo items. """ """ Don't postpone unrelevant todo items. """
...@@ -299,7 +299,7 @@ class PostponeCommandTest(CommandTest): ...@@ -299,7 +299,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error, None) self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_postpone5(self): def test_expr_postpone5(self):
""" Force postponing unrelevant items with additional -x flag. """ """ Force postponing unrelevant items with additional -x flag. """
...@@ -310,7 +310,7 @@ class PostponeCommandTest(CommandTest): ...@@ -310,7 +310,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7) due = self.today + timedelta(7)
result = "| 6| FutureStart t:{} due:{}\n".format(self.future.isoformat(), due.isoformat()) 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.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
......
...@@ -39,7 +39,7 @@ class PriorityCommandTest(CommandTest): ...@@ -39,7 +39,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"Priority changed from A to B\n| 1| (B) Foo\n") "Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -49,7 +49,7 @@ class PriorityCommandTest(CommandTest): ...@@ -49,7 +49,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "Priority set to Z.\n| 2| (Z) Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -58,7 +58,7 @@ class PriorityCommandTest(CommandTest): ...@@ -58,7 +58,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"Priority changed from A to B\n| 1| (B) Foo\n") "Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -68,7 +68,7 @@ class PriorityCommandTest(CommandTest): ...@@ -68,7 +68,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (A) Foo\n") self.assertEqual(self.output, "| 1| (A) Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -77,7 +77,7 @@ class PriorityCommandTest(CommandTest): ...@@ -77,7 +77,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -87,7 +87,7 @@ class PriorityCommandTest(CommandTest): ...@@ -87,7 +87,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -97,7 +97,7 @@ class PriorityCommandTest(CommandTest): ...@@ -97,7 +97,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -108,7 +108,7 @@ class PriorityCommandTest(CommandTest): ...@@ -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" 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.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -119,7 +119,7 @@ class PriorityCommandTest(CommandTest): ...@@ -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" 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.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -129,7 +129,7 @@ class PriorityCommandTest(CommandTest): ...@@ -129,7 +129,7 @@ class PriorityCommandTest(CommandTest):
None) None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_prio4(self): def test_expr_prio4(self):
""" Don't prioritize unrelevant todo items. """ """ Don't prioritize unrelevant todo items. """
...@@ -137,7 +137,7 @@ class PriorityCommandTest(CommandTest): ...@@ -137,7 +137,7 @@ class PriorityCommandTest(CommandTest):
self.error, None) self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_expr_prio5(self): def test_expr_prio5(self):
""" Force prioritizing unrelevant items with additional -x flag. """ """ Force prioritizing unrelevant items with additional -x flag. """
...@@ -145,7 +145,7 @@ class PriorityCommandTest(CommandTest): ...@@ -145,7 +145,7 @@ class PriorityCommandTest(CommandTest):
self.error, None) self.error, None)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"Priority set to D.\n| 5| (D) Baz id:1\n") "Priority set to D.\n| 5| (D) Baz id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -155,7 +155,7 @@ class PriorityCommandTest(CommandTest): ...@@ -155,7 +155,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
...@@ -164,7 +164,7 @@ class PriorityCommandTest(CommandTest): ...@@ -164,7 +164,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
...@@ -173,7 +173,7 @@ class PriorityCommandTest(CommandTest): ...@@ -173,7 +173,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 98.\nInvalid todo number given: 99.\n") self.assertEqual(self.errors, "Invalid todo number given: 98.\nInvalid todo number given: 99.\n")
...@@ -182,7 +182,7 @@ class PriorityCommandTest(CommandTest): ...@@ -182,7 +182,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid priority given.\n") self.assertEqual(self.errors, "Invalid priority given.\n")
...@@ -190,7 +190,7 @@ class PriorityCommandTest(CommandTest): ...@@ -190,7 +190,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand(["A"], self.todolist, self.out, self.error) command = PriorityCommand(["A"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
...@@ -198,7 +198,7 @@ class PriorityCommandTest(CommandTest): ...@@ -198,7 +198,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand(["1"], self.todolist, self.out, self.error) command = PriorityCommand(["1"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
...@@ -210,7 +210,7 @@ class PriorityCommandTest(CommandTest): ...@@ -210,7 +210,7 @@ class PriorityCommandTest(CommandTest):
self.todolist, self.out, self.error, None) self.todolist, self.out, self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n") u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
...@@ -224,7 +224,7 @@ class PriorityCommandTest(CommandTest): ...@@ -224,7 +224,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n") self.assertEqual(self.errors, "Invalid priority given.\n")
...@@ -237,7 +237,7 @@ class PriorityCommandTest(CommandTest): ...@@ -237,7 +237,7 @@ class PriorityCommandTest(CommandTest):
self.error) self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n") self.assertEqual(self.errors, "Invalid priority given.\n")
...@@ -245,7 +245,7 @@ class PriorityCommandTest(CommandTest): ...@@ -245,7 +245,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand([], self.todolist, self.out, self.error) command = PriorityCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") 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): ...@@ -42,7 +42,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22") 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.output, "| 1| Foo due:2014-10-22\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
def test_add_tag2(self): def test_add_tag2(self):
command = TagCommand(["Foo", "due", "2014-10-22"], self.todolist, command = TagCommand(["Foo", "due", "2014-10-22"], self.todolist,
...@@ -52,7 +52,7 @@ class TagCommandTest(CommandTest): ...@@ -52,7 +52,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22") 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.output, "| 1| Foo due:2014-10-22\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
def test_add_tag3(self): def test_add_tag3(self):
command = TagCommand(["-a", "2", "due", "2014-10-19"], self.todolist, command = TagCommand(["-a", "2", "due", "2014-10-19"], self.todolist,
...@@ -64,14 +64,14 @@ class TagCommandTest(CommandTest): ...@@ -64,14 +64,14 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output, self.assertEqual(self.output,
"| 2| Bar due:2014-10-22 due:2014-10-19\n") "| 2| Bar due:2014-10-22 due:2014-10-19\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
def test_add_tag4(self): def test_add_tag4(self):
command = TagCommand(["Foox", "due", "2014-10-22"], self.todolist, command = TagCommand(["Foox", "due", "2014-10-22"], self.todolist,
self.out, self.error) self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number.\n") self.assertEqual(self.errors, "Invalid todo number.\n")
...@@ -91,7 +91,7 @@ class TagCommandTest(CommandTest): ...@@ -91,7 +91,7 @@ class TagCommandTest(CommandTest):
self.out, self.error) self.out, self.error)
command.execute() 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.output, "| 3| Baz due:2014-10-20\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -100,7 +100,7 @@ class TagCommandTest(CommandTest): ...@@ -100,7 +100,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "all") self.out, self.error, lambda t: "all")
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -109,7 +109,7 @@ class TagCommandTest(CommandTest): ...@@ -109,7 +109,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "1") self.out, self.error, lambda t: "1")
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -118,7 +118,7 @@ class TagCommandTest(CommandTest): ...@@ -118,7 +118,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "2") self.out, self.error, lambda t: "2")
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -127,7 +127,7 @@ class TagCommandTest(CommandTest): ...@@ -127,7 +127,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "") self.out, self.error, lambda t: "")
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -136,7 +136,7 @@ class TagCommandTest(CommandTest): ...@@ -136,7 +136,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "99") self.out, self.error, lambda t: "99")
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -145,7 +145,7 @@ class TagCommandTest(CommandTest): ...@@ -145,7 +145,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "99") self.out, self.error, lambda t: "99")
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
"| 4| Fnord due:2014-10-20 due:2014-10-20\n") "| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -156,7 +156,7 @@ class TagCommandTest(CommandTest): ...@@ -156,7 +156,7 @@ class TagCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "| 3| Baz due:2015-11-19\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -169,7 +169,7 @@ class TagCommandTest(CommandTest): ...@@ -169,7 +169,7 @@ class TagCommandTest(CommandTest):
self.error) self.error)
command.execute() 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.output, "| 3| Baz due:2014-10-20 foo:today\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -177,7 +177,7 @@ class TagCommandTest(CommandTest): ...@@ -177,7 +177,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["1", "due"], self.todolist, self.out, self.error) command = TagCommand(["1", "due"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo\n") self.assertEqual(self.output, "| 1| Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -185,7 +185,7 @@ class TagCommandTest(CommandTest): ...@@ -185,7 +185,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["2", "due"], self.todolist, self.out, self.error) command = TagCommand(["2", "due"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 2| Bar\n") self.assertEqual(self.output, "| 2| Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -194,7 +194,7 @@ class TagCommandTest(CommandTest): ...@@ -194,7 +194,7 @@ class TagCommandTest(CommandTest):
self.error, lambda t: "all") self.error, lambda t: "all")
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, self.assertEqual(self.output,
" 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord\n") " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -204,7 +204,7 @@ class TagCommandTest(CommandTest): ...@@ -204,7 +204,7 @@ class TagCommandTest(CommandTest):
lambda t: "1") lambda t: "1")
command.execute() 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.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-22\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -213,7 +213,7 @@ class TagCommandTest(CommandTest): ...@@ -213,7 +213,7 @@ class TagCommandTest(CommandTest):
lambda t: "99") lambda t: "99")
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -222,7 +222,7 @@ class TagCommandTest(CommandTest): ...@@ -222,7 +222,7 @@ class TagCommandTest(CommandTest):
lambda t: "A") lambda t: "A")
command.execute() 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.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, "") self.assertEqual(self.errors, "")
...@@ -230,7 +230,7 @@ class TagCommandTest(CommandTest): ...@@ -230,7 +230,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["5", "due"], self.todolist, self.out, self.error) command = TagCommand(["5", "due"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number.\n") self.assertEqual(self.errors, "Invalid todo number.\n")
...@@ -238,7 +238,7 @@ class TagCommandTest(CommandTest): ...@@ -238,7 +238,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["A", "due"], self.todolist, self.out, self.error) command = TagCommand(["A", "due"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number.\n") self.assertEqual(self.errors, "Invalid todo number.\n")
...@@ -247,7 +247,7 @@ class TagCommandTest(CommandTest): ...@@ -247,7 +247,7 @@ class TagCommandTest(CommandTest):
self.error, lambda t: "A") self.error, lambda t: "A")
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 4| Fnord\n") self.assertEqual(self.output, "| 4| Fnord\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -255,7 +255,7 @@ class TagCommandTest(CommandTest): ...@@ -255,7 +255,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["4"], self.todolist, self.out, self.error) command = TagCommand(["4"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
......
...@@ -40,12 +40,12 @@ class TodoListTester(TopydoTest): ...@@ -40,12 +40,12 @@ class TodoListTester(TopydoTest):
def test_contexts(self): def test_contexts(self):
self.assertEqual(set(['Context1', 'Context2']), self.assertEqual(set(['Context1', 'Context2']),
self.todolist.contexts()) self.todolist.contexts())
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_projects(self): def test_projects(self):
self.assertEqual(set(['Project1', 'Project2']), self.assertEqual(set(['Project1', 'Project2']),
self.todolist.projects()) self.todolist.projects())
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_add1(self): def test_add1(self):
text = "(C) Adding a new task @Context3 +Project3" text = "(C) Adding a new task @Context3 +Project3"
...@@ -58,7 +58,7 @@ class TodoListTester(TopydoTest): ...@@ -58,7 +58,7 @@ class TodoListTester(TopydoTest):
self.assertEqual(set(['Context1', 'Context2', 'Context3']), self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts()) self.todolist.contexts())
self.assertEqual(self.todolist.number(todo), 6) self.assertEqual(self.todolist.number(todo), 6)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
def test_add2(self): def test_add2(self):
text = str(self.todolist) text = str(self.todolist)
...@@ -101,7 +101,7 @@ class TodoListTester(TopydoTest): ...@@ -101,7 +101,7 @@ class TodoListTester(TopydoTest):
self.assertEqual(self.todolist.todo(2).source(), self.assertEqual(self.todolist.todo(2).source(),
"(C) Baz @Context1 +Project1 key:value") "(C) Baz @Context1 +Project1 key:value")
self.assertEqual(self.todolist.count(), count - 1) 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) self.assertRaises(InvalidTodoException, self.todolist.number, todo)
def test_delete2(self): def test_delete2(self):
...@@ -112,7 +112,7 @@ class TodoListTester(TopydoTest): ...@@ -112,7 +112,7 @@ class TodoListTester(TopydoTest):
self.todolist.delete(todo) self.todolist.delete(todo)
self.assertEqual(self.todolist.count(), count) self.assertEqual(self.todolist.count(), count)
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_append1(self): def test_append1(self):
todo = self.todolist.todo(3) todo = self.todolist.todo(3)
...@@ -122,7 +122,7 @@ class TodoListTester(TopydoTest): ...@@ -122,7 +122,7 @@ class TodoListTester(TopydoTest):
"(C) Baz @Context1 +Project1 key:value @Context3") "(C) Baz @Context1 +Project1 key:value @Context3")
self.assertEqual(set(['Context1', 'Context2', 'Context3']), self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts()) self.todolist.contexts())
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
def test_append2(self): def test_append2(self):
todo = self.todolist.todo(3) todo = self.todolist.todo(3)
...@@ -145,7 +145,7 @@ class TodoListTester(TopydoTest): ...@@ -145,7 +145,7 @@ class TodoListTester(TopydoTest):
self.assertRaises(InvalidTodoException, self.todolist.todo, self.assertRaises(InvalidTodoException, self.todolist.todo,
count + 100) count + 100)
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_count(self): def test_count(self):
""" Test that empty lines are not counted. """ """ Test that empty lines are not counted. """
...@@ -167,26 +167,26 @@ class TodoListTester(TopydoTest): ...@@ -167,26 +167,26 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo(1) todo = self.todolist.todo(1)
self.todolist.set_todo_completed(todo) self.todolist.set_todo_completed(todo)
self.assertTrue(self.todolist.todo(1).is_completed()) self.assertTrue(self.todolist.todo(1).is_completed())
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
def test_todo_priority1(self): def test_todo_priority1(self):
todo = self.todolist.todo(1) todo = self.todolist.todo(1)
self.todolist.set_priority(todo, 'F') self.todolist.set_priority(todo, 'F')
self.assertEqual(self.todolist.todo(1).priority(), '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): def test_todo_priority2(self):
todo = self.todolist.todo(1) todo = self.todolist.todo(1)
self.todolist.set_priority(todo, 'C') self.todolist.set_priority(todo, 'C')
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.dirty)
def test_erase(self): def test_erase(self):
self.todolist.erase() self.todolist.erase()
self.assertEqual(self.todolist.count(), 0) self.assertEqual(self.todolist.count(), 0)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.dirty)
def test_regex1(self): def test_regex1(self):
""" Multiple hits should result in None. """ """ 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 @@ ...@@ -4,7 +4,7 @@
default_command = ls default_command = ls
; filename = todo.txt ; filename = todo.txt
; archive_filename = done.txt ; archive_filename = done.txt
colors = 1 colors = auto
; identifiers can be 'linenumber' or 'text' ; identifiers can be 'linenumber' or 'text'
identifiers = linenumber identifiers = linenumber
backup_count = 5 backup_count = 5
...@@ -42,6 +42,8 @@ append_parent_contexts = 0 ...@@ -42,6 +42,8 @@ append_parent_contexts = 0
; [light-]gray, darkgray or numbers from 0 to 255. When number is specified color ; [light-]gray, darkgray or numbers from 0 to 255. When number is specified color
; is matched from Xterm color chart available here: ; is matched from Xterm color chart available here:
; http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg ; 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 ; priority_colors = A:cyan,B:yellow,C:blue
; project_color = red ; project_color = red
...@@ -63,3 +65,32 @@ append_parent_contexts = 0 ...@@ -63,3 +65,32 @@ append_parent_contexts = 0
;listcon = lscon ;listcon = lscon
;listcontext = lscon ;listcontext = lscon
;listcontexts = 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. ...@@ -22,7 +22,10 @@ I/O on the command-line.
import getopt import getopt
import sys 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') READ_ONLY_COMMANDS = ('List', 'ListContext', 'ListProject')
...@@ -30,12 +33,14 @@ def usage(): ...@@ -30,12 +33,14 @@ def usage():
""" Prints the command-line usage of topydo. """ """ Prints the command-line usage of topydo. """
print("""\ 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 -h
topydo -v topydo -v
-a : Do not archive todo items on completion. -a : Do not archive todo items on completion.
-c : Specify an alternative configuration file. -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) -d : Specify an alternative archive file (done.txt)
-h : This help text -h : This help text
-t : Specify an alternative todo file -t : Specify an alternative todo file
...@@ -62,20 +67,59 @@ Available commands: ...@@ -62,20 +67,59 @@ Available commands:
Run `topydo help <subcommand>` for command-specific help.\ Run `topydo help <subcommand>` for command-specific help.\
""") """)
def write(p_file, p_string): def write(p_file, p_string):
""" """
Write p_string to file p_file, trailed by a newline character. 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) p_string = escape_ansi(p_string)
if p_string: if p_string:
p_file.write(p_string + "\n") 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): def error(p_string):
""" Writes an error on the standard error. """ """ Writes an error on the standard error. """
write(sys.stderr, p_string) write(sys.stderr, p_string)
...@@ -140,6 +184,9 @@ class CLIApplicationBase(object): ...@@ -140,6 +184,9 @@ class CLIApplicationBase(object):
self.do_archive = False self.do_archive = False
elif opt == "-c": elif opt == "-c":
alt_config_path = value alt_config_path = value
elif opt == "-C":
overrides[('topydo', 'force_colors')] = '1'
overrides[('topydo', 'colors')] = value
elif opt == "-t": elif opt == "-t":
overrides[('topydo', 'filename')] = value overrides[('topydo', 'filename')] = value
elif opt == "-d": elif opt == "-d":
...@@ -174,7 +221,7 @@ class CLIApplicationBase(object): ...@@ -174,7 +221,7 @@ class CLIApplicationBase(object):
command = ArchiveCommand(self.todolist, archive) command = ArchiveCommand(self.todolist, archive)
command.execute() command.execute()
if archive.is_dirty(): if archive.dirty:
archive_file.write(archive.print_todos()) archive_file.write(archive.print_todos())
def _help(self, args): def _help(self, args):
...@@ -189,21 +236,24 @@ class CLIApplicationBase(object): ...@@ -189,21 +236,24 @@ class CLIApplicationBase(object):
READ_ONLY_COMMANDS) READ_ONLY_COMMANDS)
return p_command.__module__.endswith(read_only_commands) return p_command.__module__.endswith(read_only_commands)
def _execute(self, p_command, p_args): def _backup(self, p_command, p_args):
"""
Execute a subcommand with arguments. p_command is a class (not an
object).
"""
if config().backup_count() > 0 and p_command and not self.is_read_only(p_command): 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" call = [p_command.__module__.lower()[16:-7]] + p_args # strip "topydo.commands" and "Command"
from topydo.lib.ChangeSet import ChangeSet from topydo.lib.ChangeSet import ChangeSet
self.backup = ChangeSet(self.todolist, p_call=call) 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( command = p_command(
p_args, p_args,
self.todolist, self.todolist,
lambda o: write(sys.stdout, o), output,
error, error,
input) input)
...@@ -219,7 +269,7 @@ class CLIApplicationBase(object): ...@@ -219,7 +269,7 @@ class CLIApplicationBase(object):
to the todo.txt file. 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 # do not archive when the value of the filename is an empty string
# (i.e. explicitly left empty in the configuration # (i.e. explicitly left empty in the configuration
if self.do_archive and config().archive(): if self.do_archive and config().archive():
...@@ -233,6 +283,7 @@ class CLIApplicationBase(object): ...@@ -233,6 +283,7 @@ class CLIApplicationBase(object):
self.backup.save(self.todolist) self.backup.save(self.todolist)
self.todofile.write(self.todolist.print_todos()) self.todofile.write(self.todolist.print_todos())
self.todolist.dirty = False
self.backup = None self.backup = None
......
...@@ -101,6 +101,7 @@ class PromptApplication(CLIApplicationBase): ...@@ -101,6 +101,7 @@ class PromptApplication(CLIApplicationBase):
continue continue
except ValueError as verr: except ValueError as verr:
error('Error: ' + str(verr)) error('Error: ' + str(verr))
continue
mtime_after = _todotxt_mtime() mtime_after = _todotxt_mtime()
......
...@@ -45,6 +45,12 @@ def main(): ...@@ -45,6 +45,12 @@ def main():
PromptApplication().run() PromptApplication().run()
except ImportError: except ImportError:
error("You have to install prompt-toolkit to run prompt mode.") 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: else:
CLIApplication().run() CLIApplication().run()
except IndexError: except IndexError:
......
...@@ -69,7 +69,7 @@ class PostponeCommand(MultiCommand): ...@@ -69,7 +69,7 @@ class PostponeCommand(MultiCommand):
# pylint: disable=E1103 # pylint: disable=E1103
todo.set_tag(config().tag_due(), new_due.isoformat()) todo.set_tag(config().tag_due(), new_due.isoformat())
self.todolist.set_dirty() self.todolist.dirty = True
self.out(self.printer.print_todo(todo)) self.out(self.printer.print_todo(todo))
else: else:
self.error("Invalid date pattern given.") self.error("Invalid date pattern given.")
......
...@@ -106,7 +106,7 @@ class TagCommand(Command): ...@@ -106,7 +106,7 @@ class TagCommand(Command):
self.todo.set_tag(self.tag, self.value, self.force_add, p_old_value) self.todo.set_tag(self.tag, self.value, self.force_add, p_old_value)
if old_src != self.todo.source(): if old_src != self.todo.source():
self.todolist.set_dirty() self.todolist.dirty = True
def _set(self): def _set(self):
if len(self.current_values) > 1 and not self.force_add: 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 @@ ...@@ -16,8 +16,16 @@
import configparser import configparser
import os import os
import re
import shlex 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): class ConfigError(Exception):
def __init__(self, p_text): def __init__(self, p_text):
...@@ -54,7 +62,8 @@ class _Config: ...@@ -54,7 +62,8 @@ class _Config:
self.defaults = { self.defaults = {
'topydo': { 'topydo': {
'default_command': 'ls', 'default_command': 'ls',
'colors': '1', 'colors': 'auto',
'force_colors': '0',
'filename': 'todo.txt', 'filename': 'todo.txt',
'archive_filename': 'done.txt', 'archive_filename': 'done.txt',
'identifiers': 'linenumber', 'identifiers': 'linenumber',
...@@ -107,11 +116,43 @@ class _Config: ...@@ -107,11 +116,43 @@ class _Config:
'listcontext': 'lscon', 'listcontext': 'lscon',
'listcontexts': '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.config = {}
self.cp = configparser.RawConfigParser() self.cp = configparser.RawConfigParser()
# allow uppercase config keys
self.cp.optionxform = lambda option: option
for section in self.defaults: for section in self.defaults:
self.cp.add_section(section) self.cp.add_section(section)
...@@ -121,7 +162,8 @@ class _Config: ...@@ -121,7 +162,8 @@ class _Config:
files = [ files = [
"/etc/topydo.conf", "/etc/topydo.conf",
self._home_config_path(), home_config_path('.config/topydo/config'),
home_config_path('.topydo'),
".topydo", ".topydo",
"topydo.conf", "topydo.conf",
"topydo.ini", "topydo.ini",
...@@ -144,17 +186,44 @@ class _Config: ...@@ -144,17 +186,44 @@ class _Config:
if not self.cp.has_section(section): if not self.cp.has_section(section):
self.cp.add_section(section) self.cp.add_section(section)
def _home_config_path(self):
return os.path.join(os.path.expanduser('~'), '.topydo')
def default_command(self): def default_command(self):
return self.cp.get('topydo', 'default_command') 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: try:
return self.cp.getboolean('topydo', 'colors') forced = self.cp.get('topydo', 'force_colors') == '1'
except ValueError: 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): def todotxt(self):
return os.path.expanduser(self.cp.get('topydo', 'filename')) return os.path.expanduser(self.cp.get('topydo', 'filename'))
...@@ -237,53 +306,53 @@ class _Config: ...@@ -237,53 +306,53 @@ class _Config:
return [] if hidden_tags == '' else [tag.strip() for tag in return [] if hidden_tags == '' else [tag.strip() for tag in
hidden_tags.split(',')] 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. 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): def _str_to_dict(p_string):
pri_colors_dict = dict() pri_colors_dict = dict()
for pri_color in p_string.split(','): for pri_color in p_string.split(','):
pri, color = pri_color.split(':') pri, color = pri_color.split(':')
pri_colors_dict[pri] = color pri_colors_dict[pri] = Color(color)
return pri_colors_dict return pri_colors_dict
try: try:
pri_colors_str = self.cp.get('colorscheme', 'priority_colors')
if pri_colors_str == '': if pri_colors_str == '':
pri_colors_dict = {'A': '', 'B': '', 'C': ''} pri_colors_dict = _str_to_dict('A:-1,B:-1,C:-1')
else: else:
pri_colors_dict = _str_to_dict(pri_colors_str) pri_colors_dict = _str_to_dict(pri_colors_str)
except ValueError: except ValueError:
pri_colors_dict = _str_to_dict(self.defaults['colorscheme']['priority_colors']) 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): def project_color(self):
try: try:
return self.cp.get('colorscheme', 'project_color') return Color(self.cp.getint('colorscheme', 'project_color'))
except ValueError: except ValueError:
return int(self.defaults['colorscheme']['project_color']) return Color(self.cp.get('colorscheme', 'project_color'))
def context_color(self): def context_color(self):
try: try:
return self.cp.get('colorscheme', 'context_color') return Color(self.cp.getint('colorscheme', 'context_color'))
except ValueError: except ValueError:
return int(self.defaults['colorscheme']['context_color']) return Color(self.cp.get('colorscheme', 'context_color'))
def metadata_color(self): def metadata_color(self):
try: try:
return self.cp.get('colorscheme', 'metadata_color') return Color(self.cp.getint('colorscheme', 'metadata_color'))
except ValueError: except ValueError:
return int(self.defaults['colorscheme']['metadata_color']) return Color(self.cp.get('colorscheme', 'metadata_color'))
def link_color(self): def link_color(self):
try: try:
return self.cp.get('colorscheme', 'link_color') return Color(self.cp.getint('colorscheme', 'link_color'))
except ValueError: except ValueError:
return int(self.defaults['colorscheme']['link_color']) return Color(self.cp.get('colorscheme', 'link_color'))
def auto_creation_date(self): def auto_creation_date(self):
try: try:
...@@ -314,6 +383,28 @@ class _Config: ...@@ -314,6 +383,28 @@ class _Config:
""" Returns the list format used by `ls` """ """ Returns the list format used by `ls` """
return self.cp.get('ls', 'list_format') 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): def config(p_path=None, p_overrides=None):
""" """
......
...@@ -45,40 +45,12 @@ class ExpressionCommand(Command): ...@@ -45,40 +45,12 @@ class ExpressionCommand(Command):
def _filters(self): def _filters(self):
filters = [] 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: if not self.show_all:
filters.append(Filter.DependencyFilter(self.todolist)) filters.append(Filter.DependencyFilter(self.todolist))
filters.append(Filter.RelevanceFilter()) 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: if not self.show_all:
filters.append(Filter.LimitFilter(self.limit)) filters.append(Filter.LimitFilter(self.limit))
......
...@@ -318,3 +318,32 @@ MATCHES = [ ...@@ -318,3 +318,32 @@ MATCHES = [
(_ORDINAL_TAG_MATCH, OrdinalTagFilter), (_ORDINAL_TAG_MATCH, OrdinalTagFilter),
(_PRIORITY_MATCH, PriorityFilter), (_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): ...@@ -112,7 +112,7 @@ class IcalPrinter(Printer):
if not uid: if not uid:
uid = generate_uid() uid = generate_uid()
p_todo.set_tag('ical', uid) p_todo.set_tag('ical', uid)
self.todolist.set_dirty() self.todolist.dirty = True
return uid return uid
......
...@@ -20,7 +20,8 @@ import arrow ...@@ -20,7 +20,8 @@ import arrow
import re import re
from topydo.lib.Config import config 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>.+?)}})?' MAIN_PATTERN = (r'^({{(?P<before>.+?)}})?'
r'(?P<placeholder>{ph}|\[{ph}\])' r'(?P<placeholder>{ph}|\[{ph}\])'
...@@ -107,7 +108,7 @@ def _truncate(p_str, p_repl): ...@@ -107,7 +108,7 @@ def _truncate(p_str, p_repl):
Place of the truncation is calculated depending on p_max_width. Place of the truncation is calculated depending on p_max_width.
""" """
# 4 is for '...' and an extra space at the end # 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) truncated_str = re.sub(re.escape(p_repl), p_repl[:text_lim] + '...', p_str)
return truncated_str return truncated_str
...@@ -119,7 +120,7 @@ def _right_align(p_str): ...@@ -119,7 +120,7 @@ def _right_align(p_str):
Right alignment is done using proper number of spaces calculated from Right alignment is done using proper number of spaces calculated from
'line_width' attribute. 'line_width' attribute.
""" """
to_fill = _columns() - len(p_str) to_fill = _columns() - len(escape_ansi(p_str))
if to_fill > 0: if to_fill > 0:
p_str = re.sub('\t', ' '*to_fill, p_str) p_str = re.sub('\t', ' '*to_fill, p_str)
...@@ -128,6 +129,12 @@ def _right_align(p_str): ...@@ -128,6 +129,12 @@ def _right_align(p_str):
return 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): class ListFormatParser(object):
""" Parser of format string. """ """ Parser of format string. """
def __init__(self, p_todolist, p_format=None): def __init__(self, p_todolist, p_format=None):
...@@ -196,6 +203,8 @@ class ListFormatParser(object): ...@@ -196,6 +203,8 @@ class ListFormatParser(object):
# relative completion date # relative completion date
'X': lambda t: 'x ' + humanize_date(t.completion_date()) if t.is_completed() else '', '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() self.format_list = self._preprocess_format()
...@@ -272,7 +281,7 @@ class ListFormatParser(object): ...@@ -272,7 +281,7 @@ class ListFormatParser(object):
parsed_str = _unescape_percent_sign(''.join(parsed_list)) parsed_str = _unescape_percent_sign(''.join(parsed_list))
parsed_str = _remove_redundant_spaces(parsed_str) 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) parsed_str = _truncate(parsed_str, repl_trunc)
if re.search('.*\t', parsed_str): if re.search('.*\t', parsed_str):
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
from topydo.lib.prettyprinters.Colors import PrettyPrinterColorFilter from topydo.lib.prettyprinters.Colors import PrettyPrinterColorFilter
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.TopydoString import TopydoString
class Printer(object): class Printer(object):
...@@ -29,11 +30,8 @@ class Printer(object): ...@@ -29,11 +30,8 @@ class Printer(object):
raise NotImplementedError raise NotImplementedError
def print_list(self, p_todos): def print_list(self, p_todos):
""" for todo in p_todos:
Given a list of todo items, pretty print it and return a list of self.print_todo(todo)
formatted strings.
"""
return "\n".join([self.print_todo(todo) for todo in p_todos])
class PrettyPrinter(Printer): class PrettyPrinter(Printer):
...@@ -68,7 +66,15 @@ class PrettyPrinter(Printer): ...@@ -68,7 +66,15 @@ class PrettyPrinter(Printer):
for ppf in self.filters: for ppf in self.filters:
todo_str = ppf.filter(todo_str, p_todo) 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): 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): ...@@ -52,7 +52,7 @@ class TodoListBase(object):
self._id_todo_map = {} self._id_todo_map = {}
self.add_list(p_todostrings) self.add_list(p_todostrings)
self.dirty = False self._dirty = False
def __iter__(self): def __iter__(self):
""" """
...@@ -227,11 +227,13 @@ class TodoListBase(object): ...@@ -227,11 +227,13 @@ class TodoListBase(object):
""" """
return View(p_sorter, p_filters, self) return View(p_sorter, p_filters, self)
def is_dirty(self): @property
return self.dirty def dirty(self):
return self._dirty
def set_dirty(self): @dirty.setter
self.dirty = True def dirty(self, p_flag):
self._dirty = p_flag
def todos(self): def todos(self):
return self._todos return self._todos
...@@ -277,4 +279,4 @@ class TodoListBase(object): ...@@ -277,4 +279,4 @@ class TodoListBase(object):
this list. this list.
""" """
printer = PrettyPrinter() 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 ...@@ -23,12 +23,6 @@ import re
from collections import namedtuple from collections import namedtuple
from datetime import date 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): def date_string_to_date(p_date):
""" """
...@@ -61,19 +55,57 @@ def escape_ansi(p_string): ...@@ -61,19 +55,57 @@ def escape_ansi(p_string):
escape_ansi.pattern = re.compile(r'\x1b[^m]*m') 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, Try to determine terminal size at run time. If that is not possible,
returns the default size of 80x24. 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: try:
sz = _get_terminal_size() return get_terminal_size.getter()
except ValueError: except AttributeError:
""" if p_getter:
This can result from the 'underlying buffer being detached', which get_terminal_size.getter = p_getter
occurs during running the unittest on Windows (but not on Linux?) else:
""" def inner():
terminal_size = namedtuple('Terminal_Size', 'columns lines') try:
sz = terminal_size(80, 24) # shutil.get_terminal_size was added to the standard
# library in Python 3.3
return sz 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): ...@@ -25,14 +25,14 @@ class View(object):
""" """
def __init__(self, p_sorter, p_filters, p_todolist): def __init__(self, p_sorter, p_filters, p_todolist):
self._todolist = p_todolist self.todolist = p_todolist
self._sorter = p_sorter self._sorter = p_sorter
self._filters = p_filters self._filters = p_filters
@property @property
def todos(self): def todos(self):
""" Returns a sorted and filtered list of todos in this view. """ """ 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: for _filter in self._filters:
result = _filter.filter(result) result = _filter.filter(result)
......
...@@ -18,9 +18,10 @@ ...@@ -18,9 +18,10 @@
import re 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.Config import config
from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter
from topydo.lib.TopydoString import TopydoString
class PrettyPrinterColorFilter(PrettyPrinterFilter): class PrettyPrinterColorFilter(PrettyPrinterFilter):
...@@ -33,41 +34,26 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter): ...@@ -33,41 +34,26 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
def filter(self, p_todo_str, p_todo): def filter(self, p_todo_str, p_todo):
""" Applies the colors. """ """ Applies the colors. """
if config().colors(): if config().colors():
colorscheme = Colors() p_todo_str = TopydoString(p_todo_str, p_todo)
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()
priority_color = NEUTRAL_COLOR priority_color = config().priority_color(p_todo.priority())
try:
priority_color = priority_colors[p_todo.priority()]
except KeyError:
pass
# color projects / contexts colors = [
p_todo_str = re.sub( (r'\B@(\S*\w)', AbstractColor.CONTEXT),
r'\B(\+|@)(\S*\w)', (r'\B\+(\S*\w)', AbstractColor.PROJECT),
lambda m: ( (r'\b\S+:[^/\s]\S*\b', AbstractColor.META),
context_color if m.group(0)[0] == "@" (r'(^|\s)(\w+:){1}(//\S+)', AbstractColor.LINK),
else project_color) + m.group(0) + priority_color, ]
p_todo_str)
# tags for pattern, color in colors:
p_todo_str = re.sub(r'\b\S+:[^/\s]\S*\b', for match in re.finditer(pattern, p_todo_str.data):
metadata_color + r'\g<0>' + priority_color, p_todo_str.set_color(match.start(), color)
p_todo_str) p_todo_str.set_color(match.end(), priority_color)
# add link_color to any valid URL specified outside of the tag. p_todo_str.append('', AbstractColor.NEUTRAL)
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
# color by priority # color by priority
p_todo_str = priority_color + p_todo_str p_todo_str.set_color(0, priority_color)
return p_todo_str 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