Commit 851e2cdd authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'master' into dot

Revived the dot subcommand and the dot printer for 'ls'.
parents f2072530 798ed50b
......@@ -14,3 +14,4 @@ exclude_lines =
omit =
topydo/commands/ExitCommand.py
topydo/lib/Version.py
topydo/ui/*
......@@ -8,8 +8,9 @@ python:
install:
- "python -m pip install pip --upgrade"
- "pip install ."
- "pip install .[columns]"
- "pip install .[ical]"
- "pip install .[prompt-toolkit]"
- "pip install .[prompt]"
- "pip install .[test]"
- "pip install pylint"
- "pip install codecov"
......
0.10
----
A major release, introducing a new user interface (TUI). Special thanks go to
@mruwek for helping out to get this UI in its current shape.
* New: A column-based user interface. Each column has its own filters and sort
order, allowing you to build a dashboard with your todo items. Launch with
`topydo columns`.
* New: color blocks that change from green to red (overdue) as time passes by.
Use the %z placeholder to add color blocks to the `ls` output.
* New: color option can be set to 0, 1, 16 or 256 and if needed overridden by
-C on the commandline.
* New: recurrence based on business days. Skips Saturdays and Sundays when
calculating the next date (thanks to @mruwek).
* New: items can be sorted by length (use 'length' as sort field).
* New: parents-of and children-of operators with `add`, `dep` and `append`
subcommands. The todo item receives the same parents/children from the
specified todo item.
* New: `append` understands relative dates and other tags that are special to
`add` (thanks to @rameshg87).
* Fix: dependency ID creation with orphan todo items.
* Fix: crash after completing/deleting an edited item.
* Fix: crash after completing an item that got a new dependency with `dep add`
* Change: a new tag value with an existing key can be added with the tag
subcommand (thanks to @MinchinWeb)
* Change: ~/.config/topydo/config can be used as a configuration file.
* Change: No backups are written for read-only commands (e.g. lsprj)
* Change: topydo is more scalable for large todo.txt files.
* Known issue: color blocks are not shown in `ls` output in the column UI.
0.9
---
* Dropped support for Python 2.7.
* Add ability to filter on creation/completion dates:
topydo ls created:today
topydo ls completed:today
topydo -t done.txt completed:today # if auto-archiving is set
* `ls -F` supports `%P` that expands to a single space when no priority is set,
in contrast to `%p` which expands to an empty string (thanks to @MinchinWeb).
* `ls -N` prints enough todo items such that it fits on one screen (thanks to
@MinchinWeb).
* Aliases can have a `{}` placeholder which is substituted with the alias'
arguments (thanks to @mruwek).
* `pri` accepts priorities in lowercase (thanks to @MinchinWeb).
* Several bugfixes for `dep gc`.
* Various test/CI improvements.
0.8
---
......@@ -21,8 +75,8 @@ for the majority of these new features.
* `ls` output can be customized with a -F flag or a configuration option:
[ls]
list_format = |%I| %x %{(}p{)} %c %s %k %{due:}d %{t:}t
[ls]
list_format = |%I| %x %{(}p{)} %c %s %k %{due:}d %{t:}t
or `ls -F "%{(}p{)} %s %{due:}d"`.
......
......@@ -43,10 +43,6 @@ smoothly into topydo.
exit 1
fi
if ! python2 -m pylint --errors-only topydo test; then
exit 1
fi
if ! python3 -m pylint --errors-only topydo test; then
exit 1
fi
......
......@@ -26,12 +26,26 @@ Simply install with:
pip install topydo
### Optional dependencies
### Dependencies
* [arrow][8] : Used to turn dates into a human readable version.
#### Optional dependencies:
* [icalendar][7] : To print your todo.txt file as an iCalendar file
(not supported for Python 3.2).
(not supported for PyPy3).
* [prompt-toolkit][6] : For topydo's _prompt_ mode, which offers a shell-like
interface with auto-completion.
* [arrow][8] : Used to turn dates into a human readable version.
* [urwid][12] : For topydo's _columns_ mode, a TUI with columns for
your todo items.
* [backports.shutil_get_terminal_size][9] : Used to determine your terminal
window size. This function was
added to the standard library in
Python 3.3 and so is only
required for PyPy3.
* [mock][11] : Used for testing. This was added to the standard
library in Python 3.3.
Demo
----
......@@ -46,3 +60,8 @@ Demo
[5]: https://raw.githubusercontent.com/bram85/topydo/master/doc/topydo.gif
[6]: https://github.com/jonathanslenders/python-prompt-toolkit
[7]: https://github.com/collective/icalendar
[8]: https://github.com/crsmithdev/arrow
[9]: https://github.com/chrippa/backports.shutil_get_terminal_size
[10]: https://dateutil.readthedocs.org/
[11]: https://github.com/testing-cabal/mock
[12]: https://github.com/urwid/urwid
......@@ -33,12 +33,15 @@ setup(
],
extras_require = {
':sys_platform=="win32"': ['colorama>=0.2.5'],
':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'],
'columns': ['urwid >= 1.3.0'],
'ical': ['icalendar'],
'prompt-toolkit': ['prompt-toolkit >= 0.53'],
'prompt': ['prompt_toolkit >= 0.53'],
'test': ['coverage', 'freezegun', 'green', ],
'test:python_version=="3.2"': ['mock'],
},
entry_points= {
'console_scripts': ['topydo = topydo.cli.UILoader:main'],
'console_scripts': ['topydo = topydo.ui.UILoader:main'],
},
classifiers = [
"Development Status :: 5 - Production/Stable",
......
......@@ -20,14 +20,19 @@ from topydo.lib.Utils import escape_ansi
class CommandTest(TopydoTest):
def __init__(self, *args, **kwargs):
super(CommandTest, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.output = ""
self.errors = ""
def out(self, p_output):
if p_output:
self.output += escape_ansi(p_output + "\n")
if isinstance(p_output, list) and p_output:
self.output += escape_ansi(
"\n".join([str(s) for s in p_output]) + "\n")
elif p_output:
self.output += str(p_output) + "\n"
def error(self, p_error):
if p_error:
self.errors += escape_ansi(p_error + "\n")
if isinstance(p_error, list) and p_error:
self.errors += escape_ansi(p_error + "\n") + "\n"
elif p_error:
self.errors += str(p_error) + "\n"
[column_keymap]
up = up
<Left> = prev_column
<Esc>d = delete_column
(A) item 1
(B) item 2
(C) item 3
(D) item 4
(E) item 5
(F) item 6
(G) item 7
(H) item 8
(I) item 9
(J) item 10
(K) item 11
(L) item 12
(M) item 13
(N) item 14
(O) item 15
(P) item 16
(Q) item 17
(R) item 18
(S) item 19
(T) item 20
(U) item 21
(V) item 22
(W) item 23
(X) item 24
(Y) item 25
(Z) item 26
(A) item 27
(B) item 28
(C) item 29
(D) item 30
(E) item 31
(F) item 32
(G) item 33
(H) item 34
(I) item 35
(J) item 36
(K) item 37
(L) item 38
(M) item 39
(N) item 40
(O) item 41
(P) item 42
(Q) item 43
(R) item 44
(S) item 45
(T) item 46
(U) item 47
(V) item 48
(W) item 49
(X) item 50
1 No length (zero)
2016-04-25 2 No length (zero)
3 Length of 0 days t:2016-04-25 due:2016-04-25
4 Length of 0 days t:2016-04-25 due:2016-04-25
5 Length of 0 days tomorrow t:2016-04-26 due:2016-04-26
2016-04-25 1 Length of 1 day with creation due:2016-04-26
2 Length of 1 day t:2016-04-25 due:2016-04-26
2016-04-25 Length of 2 days with creation due:2016-04-27
4 Length of 0 days t:2016-04-25 due:2016-04-25
2016-04-25 2 No length (zero)
3 Length of 0 days t:2016-04-25 due:2016-04-25
5 Length of 0 days tomorrow t:2016-04-26 due:2016-04-26
2 Length of 1 day t:2016-04-25 due:2016-04-26
2016-04-25 Length of 2 days with creation due:2016-04-27
2016-04-25 1 Length of 1 day with creation due:2016-04-26
1 No length (zero)
......@@ -3,3 +3,5 @@ foo = rm -f test
baz = FooBar
format = ls -F "|I| x c d {(}p{)} s k" -n 25
smile = ls
star = tag {} star 1
quot = lol'd
......@@ -52,4 +52,4 @@ def todolist_to_string(p_list):
def print_view(p_view):
printer = PrettyPrinter()
return printer.print_list(p_view.todos)
return "\n".join([str(s) for s in printer.print_list(p_view.todos)])
......@@ -33,7 +33,7 @@ except ImportError:
class AddCommandTest(CommandTest):
def setUp(self):
super(AddCommandTest, self).setUp()
super().setUp()
self.todolist = TodoList.TodoList([])
self.today = date.today().isoformat()
......@@ -268,6 +268,61 @@ class AddCommandTest(CommandTest):
self.assertEqual(self.output, "|wb3| {today} Bar p:1 @Context\n|wb3| {today} Bar @Context\n".format(today=self.today))
def add_parentsof_helper(self, p_tag):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Bar before:1"], self.todolist,
self.out, self.error)
command.execute()
command = AddCommand.AddCommand(["Baz {}:2".format(p_tag)],
self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.todo(3).has_tag('p', '1'))
def test_add_dep_parentsof01(self):
self.add_parentsof_helper('parentsof')
def test_add_dep_parentsof02(self):
self.add_parentsof_helper('parentof')
def test_add_dep_parentsof03(self):
self.add_parentsof_helper('parents-of')
def test_add_dep_parentsof04(self):
self.add_parentsof_helper('parent-of')
def add_childrenof_helper(self, p_tag):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Bar before:1"], self.todolist,
self.out, self.error)
command.execute()
command = AddCommand.AddCommand(["Baz {}:1".format(p_tag)],
self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.todo(3).has_tag('id', '2'))
self.assertTrue(self.todolist.todo(2).has_tag('p', '2'))
def test_add_dep_childrenof01(self):
self.add_childrenof_helper('childrenof')
def test_add_dep_childrenof02(self):
self.add_childrenof_helper('childof')
def test_add_dep_childrenof03(self):
self.add_childrenof_helper('children-of')
def test_add_dep_childrenof04(self):
self.add_childrenof_helper('child-of')
def test_add_reldate1(self):
command = AddCommand.AddCommand(["Foo due:today"], self.todolist,
self.out, self.error)
......
......@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from datetime import date
from test.command_testcase import CommandTest
from topydo.commands.AppendCommand import AppendCommand
......@@ -23,9 +24,10 @@ from topydo.lib.TodoList import TodoList
class AppendCommandTest(CommandTest):
def setUp(self):
super(AppendCommandTest, self).setUp()
super().setUp()
self.todolist = TodoList([])
self.todolist.add("Foo")
self.today = date.today().isoformat()
def test_append1(self):
command = AppendCommand([1, "Bar"], self.todolist, self.out,
......@@ -79,6 +81,27 @@ class AppendCommandTest(CommandTest):
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
def test_append8(self):
command = AppendCommand([1, "due:today t:today"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.output,
"| 1| Foo due:%s t:%s\n" % (self.today, self.today))
self.assertEqual(self.errors, "")
def test_append9(self):
self.todolist.add("Qux due:2015-12-21 t:2015-12-21 before:1")
self.todolist.add("Baz")
command = AppendCommand([2, "due:today t:today before:3"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(
self.output,
"| 2| Qux due:%s t:%s p:1 p:2\n" % (self.today, self.today))
self.assertEqual(self.errors, "")
def test_help(self):
command = AppendCommand(["help"], self.todolist, self.out, self.error)
command.execute()
......
......@@ -30,8 +30,8 @@ class ArchiveCommandTest(CommandTest):
command = ArchiveCommand(todolist, archive)
command.execute()
self.assertTrue(todolist.is_dirty())
self.assertTrue(archive.is_dirty())
self.assertTrue(todolist.dirty)
self.assertTrue(archive.dirty)
self.assertEqual(todolist.print_todos(), "x Not complete\n(C) Active")
self.assertEqual(archive.print_todos(), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete")
......
......@@ -19,152 +19,159 @@
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Colors import NEUTRAL_COLOR, Colors
from topydo.lib.Color import Color
from topydo.lib.Config import config
from topydo.lib.Todo import Todo
NEUTRAL_COLOR = '\033[0m'
class ColorsTest(TopydoTest):
def test_project_color1(self):
config(p_overrides={('colorscheme', 'project_color'): '2'})
color = Colors().get_project_color()
self.assertEqual(color, '\033[1;38;5;2m')
self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), '\033[1;32m')
def test_project_color2(self):
config(p_overrides={('colorscheme', 'project_color'): 'Foo'})
color = Colors().get_project_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_project_color3(self):
config(p_overrides={('colorscheme', 'project_color'): 'yellow'})
color = Colors().get_project_color()
self.assertEqual(color, '\033[1;33m')
self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), '\033[1;33m')
def test_project_color4(self):
config(p_overrides={('colorscheme', 'project_color'): '686'})
color = Colors().get_project_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().project_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_context_color1(self):
config(p_overrides={('colorscheme', 'context_color'): '35'})
color = Colors().get_context_color()
self.assertEqual(color, '\033[1;38;5;35m')
self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), '\033[1;38;5;35m')
def test_context_color2(self):
config(p_overrides={('colorscheme', 'context_color'): 'Bar'})
color = Colors().get_context_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_context_color3(self):
config(p_overrides={('colorscheme', 'context_color'): 'magenta'})
color = Colors().get_context_color()
self.assertEqual(color, '\033[1;35m')
self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), '\033[1;35m')
def test_context_color4(self):
config(p_overrides={('colorscheme', 'context_color'): '392'})
color = Colors().get_context_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().context_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_metadata_color1(self):
config(p_overrides={('colorscheme', 'metadata_color'): '128'})
color = Colors().get_metadata_color()
self.assertEqual(color, '\033[1;38;5;128m')
self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), '\033[1;38;5;128m')
def test_metadata_color2(self):
config(p_overrides={('colorscheme', 'metadata_color'): 'Baz'})
color = Colors().get_metadata_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_metadata_color3(self):
config(p_overrides={('colorscheme', 'metadata_color'): 'light-red'})
color = Colors().get_metadata_color()
self.assertEqual(color, '\033[1;1;31m')
self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), '\033[1;1;31m')
def test_metadata_color4(self):
config(p_overrides={('colorscheme', 'metadata_color'): '777'})
color = Colors().get_metadata_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().metadata_color().as_ansi(p_decoration='bold'), NEUTRAL_COLOR)
def test_link_color1(self):
config(p_overrides={('colorscheme', 'link_color'): '77'})
color = Colors().get_link_color()
self.assertEqual(color, '\033[4;38;5;77m')
self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), '\033[4;38;5;77m')
def test_link_color2(self):
config(p_overrides={('colorscheme', 'link_color'): 'FooBar'})
color = Colors().get_link_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), NEUTRAL_COLOR)
def test_link_color3(self):
config(p_overrides={('colorscheme', 'link_color'): 'red'})
color = Colors().get_link_color()
self.assertEqual(color, '\033[4;31m')
self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), '\033[4;31m')
def test_link_color4(self):
config(p_overrides={('colorscheme', 'link_color'): '777'})
color = Colors().get_link_color()
self.assertEqual(color, NEUTRAL_COLOR)
self.assertEqual(config().link_color().as_ansi(p_decoration='underline'), NEUTRAL_COLOR)
def test_priority_color1(self):
config("test/data/ColorsTest1.conf")
colors = Colors()
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
self.assertEqual(colors.get_priority_color('A'), '\033[0;38;5;1m')
self.assertEqual(colors.get_priority_color('B'), '\033[0;38;5;2m')
self.assertEqual(colors.get_priority_color('C'), '\033[0;38;5;3m')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, '\033[0;31m')
self.assertEqual(color_b, '\033[0;32m')
self.assertEqual(color_c, '\033[0;33m')
def test_priority_color2(self):
config("test/data/ColorsTest2.conf")
colors = 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(colors.get_priority_color('A'), '\033[0;35m')
self.assertEqual(colors.get_priority_color('B'), '\033[0;1;36m')
self.assertEqual(colors.get_priority_color('C'), '\033[0;37m')
self.assertEqual(color_a, '\033[0;35m')
self.assertEqual(color_b, '\033[0;1;36m')
self.assertEqual(color_c, '\033[0;37m')
def test_priority_color3(self):
config("test/data/ColorsTest3.conf")
colors = Colors()
self.assertEqual(colors.get_priority_color('A'), '\033[0;35m')
self.assertEqual(colors.get_priority_color('B'), '\033[0;1;36m')
self.assertEqual(colors.get_priority_color('Z'), NEUTRAL_COLOR)
self.assertEqual(colors.get_priority_color('D'), '\033[0;31m')
self.assertEqual(colors.get_priority_color('C'), '\033[0;38;5;7m')
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_z = Todo('(Z) FooBar')
todo_d = Todo('(D) Baz')
todo_c = Todo('(C) FooBaz')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_z = config().priority_color(todo_z.priority()).as_ansi()
color_d = config().priority_color(todo_d.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, '\033[0;35m')
self.assertEqual(color_b, '\033[0;1;36m')
self.assertEqual(color_z, NEUTRAL_COLOR)
self.assertEqual(color_d, '\033[0;31m')
self.assertEqual(color_c, '\033[0;37m')
def test_priority_color4(self):
config("test/data/ColorsTest4.conf")
colors = Colors()
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
self.assertEqual(colors.get_priority_color('A'), NEUTRAL_COLOR)
self.assertEqual(colors.get_priority_color('B'), NEUTRAL_COLOR)
self.assertEqual(colors.get_priority_color('C'), NEUTRAL_COLOR)
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, '')
self.assertEqual(color_b, '')
self.assertEqual(color_c, '')
def test_empty_color_values(self):
config("test/data/ColorsTest5.conf")
colors = Colors()
project_color = colors.get_project_color()
context_color = colors.get_context_color()
link_color = colors.get_link_color()
metadata_color = colors.get_metadata_color()
self.assertEqual(colors.get_priority_color('A'), NEUTRAL_COLOR)
self.assertEqual(colors.get_priority_color('B'), NEUTRAL_COLOR)
self.assertEqual(colors.get_priority_color('C'), NEUTRAL_COLOR)
project_color = config().project_color().as_ansi(p_decoration='bold')
context_color = config().context_color().as_ansi(p_decoration='bold')
link_color = config().link_color().as_ansi(p_decoration='underline')
metadata_color = config().metadata_color().as_ansi(p_decoration='bold')
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, NEUTRAL_COLOR)
self.assertEqual(color_b, NEUTRAL_COLOR)
self.assertEqual(color_c, NEUTRAL_COLOR)
self.assertEqual(project_color, '')
self.assertEqual(context_color, '')
self.assertEqual(link_color, '')
......@@ -172,19 +179,31 @@ class ColorsTest(TopydoTest):
def test_empty_colorscheme(self):
config("test/data/config1")
colors = Colors()
project_color = colors.get_project_color()
context_color = colors.get_context_color()
link_color = colors.get_link_color()
metadata_color = colors.get_metadata_color()
self.assertEqual(colors.get_priority_color('A'), '\033[0;36m')
self.assertEqual(colors.get_priority_color('B'), '\033[0;33m')
self.assertEqual(colors.get_priority_color('C'), '\033[0;34m')
project_color = config().project_color().as_ansi(p_decoration='bold')
context_color = config().context_color().as_ansi(p_decoration='bold')
link_color = config().link_color().as_ansi(p_decoration='underline')
metadata_color = config().metadata_color().as_ansi(p_decoration='bold')
todo_a = Todo('(A) Foo')
todo_b = Todo('(B) Bar')
todo_c = Todo('(C) FooBar')
color_a = config().priority_color(todo_a.priority()).as_ansi()
color_b = config().priority_color(todo_b.priority()).as_ansi()
color_c = config().priority_color(todo_c.priority()).as_ansi()
self.assertEqual(color_a, '\033[0;36m')
self.assertEqual(color_b, '\033[0;33m')
self.assertEqual(color_c, '\033[0;34m')
self.assertEqual(project_color, '\033[1;31m')
self.assertEqual(context_color, '\033[1;35m')
self.assertEqual(link_color, '\033[4;36m')
self.assertEqual(metadata_color, '\033[1;32m')
def test_neutral_color(self):
color = Color('NEUTRAL')
self.assertEqual(color.as_ansi(), NEUTRAL_COLOR)
if __name__ == '__main__':
unittest.main()
......@@ -45,8 +45,7 @@ class ConfigTest(TopydoTest):
""" Bad colour switch value. """
# boolean settings must first be typecast to integers, because all
# strings evaulate to 'True'
self.assertEqual(config("test/data/ConfigTest4.conf").colors(),
bool(int(config().defaults["topydo"]["colors"])))
self.assertEqual(config("test/data/ConfigTest4.conf").colors(), 16)
def test_config06(self):
""" Bad auto creation date switch value. """
......@@ -83,62 +82,76 @@ class ConfigTest(TopydoTest):
self.assertEqual(config("test/data/ConfigTest4.conf").append_parent_contexts(),
bool(int(config().defaults["dep"]["append_parent_contexts"])))
@skip("Error checking not yet implemented")
def test_config14(self):
""" Bad priority color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").priority_colors(),
config().defaults["colorscheme"]["priority_colors"])
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('A').color, 6)
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('B').color, 3)
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('C').color, 4)
@skip("Error checking not yet implemented")
def test_config15(self):
""" Bad project color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").project_color(),
config().defaults["colorscheme"]["project_color"])
self.assertTrue(config("test/data/ConfigTest4.conf").project_color().is_neutral())
@skip("Error checking not yet implemented")
def test_config16(self):
""" Bad context color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").context_color(),
config().defaults["colorscheme"]["context_color"])
self.assertTrue(config("test/data/ConfigTest4.conf").context_color().is_neutral())
@skip("Error checking not yet implemented")
def test_config17(self):
""" Bad metadata color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").metadata_color(),
config().defaults["colorscheme"]["metadata_color"])
self.assertTrue(config("test/data/ConfigTest4.conf").metadata_color().is_neutral())
@skip("Error checking not yet implemented")
def test_config18(self):
""" Bad link color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").link_color(),
config().defaults["colorscheme"]["link_color"])
self.assertTrue(config("test/data/ConfigTest4.conf").link_color().is_neutral())
@skip("Test not yet implemented")
# the test needs to be of the internal function _str_to_dict
def test_config19(self):
""" No priority color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").priority_colors(),
config().defaults["colorscheme"]["priority_colors"])
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('A').color, 6)
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('B').color, 3)
self.assertEqual(config("test/data/ConfigTest4.conf").priority_color('C').color, 4)
def test_config20(self):
""" No project color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").project_color(),
config().defaults["colorscheme"]["project_color"])
self.assertEqual(config("test/data/ConfigTest5.conf").project_color().color, 1)
def test_config21(self):
""" No context color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").context_color(),
config().defaults["colorscheme"]["context_color"])
self.assertEqual(config("test/data/ConfigTest5.conf").context_color().color, 5)
def test_config22(self):
""" No metadata color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").metadata_color(),
config().defaults["colorscheme"]["metadata_color"])
self.assertEqual(config("test/data/ConfigTest5.conf").metadata_color().color, 2)
def test_config23(self):
""" No link color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").link_color(),
config().defaults["colorscheme"]["link_color"])
self.assertEqual(config("test/data/ConfigTest5.conf").link_color().color, 6)
def test_config24(self):
""" column_keymap test. """
keymap, keystates = config("test/data/ConfigTest6.conf").column_keymap()
self.assertEqual(keymap['pp'], 'postpone')
self.assertEqual(keymap['ps'], 'postpone_s')
self.assertEqual(keymap['pr'], 'pri')
self.assertEqual(keymap['pra'], 'cmd pri {} a')
self.assertIn('p', keystates)
self.assertIn('g', keystates)
self.assertIn('pp', keystates)
self.assertIn('ps', keystates)
self.assertIn('pr', keystates)
self.assertEqual(keymap['up'], 'up')
self.assertIn('u', keystates)
self.assertEqual(keymap['<Left>'], 'prev_column')
self.assertNotIn('<Lef', keystates)
self.assertEqual(keymap['<Esc>d'], 'delete_column')
self.assertNotIn('<Esc', keystates)
self.assertIn('<Esc>', keystates)
if __name__ == '__main__':
unittest.main()
......@@ -33,7 +33,7 @@ def _no_prompt(self):
class DeleteCommandTest(CommandTest):
def setUp(self):
super(DeleteCommandTest, self).setUp()
super().setUp()
todos = [
"Foo id:1",
"Bar p:1",
......@@ -48,7 +48,7 @@ class DeleteCommandTest(CommandTest):
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Bar")
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -58,7 +58,7 @@ class DeleteCommandTest(CommandTest):
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Bar")
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -68,7 +68,7 @@ class DeleteCommandTest(CommandTest):
_yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.output,
"| 2| Bar p:1\nRemoved: Bar\nRemoved: Foo\n")
......@@ -79,7 +79,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -89,7 +89,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -98,7 +98,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["2"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Foo")
self.assertEqual(self.output, "Removed: Bar p:1\nThe following todo item(s) became active:\n| 1| Foo\n")
self.assertEqual(self.errors, "")
......@@ -107,7 +107,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["99"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -115,7 +115,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["A"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -159,7 +159,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
......@@ -169,7 +169,7 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: A.\n")
......@@ -181,7 +181,7 @@ class DeleteCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -193,7 +193,7 @@ class DeleteCommandTest(CommandTest):
result = "Removed: a @test with due:2015-06-03\nRemoved: a @test with +project\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -203,7 +203,7 @@ class DeleteCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Removed: a @test with due:2015-06-03\n")
self.assertEqual(self.errors, "")
......@@ -212,7 +212,7 @@ class DeleteCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_del4(self):
""" Remove only relevant todo items. """
......@@ -222,7 +222,7 @@ class DeleteCommandTest(CommandTest):
result = "Foo"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 1)
self.assertEqual(self.todolist.print_todos(), result)
......@@ -232,14 +232,14 @@ class DeleteCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.count(), 0)
def test_empty(self):
command = DeleteCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......
......@@ -23,7 +23,7 @@ from topydo.lib.TodoList import TodoList
class DepCommandTest(CommandTest):
def setUp(self):
super(DepCommandTest, self).setUp()
super().setUp()
todos = [
"Foo id:1",
"Bar p:1",
......@@ -40,7 +40,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -50,7 +50,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -60,7 +60,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -69,7 +69,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -77,7 +77,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["add", "1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -86,7 +86,7 @@ class DepCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -96,7 +96,7 @@ class DepCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('p', '2'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -106,7 +106,7 @@ class DepCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('p', '2'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -116,11 +116,72 @@ class DepCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
def add_parentsof_helper(self, p_args):
command = DepCommand(p_args, self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(4).has_tag('p', '1'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
def test_add10(self):
self.add_parentsof_helper(["add", "4", "parents-of", "2"])
def test_add11(self):
self.add_parentsof_helper(["add", "4", "parent-of", "2"])
def test_add12(self):
self.add_parentsof_helper(["add", "4", "parentsof", "2"])
def test_add13(self):
self.add_parentsof_helper(["add", "4", "parentof", "2"])
def test_add14(self):
command = DepCommand(["add", "4", "parents-of", "5"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
def add_childrenof_helper(self, p_args):
command = DepCommand(p_args, self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(2).has_tag('p', '2'))
self.assertTrue(self.todolist.todo(3).has_tag('p', '2'))
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
def test_add15(self):
self.add_childrenof_helper(["add", "4", "children-of", "1"])
def test_add16(self):
self.add_childrenof_helper(["add", "4", "child-of", "1"])
def test_add17(self):
self.add_childrenof_helper(["add", "4", "childrenof", "1"])
def test_add18(self):
self.add_childrenof_helper(["add", "4", "childof", "1"])
def test_add19(self):
command = DepCommand(["add", "4", "children-of", "5"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
def rm_helper(self, p_args):
"""
Helper function that checks the removal of the dependency from todo 1
......@@ -129,7 +190,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(p_args, self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).has_tag('id', '1'))
self.assertFalse(self.todolist.todo(3).has_tag('p', '1'))
self.assertEqual(self.output, "")
......@@ -152,7 +213,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -161,7 +222,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -169,7 +230,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["rm", "1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -178,7 +239,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 2| Bar p:1\n| 3| Baz p:1\n")
self.assertEqual(self.errors, "")
......@@ -187,7 +248,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -196,7 +257,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -205,7 +266,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -213,7 +274,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["ls", "1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -221,7 +282,7 @@ class DepCommandTest(CommandTest):
command = DepCommand(["ls"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -230,7 +291,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -239,7 +300,7 @@ class DepCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertFalse(self.output)
self.assertFalse(self.errors)
self.assertFalse(self.todolist.todo(5).has_tag('p', '99'))
......@@ -256,7 +317,7 @@ class DepCommandTest(CommandTest):
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_no_subsubcommand(self):
command = DepCommand([], self.todolist, self.out, self.error)
......@@ -264,7 +325,7 @@ class DepCommandTest(CommandTest):
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_help(self):
command = DepCommand(["help"], self.todolist, self.out, self.error)
......
......@@ -23,7 +23,7 @@ from topydo.lib.TodoList import TodoList
class DepriCommandTest(CommandTest):
def setUp(self):
super(DepriCommandTest, self).setUp()
super().setUp()
todos = [
"(A) Foo",
"Bar",
......@@ -39,7 +39,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["1"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).priority(), None)
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\n")
self.assertEqual(self.errors, "")
......@@ -48,7 +48,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["2"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.todolist.todo(2).priority(), None)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -57,7 +57,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["Foo"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).priority(), None)
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\n")
self.assertEqual(self.errors, "")
......@@ -67,7 +67,7 @@ class DepriCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).priority(), None)
self.assertEqual(self.todolist.todo(3).priority(), None)
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\nPriority removed.\n| 3| Baz\n")
......@@ -80,7 +80,7 @@ class DepriCommandTest(CommandTest):
result = "Priority removed.\n| 4| a @test with due:2015-06-03\nPriority removed.\n| 5| a @test with +project p:1\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -91,7 +91,7 @@ class DepriCommandTest(CommandTest):
result = "Priority removed.\n| 4| a @test with due:2015-06-03\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -100,7 +100,7 @@ class DepriCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_depri4(self):
""" Don't remove priority from unrelevant todo items. """
......@@ -108,7 +108,7 @@ class DepriCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_depri5(self):
""" Force unprioritizing unrelevant items with additional -x flag. """
......@@ -116,7 +116,7 @@ class DepriCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority removed.\n| 6| Bax id:1\n")
self.assertEqual(self.errors, "")
......@@ -124,7 +124,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand(["99"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -133,7 +133,7 @@ class DepriCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
......@@ -142,7 +142,7 @@ class DepriCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: FooBar.\n")
......@@ -154,7 +154,7 @@ class DepriCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -163,7 +163,7 @@ class DepriCommandTest(CommandTest):
command = DepriCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......
......@@ -32,7 +32,7 @@ def _no_prompt(self):
class DoCommandTest(CommandTest):
def setUp(self):
super(DoCommandTest, self).setUp()
super().setUp()
todos = [
"Foo id:1",
"Bar p:1",
......@@ -61,7 +61,7 @@ class DoCommandTest(CommandTest):
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
......@@ -77,7 +77,7 @@ class DoCommandTest(CommandTest):
for number in [1, 2, 3]:
self.assertTrue(self.todolist.todo(number).is_completed())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertFalse(self.todolist.todo(4).is_completed())
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -89,7 +89,7 @@ class DoCommandTest(CommandTest):
result = "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {} Foo id:1\n".format(self.today)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(1).is_completed())
self.assertFalse(self.todolist.todo(2).is_completed())
self.assertFalse(self.todolist.todo(3).is_completed())
......@@ -130,7 +130,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(p_flags, self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.count(), 12)
......@@ -162,7 +162,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["99"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -170,7 +170,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["AAA"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -179,7 +179,7 @@ class DoCommandTest(CommandTest):
_yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -209,7 +209,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["5"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.todolist.todo(5).completion_date(),
date(2014, 10, 18))
self.assertFalse(self.output)
......@@ -219,7 +219,7 @@ class DoCommandTest(CommandTest):
command = DoCommand(["baz"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
......@@ -230,7 +230,7 @@ class DoCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Completed: x 2014-11-18 Baz p:1\n")
self.assertEqual(self.errors, "")
......@@ -239,7 +239,7 @@ class DoCommandTest(CommandTest):
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x 2014-11-18 Bar p:1\nCompleted: x 2014-11-18 Baz p:1\nCompleted: x 2014-11-18 Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -248,7 +248,7 @@ class DoCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Completed: x 2014-11-18 Baz p:1\n")
self.assertEqual(self.errors, "")
......@@ -257,7 +257,7 @@ class DoCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.errors, "")
......@@ -271,7 +271,7 @@ class DoCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 12| {today} Recurring! rec:1d due:{today}\nCompleted: x {yesterday} Recurring! rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "")
......@@ -285,7 +285,7 @@ class DoCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 12| {today} Recurring! rec:1d due:{today}\nCompleted: x {yesterday} Recurring! rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "")
......@@ -298,7 +298,7 @@ class DoCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 12| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {yesterday} Strict due:2014-01-01 rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "")
......@@ -310,7 +310,7 @@ class DoCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.yesterday))
self.assertEqual(self.errors, "")
......@@ -323,7 +323,7 @@ class DoCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.yesterday))
self.assertEqual(self.errors, "")
......@@ -385,7 +385,7 @@ class DoCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -394,7 +394,7 @@ class DoCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Completed: x {t} a @test with due:2015-06-03\nCompleted: x {t} a @test with +project\n".format(t=self.today))
self.assertEqual(self.errors, "")
......@@ -403,7 +403,7 @@ class DoCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Completed: x {} a @test with due:2015-06-03\n".format(self.today))
self.assertEqual(self.errors, "")
......@@ -412,7 +412,7 @@ class DoCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_do4(self):
""" Don't do anything with unrelevant todo items. """
......@@ -420,7 +420,7 @@ class DoCommandTest(CommandTest):
None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_do5(self):
""" Force marking unrelevant items as done with additional -x flag. """
......@@ -430,7 +430,7 @@ class DoCommandTest(CommandTest):
result = "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {t} Bar p:1\nCompleted: x {t} Baz p:1\nCompleted: x {t} Foo id:1\n".format(t=self.today)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -450,7 +450,7 @@ class DoCommandTest(CommandTest):
command = DoCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......
......@@ -33,7 +33,7 @@ except ImportError:
class EditCommandTest(CommandTest):
def setUp(self):
super(EditCommandTest, self).setUp()
super().setUp()
todos = [
"Foo id:1",
"Bar p:1 @test",
......@@ -56,7 +56,7 @@ class EditCommandTest(CommandTest):
command.execute()
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.print_todos(), u"Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1")
@mock.patch('topydo.commands.EditCommand._is_edited')
......@@ -72,7 +72,7 @@ class EditCommandTest(CommandTest):
None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat")
......@@ -82,7 +82,7 @@ class EditCommandTest(CommandTest):
None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_edit04(self):
......@@ -91,7 +91,7 @@ class EditCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, "Invalid todo number given: 5.\n")
def test_edit05(self):
......@@ -102,7 +102,7 @@ class EditCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -119,7 +119,7 @@ class EditCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(),
u"Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat")
......@@ -137,7 +137,7 @@ class EditCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.errors, "Editing aborted. Nothing to do.\n")
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nBar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a")
......@@ -158,7 +158,7 @@ class EditCommandTest(CommandTest):
expected = u"| 3| Lazy Cat\n| 4| Lazy Dog\n"
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, expected)
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog")
......
......@@ -301,7 +301,7 @@ class FilterTest(TopydoTest):
class OrdinalTagFilterTest(TopydoTest):
def setUp(self):
super(OrdinalTagFilterTest, self).setUp()
super().setUp()
today = date.today()
tomorrow = today + timedelta(1)
......@@ -379,9 +379,87 @@ class OrdinalTagFilterTest(TopydoTest):
self.assertEqual(result[0].source(), self.todo3)
class CreationFilterTest(TopydoTest):
def setUp(self):
super().setUp()
self.todo1 = "2015-12-19 With creation date."
self.todo2 = "Without creation date."
self.todos = [Todo(self.todo1), Todo(self.todo2)]
def test_filter1(self):
cf = Filter.CreationFilter('create:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter2(self):
cf = Filter.CreationFilter('creation:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter3(self):
cf = Filter.CreationFilter('created:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
class CompletionFilterTest(TopydoTest):
def setUp(self):
super().setUp()
self.todo1 = "2015-12-19 With creation date."
self.todo2 = "x 2015-12-19 2015-12-18 Without creation date."
self.todo3 = "x 2015-12-18 Without creation date."
self.todos = [Todo(self.todo1), Todo(self.todo2), Todo(self.todo3)]
def test_filter1(self):
cf = Filter.CompletionFilter('complete:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter2(self):
cf = Filter.CompletionFilter('completed:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter3(self):
cf = Filter.CompletionFilter('completion:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter4(self):
cf = Filter.CompletionFilter('completion:<=2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 2)
self.assertEqual(result[0].source(), self.todo2)
self.assertEqual(result[1].source(), self.todo3)
class PriorityFilterTest(TopydoTest):
def setUp(self):
super(PriorityFilterTest, self).setUp()
super().setUp()
self.todo1 = "(A) Foo"
self.todo2 = "(B) Bar"
......
......@@ -21,8 +21,8 @@ from topydo.Commands import get_subcommand
from topydo.commands.AddCommand import AddCommand
from topydo.commands.DeleteCommand import DeleteCommand
from topydo.commands.ListCommand import ListCommand
from topydo.commands.ListProjectCommand import ListProjectCommand
from topydo.lib.Config import config
from topydo.commands.TagCommand import TagCommand
from topydo.lib.Config import config, ConfigError
class GetSubcommandTest(TopydoTest):
def test_normal_cmd(self):
......@@ -60,6 +60,14 @@ class GetSubcommandTest(TopydoTest):
self.assertTrue(issubclass(real_cmd, ListCommand))
self.assertEqual(final_args, [u"\u263b"])
def test_alias04(self):
config("test/data/aliases.conf")
args = ["star", "foo"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, TagCommand))
self.assertEqual(final_args, ["foo", "star", "1"])
def test_default_cmd01(self):
args = ["bar"]
real_cmd, final_args = get_subcommand(args)
......@@ -111,6 +119,15 @@ class GetSubcommandTest(TopydoTest):
real_cmd, final_args = get_subcommand(args)
self.assertEqual(real_cmd, None)
def test_alias_quotation(self):
config("test/data/aliases.conf")
args = ["quot"]
with self.assertRaises(ConfigError) as ce:
get_subcommand(args)
self.assertEqual(str(ce.exception), 'No closing quotation')
def test_help(self):
real_cmd, final_args = get_subcommand(['help', 'nonexisting'])
self.assertFalse(real_cmd)
......
......@@ -22,7 +22,7 @@ from topydo.lib.Graph import DirectedGraph
class GraphTest(TopydoTest):
def setUp(self):
super(GraphTest, self).setUp()
super().setUp()
self.graph = DirectedGraph()
......
......@@ -17,23 +17,33 @@
import codecs
import re
import unittest
from collections import namedtuple
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListCommand import ListCommand
from topydo.lib.Config import config
# We're searching for 'mock'
# 'mock' was added as 'unittest.mock' in Python 3.3, but PyPy 3 is based on Python 3.2
# pylint: disable=no-name-in-module
try:
from unittest import mock
except ImportError:
import mock
class ListCommandTest(CommandTest):
def setUp(self):
super(ListCommandTest, self).setUp()
super().setUp()
self.todolist = load_file_to_todolist("test/data/ListCommandTest.txt")
self.terminal_size = namedtuple('terminal_size', ['columns', 'lines'])
def test_list01(self):
command = ListCommand([""], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -42,7 +52,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -51,7 +61,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -59,7 +69,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.errors, "")
......@@ -68,7 +78,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
......@@ -77,7 +87,7 @@ class ListCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
......@@ -86,7 +96,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -95,7 +105,7 @@ class ListCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -104,7 +114,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -114,7 +124,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["project"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
......@@ -125,7 +135,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -134,7 +144,7 @@ class ListCommandTest(CommandTest):
self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.errors, "")
......@@ -144,7 +154,7 @@ class ListCommandTest(CommandTest):
command = ListCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " | 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n | 4| (C) Drink beer @ home\n | 5| (C) 13 + 29 = 42\n | 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -152,7 +162,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["p:<10"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -162,7 +172,7 @@ class ListCommandTest(CommandTest):
command = ListCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "|t5c| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n|wa5| (C) Drink beer @ home\n|z63| (C) 13 + 29 = 42\n|mfg| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -171,7 +181,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output,
"| 3| (C) Baz @Context1 +Project1 key:value\n")
self.assertEqual(self.errors, "")
......@@ -181,7 +191,7 @@ class ListCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
def test_list19(self):
......@@ -192,7 +202,7 @@ class ListCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 id:1 key:value\n| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
......@@ -200,7 +210,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-f text"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -209,7 +219,7 @@ class ListCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
......@@ -221,7 +231,7 @@ class ListCommandTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, '| 1| Foo.\n')
def test_list31(self):
......@@ -313,8 +323,64 @@ class ListCommandTest(CommandTest):
self.assertEqual(self.errors, "option -z not recognized\n")
def test_list42(self):
command = ListCommand(["-x", "+Project1", "-id:1"], self.todolist, self.out,
self.error)
command = ListCommand(["-x", "+Project1", "-id:1"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_list43(self):
"""Test basic 'N' parameter."""
command = ListCommand(["-N"], self.todolist, self.out, self.error)
command.execute()
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, "")
@mock.patch('topydo.commands.ListCommand.get_terminal_size')
def test_list44(self, mock_terminal_size):
"""
Test 'N' parameter with output longer than available terminal lines.
"""
self.todolist = load_file_to_todolist("test/data/ListCommand_50_items.txt")
mock_terminal_size.return_value = self.terminal_size(80, 23)
command = ListCommand(["-N"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (A) item 1\n| 27| (A) item 27\n| 2| (B) item 2\n| 28| (B) item 28\n| 3| (C) item 3\n| 29| (C) item 29\n| 4| (D) item 4\n| 30| (D) item 30\n| 5| (E) item 5\n| 31| (E) item 31\n| 6| (F) item 6\n| 32| (F) item 32\n| 7| (G) item 7\n| 33| (G) item 33\n| 8| (H) item 8\n| 34| (H) item 34\n| 9| (I) item 9\n| 35| (I) item 35\n| 10| (J) item 10\n| 36| (J) item 36\n| 11| (K) item 11\n")
self.assertEqual(self.errors, "")
@mock.patch('topydo.commands.ListCommand.get_terminal_size')
def test_list45(self, mock_terminal_size):
"""Test basic 'N' parameter with nine line terminal."""
# have 9 lines on the terminal will print 7 items and leave 2 lines
# for the next prompt
mock_terminal_size.return_value = self.terminal_size(100, 9)
self.todolist = load_file_to_todolist("test/data/ListCommand_50_items.txt")
command = ListCommand(["-N"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (A) item 1\n| 27| (A) item 27\n| 2| (B) item 2\n| 28| (B) item 28\n| 3| (C) item 3\n| 29| (C) item 29\n| 4| (D) item 4\n")
self.assertEqual(self.errors, "")
@mock.patch('topydo.commands.ListCommand.get_terminal_size')
def test_list46(self, mock_terminal_size):
"""Test basic 'N' parameter with zero height terminal."""
# we still print at least 1 item
mock_terminal_size.return_value = self.terminal_size(100, 0)
self.todolist = load_file_to_todolist("test/data/ListCommand_50_items.txt")
command = ListCommand(["-N"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (A) item 1\n")
self.assertEqual(self.errors, "")
def test_list47(self):
command = ListCommand(["created:2015-11-05"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
......@@ -331,7 +397,7 @@ class ListCommandTest(CommandTest):
class ListCommandUnicodeTest(CommandTest):
def setUp(self):
super(ListCommandUnicodeTest, self).setUp()
super().setUp()
self.todolist = load_file_to_todolist("test/data/ListCommandUnicodeTest.txt")
def test_list_unicode1(self):
......@@ -340,7 +406,7 @@ class ListCommandUnicodeTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
expected = u"| 1| (C) And some sp\u00e9cial tag:\u25c4\n"
......@@ -354,7 +420,7 @@ class ListCommandJsonTest(CommandTest):
command = ListCommand(["-f", "json"], todolist, self.out, self.error)
command.execute()
self.assertFalse(todolist.is_dirty())
self.assertFalse(todolist.dirty)
jsontext = ""
with codecs.open('test/data/ListCommandTest.json', 'r',
......@@ -370,7 +436,7 @@ class ListCommandJsonTest(CommandTest):
command = ListCommand(["-f", "json"], todolist, self.out, self.error)
command.execute()
self.assertFalse(todolist.is_dirty())
self.assertFalse(todolist.dirty)
jsontext = ""
with codecs.open('test/data/ListCommandUnicodeTest.json', 'r',
......@@ -400,7 +466,7 @@ class ListCommandIcalTest(CommandTest):
self.error)
command.execute()
self.assertTrue(todolist.is_dirty())
self.assertTrue(todolist.dirty)
icaltext = ""
with codecs.open('test/data/ListCommandTest.ics', 'r',
......@@ -417,7 +483,7 @@ class ListCommandIcalTest(CommandTest):
command = ListCommand(["-f", "ical"], todolist, self.out, self.error)
command.execute()
self.assertTrue(todolist.is_dirty())
self.assertTrue(todolist.dirty)
icaltext = ""
with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r',
......
......@@ -25,16 +25,18 @@ from topydo.commands.ListCommand import ListCommand
from topydo.lib.Config import config
# We're searching for 'mock'
# 'mock' was added as 'unittest.mock' in Python 3.3, but PyPy 3 is based on Python 3.2
# pylint: disable=no-name-in-module
try:
from unittest import mock
except ImportError:
import mock
@freeze_time("2015, 11, 06")
class ListFormatTest(CommandTest):
def setUp(self):
super(ListFormatTest, self).setUp()
super().setUp()
self.todolist = load_file_to_todolist("test/data/ListFormat.txt")
self.terminal_size = namedtuple('terminal_size', ['columns', 'lines'])
......@@ -43,7 +45,7 @@ class ListFormatTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox +jumped over the and jar due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
......@@ -60,7 +62,7 @@ class ListFormatTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolore... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
......@@ -77,7 +79,7 @@ class ListFormatTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
......@@ -94,7 +96,7 @@ class ListFormatTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
......@@ -111,7 +113,7 @@ class ListFormatTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolore... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
......@@ -119,6 +121,8 @@ class ListFormatTest(CommandTest):
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format06(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
......@@ -127,8 +131,8 @@ class ListFormatTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| D Bar @Context1 +Project2 (3 months ago, due a month ago, started a month ago)
| 2| Z Lorem ipsum dolorem sit amet. Red @f... lazy:bar (just now, due in 2 days, starts in a day)
result = """| 1| D Bar @Context1 +Project2 (3 months ago, due a month ago, started a month ago)
| 2| Z Lorem ipsum dolorem sit amet. Red @fox ... lazy:bar (today, due in 2 days, starts in a day)
| 3| C Foo @Context2 Not@Context +Project1 Not+Project (4 months ago)
| 4| C Baz @Context1 +Project1 key:value
| 5| Drink beer @ home
......@@ -144,7 +148,7 @@ class ListFormatTest(CommandTest):
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)
result = """| 1| D Bar @Context1 +Project2 (due a month ago, started a month ago)
| 2| Z Lorem ipsum dolorem sit amet. Red @fox +jumped... 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
......@@ -161,7 +165,7 @@ class ListFormatTest(CommandTest):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""2015-08-31 2015-09-30 2015-09-29
result = """2015-08-31 2015-09-30 2015-09-29
2015-11-06 2015-11-08 2015-11-07
2015-07-12
......@@ -178,8 +182,8 @@ x 2014-12-12
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""3 months ago | a month ago | a month ago |
just now | in 2 days | in a day |
result = """3 months ago | a month ago | a month ago |
today | in 2 days | in a day |
4 months ago | | |
| | |
| | |
......@@ -192,7 +196,7 @@ just now | in 2 days | in a day |
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""|1|
result = """|1|
|2| lazy:bar
|3|
|4| key:value
......@@ -206,7 +210,7 @@ just now | in 2 days | in a day |
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| due:2015-09-30 t:2015-09-29
result = """| 1| due:2015-09-30 t:2015-09-29
| 2| due:2015-11-08 lazy:bar t:2015-11-07
| 3|
| 4| key:value
......@@ -220,7 +224,7 @@ just now | in 2 days | in a day |
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| %
result = """| 1| %
| 2| %
| 3| %
| 4| %
......@@ -234,7 +238,7 @@ just now | in 2 days | in a day |
self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox +jumped over the and jar due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
......@@ -250,7 +254,7 @@ just now | in 2 days | in a day |
self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 4| (C) Baz @Context1 +Project1 key:value
"""
......@@ -260,7 +264,7 @@ just now | in 2 days | in a day |
command = ListCommand(["-x", "-F", "%c"], self.todolist, self.out, self.error)
command.execute()
result = u"""2015-08-31
result = """2015-08-31
2015-11-06
2015-07-12
......@@ -273,8 +277,8 @@ just now | in 2 days | in a day |
command = ListCommand(["-x", "-F", "%C"], self.todolist, self.out, self.error)
command.execute()
result = u"""3 months ago
just now
result = """3 months ago
today
4 months ago
......@@ -286,7 +290,7 @@ just now
command = ListCommand(["-x", "-F", "%d"], self.todolist, self.out, self.error)
command.execute()
result = u"""2015-09-30
result = """2015-09-30
2015-11-08
......@@ -299,7 +303,7 @@ just now
command = ListCommand(["-x", "-F", "%D"], self.todolist, self.out, self.error)
command.execute()
result = u"""a month ago
result = """a month ago
in 2 days
......@@ -312,7 +316,7 @@ in 2 days
command = ListCommand(["-x", "-F", "%h"], self.todolist, self.out, self.error)
command.execute()
result = u"""due a month ago, started a month ago
result = """due a month ago, started a month ago
due in 2 days, starts in a day
......@@ -325,8 +329,8 @@ due in 2 days, starts in a day
command = ListCommand(["-x", "-F", "%H"], self.todolist, self.out, self.error)
command.execute()
result = u"""3 months ago, due a month ago, started a month ago
just now, due in 2 days, starts in a day
result = """3 months ago, due a month ago, started a month ago
today, due in 2 days, starts in a day
4 months ago
......@@ -338,7 +342,7 @@ just now, due in 2 days, starts in a day
command = ListCommand(["-x", "-F", "%i"], self.todolist, self.out, self.error)
command.execute()
result = u"""1
result = """1
2
3
4
......@@ -351,7 +355,7 @@ just now, due in 2 days, starts in a day
command = ListCommand(["-x", "-F", "%I"], self.todolist, self.out, self.error)
command.execute()
result = u""" 1
result = """ 1
2
3
4
......@@ -364,7 +368,7 @@ just now, due in 2 days, starts in a day
command = ListCommand(["-x", "-F", "%k"], self.todolist, self.out, self.error)
command.execute()
result = u"""
result = """
lazy:bar
key:value
......@@ -377,7 +381,7 @@ date:2014-12-12
command = ListCommand(["-x", "-F", "%K"], self.todolist, self.out, self.error)
command.execute()
result = u"""due:2015-09-30 t:2015-09-29
result = """due:2015-09-30 t:2015-09-29
due:2015-11-08 lazy:bar t:2015-11-07
key:value
......@@ -390,7 +394,7 @@ date:2014-12-12
command = ListCommand(["-x", "-F", "%p"], self.todolist, self.out, self.error)
command.execute()
result = u"""D
result = """D
Z
C
C
......@@ -419,7 +423,7 @@ Completed but with
command = ListCommand(["-x", "-F", "%S"], self.todolist, self.out, self.error)
command.execute()
result = u"""Bar @Context1 +Project2
result = """Bar @Context1 +Project2
Lorem ipsum dolorem sit amet. Red @fox +jumped...
Foo @Context2 Not@Context +Project1 Not+Project
Baz @Context1 +Project1
......@@ -432,7 +436,7 @@ Completed but with
command = ListCommand(["-x", "-F", "%t"], self.todolist, self.out, self.error)
command.execute()
result = u"""2015-09-29
result = """2015-09-29
2015-11-07
......@@ -445,7 +449,7 @@ Completed but with
command = ListCommand(["-x", "-F", "%T"], self.todolist, self.out, self.error)
command.execute()
result = u"""a month ago
result = """a month ago
in a day
......@@ -458,7 +462,7 @@ in a day
command = ListCommand(["-x", "-F", "%x"], self.todolist, self.out, self.error)
command.execute()
result = u"""
result = """
......@@ -471,7 +475,7 @@ x 2014-12-12
command = ListCommand(["-x", "-F", "%X"], self.todolist, self.out, self.error)
command.execute()
result = u"""
result = """
......@@ -484,7 +488,7 @@ x 11 months ago
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%{{}p{}}"], self.todolist, self.out, self.error)
command.execute()
result = u"""{C}
result = """{C}
{C}
{D}
{Z}
......@@ -497,7 +501,7 @@ x 11 months ago
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%{\%p}p{\%p}"], self.todolist, self.out, self.error)
command.execute()
result = u"""%pC%p
result = """%pC%p
%pC%p
%pD%p
%pZ%p
......@@ -510,7 +514,7 @@ x 11 months ago
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%p%p"], self.todolist, self.out, self.error)
command.execute()
result = u"""CC
result = """CC
CC
DD
ZZ
......@@ -525,7 +529,7 @@ ZZ
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%p{ } %{ }p"], self.todolist, self.out, self.error)
command.execute()
result = u"""C C
result = """C C
C C
D D
Z Z
......@@ -541,7 +545,7 @@ Z Z
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%p{ } %{ }p"], self.todolist, self.out, self.error)
command.execute()
result = u"""C C
result = """C C
C C
D D
Z Z
......@@ -556,7 +560,7 @@ Z Z
command = ListCommand(["-x", "-s", "desc:priority", "-F", " %{ }p"], self.todolist, self.out, self.error)
command.execute()
result = u""" C
result = """ C
C
D
Z
......@@ -572,7 +576,7 @@ Z Z
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%&"], self.todolist, self.out, self.error)
command.execute()
result = u"""
result = """
......@@ -589,7 +593,7 @@ Z Z
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%"], self.todolist, self.out, self.error)
command.execute()
result = u"""
result = """
......@@ -606,7 +610,7 @@ Z Z
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
......@@ -622,7 +626,7 @@ Z Z
command = ListCommand(["-x", "-F", "|%I| %x %{(}p{)} %c %S\\t%K"], self.todolist, self.out, self.error)
command.execute()
result = u"""| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
......@@ -639,28 +643,50 @@ Z Z
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = u""" | 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
result = """ | 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
def test_list_format43(self):
command = ListCommand(["-x", "-F", "%P -"], self.todolist, self.out, self.error)
command.execute()
result = """D -
Z -
C -
C -
-
-
"""
self.assertEqual(self.output, result)
def test_list_format44(self):
command = ListCommand(["-x", "-F", "%i %{(}P{)}"], self.todolist, self.out, self.error)
command.execute()
result = """1 (D)
2 (Z)
3 (C)
4 (C)
5 ( )
6 ( )
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format43(self, mock_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{)}'})
command1 = ListCommand(["-x"], self.todolist, self.out, self.error)
command1.execute()
config(p_overrides={('ls', 'list_format'): '%z|%I| %x %p %S %k\\t%{(}h{)}'})
command2 = ListCommand(["-x"], self.todolist, self.out, self.error)
command2.execute()
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)
......@@ -669,7 +695,21 @@ Z Z
| 5| Drink beer @ home
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result * 2)
self.assertEqual(self.output, result)
def test_list_format46(self):
command = ListCommand(["-x", "-F", "%r"], self.todolist, self.out, self.error)
command.execute()
result = """(D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
(Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox +jumped over the lazy:bar and jar due:2015-11-08 t:2015-11-07
(C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
(C) Baz @Context1 +Project1 key:value
Drink beer @ home id:1 p:2 ical:foobar
x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
if __name__ == '__main__':
unittest.main()
......@@ -24,7 +24,7 @@ from topydo.lib.TodoList import TodoList
class PostponeCommandTest(CommandTest):
def setUp(self):
super(PostponeCommandTest, self).setUp()
super().setUp()
self.today = date.today()
self.past = date.today() - timedelta(1)
self.future = date.today() + timedelta(1)
......@@ -49,7 +49,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -61,7 +61,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -73,7 +73,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -85,7 +85,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), self.start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -97,7 +97,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
start = self.start + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103
self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -109,7 +109,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 4| Past due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -121,7 +121,7 @@ class PostponeCommandTest(CommandTest):
due = self.future + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), self.future_start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -134,7 +134,7 @@ class PostponeCommandTest(CommandTest):
due = self.future + timedelta(7)
start = self.future_start + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -144,7 +144,7 @@ class PostponeCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid date pattern given.\n")
......@@ -153,7 +153,7 @@ class PostponeCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -162,7 +162,7 @@ class PostponeCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -170,7 +170,7 @@ class PostponeCommandTest(CommandTest):
command = PostponeCommand(["1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -181,7 +181,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -193,7 +193,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo due:{}\n| 2| Bar due:{}\n".format(due.isoformat(), due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -204,7 +204,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo due:{}\n| 2| Bar due:{}\n".format(due.isoformat(), due.isoformat()))
self.assertEqual(self.errors, "")
......@@ -216,7 +216,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
start = self.start + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
# pylint: disable=E1103
self.assertEqual(self.output, "| 2| Bar due:{}\n| 3| Baz due:{} t:{}\n".format(due.isoformat(), due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "")
......@@ -226,7 +226,7 @@ class PostponeCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid date pattern given.\n")
......@@ -235,7 +235,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 123.\n")
......@@ -244,7 +244,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: Zoo.\nInvalid todo number given: 99.\nInvalid todo number given: 123.\n")
......@@ -254,7 +254,7 @@ class PostponeCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -267,7 +267,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(14)
result = "| 2| Bar due:{d}\n| 3| Baz due:{d} t:{s}\n".format(d=due.isoformat(), s=self.start.isoformat())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -282,7 +282,7 @@ class PostponeCommandTest(CommandTest):
result = "| 3| Baz due:{} t:{}\n".format(due.isoformat(),
self.start.isoformat())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -291,7 +291,7 @@ class PostponeCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_postpone4(self):
""" Don't postpone unrelevant todo items. """
......@@ -299,7 +299,7 @@ class PostponeCommandTest(CommandTest):
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_postpone5(self):
""" Force postponing unrelevant items with additional -x flag. """
......@@ -310,7 +310,7 @@ class PostponeCommandTest(CommandTest):
due = self.today + timedelta(7)
result = "| 6| FutureStart t:{} due:{}\n".format(self.future.isoformat(), due.isoformat())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......
......@@ -23,7 +23,7 @@ from topydo.lib.TodoList import TodoList
class PriorityCommandTest(CommandTest):
def setUp(self):
super(PriorityCommandTest, self).setUp()
super().setUp()
todos = [
"(A) Foo",
"Bar",
......@@ -39,7 +39,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.errors, "")
......@@ -49,7 +49,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority set to Z.\n| 2| (Z) Bar\n")
self.assertEqual(self.errors, "")
......@@ -58,7 +58,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.errors, "")
......@@ -68,7 +68,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| (A) Foo\n")
self.assertEqual(self.errors, "")
......@@ -77,7 +77,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "")
......@@ -87,7 +87,17 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "")
def test_set_prio7(self):
""" Allow lowercase priority to be set. """
command = PriorityCommand(["Foo", "2", "c"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "")
......@@ -98,7 +108,7 @@ class PriorityCommandTest(CommandTest):
result = "Priority changed from B to C\n| 3| (C) a @test with due:2015-06-03\nPriority set to C.\n| 4| (C) a @test with +project p:1\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -109,7 +119,7 @@ class PriorityCommandTest(CommandTest):
result = "Priority changed from B to C\n| 3| (C) a @test with due:2015-06-03\n"
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
......@@ -119,7 +129,7 @@ class PriorityCommandTest(CommandTest):
None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_prio4(self):
""" Don't prioritize unrelevant todo items. """
......@@ -127,7 +137,7 @@ class PriorityCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_expr_prio5(self):
""" Force prioritizing unrelevant items with additional -x flag. """
......@@ -135,7 +145,7 @@ class PriorityCommandTest(CommandTest):
self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"Priority set to D.\n| 5| (D) Baz id:1\n")
self.assertEqual(self.errors, "")
......@@ -145,7 +155,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given.\n")
......@@ -154,7 +164,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
......@@ -163,7 +173,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 98.\nInvalid todo number given: 99.\n")
......@@ -172,7 +182,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid priority given.\n")
......@@ -180,7 +190,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand(["A"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -188,7 +198,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand(["1"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......@@ -200,7 +210,7 @@ class PriorityCommandTest(CommandTest):
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
......@@ -214,7 +224,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n")
......@@ -227,7 +237,7 @@ class PriorityCommandTest(CommandTest):
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n")
......@@ -235,7 +245,7 @@ class PriorityCommandTest(CommandTest):
command = PriorityCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2016 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from freezegun import freeze_time
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
from topydo.lib.ProgressColor import progress_color
from topydo.lib.Todo import Todo
def set_256_colors():
config(p_overrides={('topydo', 'colors'): '256'})
@freeze_time('2016, 01, 01')
class ProgressColorTest(TopydoTest):
def test_progress1(self):
""" Test progress of task with no length """
color = progress_color(Todo('Foo'))
self.assertEqual(color.color, 2)
def test_progress2(self):
""" Test progress of task with no length (but with creation date). """
color = progress_color(Todo('2016-02-11 Foo'))
self.assertEqual(color.color, 2)
def test_progress3(self):
""" Test progress of task with no length """
set_256_colors()
color = progress_color(Todo('Foo'))
self.assertEqual(color.color, 22)
def test_progress4(self):
""" Test progress of task with no length (but with creation date). """
set_256_colors()
color = progress_color(Todo('2016-02-11 Foo'))
self.assertEqual(color.color, 22)
def test_progress5(self):
""" Test overdue tasks """
color = progress_color(Todo('Foo due:2015-12-31'))
self.assertEqual(color.color, 1)
def test_progress6(self):
""" Test overdue tasks """
set_256_colors()
color = progress_color(Todo('Foo due:2015-12-31'))
self.assertEqual(color.color, 196)
def test_progress7(self):
""" Due today """
color = progress_color(Todo('Foo due:2016-01-01'))
self.assertEqual(color.color, 3)
def test_progress8(self):
""" Due today (256) """
set_256_colors()
color = progress_color(Todo('Foo due:2016-01-01'))
self.assertEqual(color.color, 202)
def test_progress9(self):
""" Due tomorrow """
color = progress_color(Todo('Foo due:2016-01-02'))
# a length of 14 days is assumed
self.assertEqual(color.color, 3)
def test_progress10(self):
set_256_colors()
color = progress_color(Todo('Foo due:2016-01-02'))
# a length of 14 days is assumed
self.assertEqual(color.color, 208)
def test_progress11(self):
""" Due tomorrow (creation date) """
color = progress_color(Todo('2016-01-01 Foo due:2016-01-02'))
# a length of 14 days is assumed
self.assertEqual(color.color, 2)
def test_progress12(self):
""" Due tomorrow (creation date) """
set_256_colors()
color = progress_color(Todo('2016-01-01 Foo due:2016-01-02'))
self.assertEqual(color.color, 22)
def test_progress13(self):
""" Due tomorrow (recurrence) """
color = progress_color(Todo('Foo due:2016-01-02 rec:1d'))
self.assertEqual(color.color, 2)
def test_progress14(self):
""" Due tomorrow (recurrence) """
set_256_colors()
color = progress_color(Todo('Foo due:2016-01-02 rec:1d'))
self.assertEqual(color.color, 22)
def test_progress15(self):
""" Due tomorrow (creation date + recurrence) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 rec:1d'))
self.assertEqual(color.color, 2)
def test_progress16(self):
""" Due tomorrow (creation date + recurrence) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 rec:1d'))
self.assertEqual(color.color, 22)
def test_progress17(self):
""" Due tomorrow (creation date + recurrence + start date) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 rec:1d t:2016-01-02'))
self.assertEqual(color.color, 2)
def test_progress18(self):
""" Due tomorrow (creation date + recurrence + start date) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 rec:1d t:2016-01-02'))
self.assertEqual(color.color, 22)
def test_progress19(self):
""" Due tomorrow (creation date + strict recurrence + start date) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 rec:+1d t:2016-01-02'))
self.assertEqual(color.color, 2)
def test_progress20(self):
""" Due tomorrow (creation date + strict recurrence + start date) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 rec:+1d t:2016-01-02'))
self.assertEqual(color.color, 22)
def test_progress21(self):
""" Due tomorrow (creation date + start date) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 t:2016-01-02'))
self.assertEqual(color.color, 2)
def test_progress22(self):
""" Due tomorrow (creation date + start date) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 t:2016-01-02'))
self.assertEqual(color.color, 22)
def test_progress23(self):
""" Due tomorrow (creation date + start date) """
color = progress_color(Todo('2016-12-01 Foo due:2016-01-02 t:2015-12-31'))
self.assertEqual(color.color, 10)
def test_progress24(self):
""" Due tomorrow (creation date + start date) """
set_256_colors()
color = progress_color(Todo('2015-12-01 Foo due:2016-01-02 t:2015-12-31'))
self.assertEqual(color.color, 118)
def test_progress25(self):
""" Start date after due date """
color = progress_color(Todo('Foo due:2016-01-02 t:2016-01-03'))
# a length of 14 days is assumed
self.assertEqual(color.color, 3)
def test_progress26(self):
""" Start date after due date """
set_256_colors()
color = progress_color(Todo('Foo due:2016-01-02 t:2016-01-03'))
# a length of 14 days is assumed
self.assertEqual(color.color, 208)
def test_progress27(self):
""" Creation date after due date """
set_256_colors()
color = progress_color(Todo('2016-01-03 Foo due:2016-01-02'))
# a length of 14 days is assumed
self.assertEqual(color.color, 208)
if __name__ == '__main__':
unittest.main()
......@@ -25,7 +25,7 @@ from topydo.lib.Todo import Todo
class RecurrenceTest(TopydoTest):
def setUp(self):
super(RecurrenceTest, self).setUp()
super().setUp()
self.todo = Todo("Test rec:1w")
self.stricttodo = Todo("Test rec:+1w")
......
......@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from datetime import date, timedelta
from datetime import date
from freezegun import freeze_time
from test.topydo_testcase import TopydoTest
......@@ -25,7 +25,7 @@ from topydo.lib.RelativeDate import relative_date_to_date
@freeze_time('2015, 11, 06')
class RelativeDateTester(TopydoTest):
def setUp(self):
super(RelativeDateTester, self).setUp()
super().setUp()
self.yesterday = date(2015, 11, 5)
self.today = date(2015, 11, 6)
self.tomorrow = date(2015, 11, 7)
......@@ -40,6 +40,18 @@ class RelativeDateTester(TopydoTest):
result = relative_date_to_date('1d')
self.assertEqual(result, self.tomorrow)
def test_zero_bdays(self):
result = relative_date_to_date('0b')
self.assertEqual(result, self.today)
def test_one_bday(self):
result = relative_date_to_date('1b')
self.assertEqual(result, self.monday)
def test_one_bweek(self):
result = relative_date_to_date('5b')
self.assertEqual(result, self.friday)
def test_one_week(self):
result = relative_date_to_date('1w')
self.assertEqual(result, date(2015, 11, 13))
......@@ -152,6 +164,14 @@ class RelativeDateTester(TopydoTest):
result = relative_date_to_date('-0d')
self.assertTrue(result, self.today)
def test_negative_period3(self):
result = relative_date_to_date('-1b')
self.assertEqual(result, date(2015, 11, 5))
def test_negative_period4(self):
result = relative_date_to_date('-5b')
self.assertEqual(result, date(2015, 10, 30))
def test_weekday_next_week(self):
"""
When entering "Friday" on a Friday, return next week Friday instead of
......
......@@ -35,7 +35,7 @@ from topydo.lib.TodoList import TodoList
class RevertCommandTest(CommandTest):
def setUp(self):
super(RevertCommandTest, self).setUp()
super().setUp()
todos = [
"Foo",
"Bar",
......
......@@ -24,7 +24,7 @@ from topydo.lib.Config import config
class SortCommandTest(CommandTest):
def setUp(self):
super(SortCommandTest, self).setUp()
super().setUp()
self.todolist = load_file_to_todolist("test/data/SorterTest1.txt")
def test_sort1(self):
......
......@@ -14,6 +14,7 @@
# 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.facilities import (load_file, load_file_to_todolist, print_view,
......@@ -23,6 +24,7 @@ from topydo.lib.Config import config
from topydo.lib.Sorter import Sorter
@freeze_time("2016, 04, 25")
class SorterTest(TopydoTest):
def sort_file(self, p_filename, p_filename_ref, p_sorter):
"""
......@@ -189,5 +191,14 @@ class SorterTest(TopydoTest):
'test/data/SorterTest13-result-context.txt', sorter)
def test_sort19(self):
"""
Check sorting by length.
"""
sorter = Sorter('length,text')
self.sort_file('test/data/SorterTest14.txt',
'test/data/SorterTest14-result.txt', sorter)
if __name__ == '__main__':
unittest.main()
......@@ -24,7 +24,7 @@ from topydo.lib.TodoList import TodoList
class TagCommandTest(CommandTest):
def setUp(self):
super(TagCommandTest, self).setUp()
super().setUp()
todos = [
"Foo",
"Bar due:2014-10-22",
......@@ -42,7 +42,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22")
self.assertEqual(self.output, "| 1| Foo due:2014-10-22\n")
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_add_tag2(self):
command = TagCommand(["Foo", "due", "2014-10-22"], self.todolist,
......@@ -52,7 +52,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22")
self.assertEqual(self.output, "| 1| Foo due:2014-10-22\n")
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_add_tag3(self):
command = TagCommand(["-a", "2", "due", "2014-10-19"], self.todolist,
......@@ -64,23 +64,34 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output,
"| 2| Bar due:2014-10-22 due:2014-10-19\n")
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_add_tag4(self):
command = TagCommand(["Foox", "due", "2014-10-22"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number.\n")
def test_force_add_tag01(self):
'''Tries to different values to a tag for the same name 3 times.'''
for letter in ['a', 'b', 'c']:
command = TagCommand(['-a', '1', 'k', letter], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(1).source(), "Foo k:a k:b k:c")
def test_set_tag04(self):
command = TagCommand(["3", "due", "2014-10-20"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 3| Baz due:2014-10-20\n")
self.assertEqual(self.errors, "")
......@@ -89,7 +100,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "all")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
......@@ -98,7 +109,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "1")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -107,7 +118,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "2")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
......@@ -116,7 +127,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -125,7 +136,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "99")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -134,7 +145,7 @@ class TagCommandTest(CommandTest):
self.out, self.error, lambda t: "99")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
"| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
......@@ -145,7 +156,7 @@ class TagCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 3| Baz due:2015-11-19\n")
self.assertEqual(self.errors, "")
......@@ -158,7 +169,7 @@ class TagCommandTest(CommandTest):
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 3| Baz due:2014-10-20 foo:today\n")
self.assertEqual(self.errors, "")
......@@ -166,7 +177,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["1", "due"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "| 1| Foo\n")
self.assertEqual(self.errors, "")
......@@ -174,7 +185,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["2", "due"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 2| Bar\n")
self.assertEqual(self.errors, "")
......@@ -183,7 +194,7 @@ class TagCommandTest(CommandTest):
self.error, lambda t: "all")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output,
" 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord\n")
self.assertEqual(self.errors, "")
......@@ -193,7 +204,7 @@ class TagCommandTest(CommandTest):
lambda t: "1")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -202,7 +213,7 @@ class TagCommandTest(CommandTest):
lambda t: "99")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -211,7 +222,7 @@ class TagCommandTest(CommandTest):
lambda t: "A")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
......@@ -219,7 +230,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["5", "due"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number.\n")
......@@ -227,7 +238,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["A", "due"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number.\n")
......@@ -236,7 +247,7 @@ class TagCommandTest(CommandTest):
self.error, lambda t: "A")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.output, "| 4| Fnord\n")
self.assertEqual(self.errors, "")
......@@ -244,7 +255,7 @@ class TagCommandTest(CommandTest):
command = TagCommand(["4"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
......
......@@ -29,7 +29,7 @@ from topydo.lib.TodoListBase import InvalidTodoException, TodoListBase
class TodoListTester(TopydoTest):
def setUp(self):
super(TodoListTester, self).setUp()
super().setUp()
self.todofile = TodoFile('test/data/TodoListTest.txt')
lines = [line for line in self.todofile.read()
......@@ -40,12 +40,12 @@ class TodoListTester(TopydoTest):
def test_contexts(self):
self.assertEqual(set(['Context1', 'Context2']),
self.todolist.contexts())
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_projects(self):
self.assertEqual(set(['Project1', 'Project2']),
self.todolist.projects())
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_add1(self):
text = "(C) Adding a new task @Context3 +Project3"
......@@ -58,7 +58,7 @@ class TodoListTester(TopydoTest):
self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts())
self.assertEqual(self.todolist.number(todo), 6)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_add2(self):
text = str(self.todolist)
......@@ -101,7 +101,7 @@ class TodoListTester(TopydoTest):
self.assertEqual(self.todolist.todo(2).source(),
"(C) Baz @Context1 +Project1 key:value")
self.assertEqual(self.todolist.count(), count - 1)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
self.assertRaises(InvalidTodoException, self.todolist.number, todo)
def test_delete2(self):
......@@ -112,7 +112,7 @@ class TodoListTester(TopydoTest):
self.todolist.delete(todo)
self.assertEqual(self.todolist.count(), count)
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_append1(self):
todo = self.todolist.todo(3)
......@@ -122,7 +122,7 @@ class TodoListTester(TopydoTest):
"(C) Baz @Context1 +Project1 key:value @Context3")
self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_append2(self):
todo = self.todolist.todo(3)
......@@ -145,7 +145,7 @@ class TodoListTester(TopydoTest):
self.assertRaises(InvalidTodoException, self.todolist.todo,
count + 100)
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_count(self):
""" Test that empty lines are not counted. """
......@@ -167,26 +167,26 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo(1)
self.todolist.set_todo_completed(todo)
self.assertTrue(self.todolist.todo(1).is_completed())
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_todo_priority1(self):
todo = self.todolist.todo(1)
self.todolist.set_priority(todo, 'F')
self.assertEqual(self.todolist.todo(1).priority(), 'F')
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_todo_priority2(self):
todo = self.todolist.todo(1)
self.todolist.set_priority(todo, 'C')
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.todolist.dirty)
def test_erase(self):
self.todolist.erase()
self.assertEqual(self.todolist.count(), 0)
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.dirty)
def test_regex1(self):
""" Multiple hits should result in None. """
......@@ -219,6 +219,13 @@ class TodoListTester(TopydoTest):
config("test/data/todolist-uid.conf")
self.assertRaises(InvalidTodoException, self.todolist.todo, 1)
def test_uid4(self):
"""
Handle UIDs properly when line numbers are configured.
"""
config(p_overrides={('topydo', 'identifiers'): 'linenumber'})
self.assertRaises(InvalidTodoException, self.todolist.todo, '11a')
def test_new_uid(self):
""" Make sure that item has new text ID after append. """
config("test/data/todolist-uid.conf")
......@@ -227,10 +234,23 @@ class TodoListTester(TopydoTest):
self.assertNotEqual(self.todolist.number(todo), 't5c')
def test_iteration(self):
""" Confirms that the iternation method is working. """
results = ["(C) Foo @Context2 Not@Context +Project1 Not+Project",
"(D) Bar @Context1 +Project2",
"(C) Baz @Context1 +Project1 key:value",
"(C) Drink beer @ home",
"(C) 13 + 29 = 42"]
i = 0
for todo in self.todolist:
self.assertEqual(todo.src, results[i])
i += 1
class TodoListDependencyTester(TopydoTest):
def setUp(self):
super(TodoListDependencyTester, self).setUp()
super().setUp()
self.todolist = TodoList([])
self.todolist.add("Foo id:1")
......@@ -242,6 +262,7 @@ class TodoListDependencyTester(TopydoTest):
self.todolist.add("Another one with +Project")
self.todolist.add("Todo with +AnotherProject")
self.todolist.add("Todo without children id:3")
self.todolist.add("Orphan p:4")
def test_check_dep(self):
children = self.todolist.children(self.todolist.todo(1))
......@@ -272,8 +293,8 @@ class TodoListDependencyTester(TopydoTest):
todo5 = self.todolist.todo(5)
self.todolist.add_dependency(todo5, todo4)
self.assertTrue(todo5.has_tag('id', '4'))
self.assertTrue(todo4.has_tag('p', '4'))
self.assertTrue(todo5.has_tag('id', '5'))
self.assertTrue(todo4.has_tag('p', '5'))
def test_add_dep2(self):
"""
......@@ -287,8 +308,8 @@ class TodoListDependencyTester(TopydoTest):
self.todolist.add_dependency(todo5, todo4)
self.todolist.add_dependency(todo4, todo1)
self.assertTrue(todo4.has_tag('id', '5'))
self.assertTrue(todo1.has_tag('p', '5'))
self.assertTrue(todo4.has_tag('id', '6'))
self.assertTrue(todo1.has_tag('p', '6'))
def test_add_dep3(self):
"""
......@@ -322,6 +343,7 @@ class TodoListDependencyTester(TopydoTest):
self.assertFalse(from_todo.has_tag('id'))
self.assertFalse(to_todo.has_tag('p'))
self.assertFalse(self.todolist.todo_by_dep_id('2'))
def test_remove_dep2(self):
old = str(self.todolist)
......@@ -330,6 +352,9 @@ class TodoListDependencyTester(TopydoTest):
self.todolist.remove_dependency(from_todo, to_todo)
self.assertEqual(str(self.todolist), old)
self.assertTrue(self.todolist.todo_by_dep_id('1'))
self.assertTrue(self.todolist.todo_by_dep_id('2'))
self.assertTrue(self.todolist.todo_by_dep_id('3'))
def test_remove_dep3(self):
""" Try to remove non-existing dependency. """
......@@ -339,6 +364,9 @@ class TodoListDependencyTester(TopydoTest):
self.todolist.remove_dependency(from_todo, to_todo)
self.assertEqual(str(self.todolist), old)
self.assertTrue(self.todolist.todo_by_dep_id('1'))
self.assertTrue(self.todolist.todo_by_dep_id('2'))
self.assertTrue(self.todolist.todo_by_dep_id('3'))
def test_remove_todo_check_children(self):
todo = self.todolist.todo(2)
......@@ -351,6 +379,7 @@ class TodoListDependencyTester(TopydoTest):
todo = self.todolist.todo(3)
self.todolist.delete(todo)
self.assertFalse(todo.has_tag('p', '2'))
self.assertFalse(self.todolist.todo_by_dep_id('2'))
todo = self.todolist.todo(1)
children = self.todolist.children(todo)
......@@ -372,6 +401,44 @@ class TodoListDependencyTester(TopydoTest):
self.assertTrue(todolist.todo_by_dep_id('1'))
self.assertFalse(todolist.todo_by_dep_id('2'))
def test_add_after_dependencies(self):
"""
Test that information is properly stored after dependency related
information was retrieved from the todo list.
"""
todo = self.todolist.todo(1)
self.todolist.parents(todo)
self.todolist.add('New dependency id:99')
self.todolist.add('Child p:99')
self.assertTrue(self.todolist.dirty)
self.assertTrue(self.todolist.todo_by_dep_id('99'))
def test_delete01(self):
""" Check that dependency tags are cleaned up. """
todo = self.todolist.todo(4)
self.todolist.delete(todo, p_leave_tags=False)
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(3).source(), "Baz p:1")
def test_delete02(self):
""" Check that dependency tags are left when requested. """
todo = self.todolist.todo(4)
self.todolist.delete(todo, p_leave_tags=True)
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(3).source(), "Baz p:1 id:2")
def test_delete03(self):
""" Check that dependency tags are left when requested. """
todo = self.todolist.todo(3)
self.todolist.delete(todo, p_leave_tags=True)
self.assertTrue(self.todolist.dirty)
self.assertEqual(self.todolist.todo(3).source(), "Buzz p:2")
class TodoListCleanDependencyTester(TopydoTest):
"""
......@@ -383,7 +450,7 @@ class TodoListCleanDependencyTester(TopydoTest):
"""
def setUp(self):
super(TodoListCleanDependencyTester, self).setUp()
super().setUp()
self.todolist = TodoList([])
def test_clean_dependencies1(self):
......@@ -419,19 +486,7 @@ class TodoListCleanDependencyTester(TopydoTest):
self.todolist.clean_dependencies()
self.assertFalse(self.todolist.todo(1).has_tag('id'))
def test_clean_dependencies4(self):
""" Clean p: items when siblings are still connected to parent. """
self.todolist.add("Foo id:1")
self.todolist.add("Bar p:1")
self.todolist.add("Baz p:1 id:2")
self.todolist.add("Buzz p:2 p:1")
self.todolist.clean_dependencies()
self.assertFalse(self.todolist.todo(4).has_tag('p', '1'))
self.assertTrue(self.todolist.todo(1).has_tag('id', '1'))
self.assertTrue(self.todolist.todo(2).has_tag('p', '1'))
self.assertFalse(self.todolist.todo_by_dep_id('1'))
if __name__ == '__main__':
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Utils import translate_key_to_config
class UtilsTest(TopydoTest):
def test_key_to_cfg(self):
ctrl_s = translate_key_to_config('ctrl s')
meta_d = translate_key_to_config('meta d')
esc = translate_key_to_config('esc')
f4 = translate_key_to_config('f4')
self.assertEqual(ctrl_s, '<C-s>')
self.assertEqual(meta_d, '<M-d>')
self.assertEqual(esc, '<Esc>')
self.assertEqual(f4, '<F4>')
if __name__ == '__main__':
unittest.main()
......@@ -4,7 +4,7 @@
default_command = ls
; filename = todo.txt
; archive_filename = done.txt
colors = 1
colors = auto
; identifiers can be 'linenumber' or 'text'
identifiers = linenumber
backup_count = 5
......@@ -42,6 +42,8 @@ append_parent_contexts = 0
; [light-]gray, darkgray or numbers from 0 to 255. When number is specified color
; is matched from Xterm color chart available here:
; http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg
; When using values between 16 and 256, make sure to set colors = 256 in the
; [topydo] section.
; priority_colors = A:cyan,B:yellow,C:blue
; project_color = red
......@@ -52,7 +54,9 @@ append_parent_contexts = 0
[aliases]
;showall = ls -x
;next = ls -n 1
;top = ls -F '|%I| %x %p %S %k %{(}H{)}'
;top = ls -F '%I %x %P %S %k %{(}H{)}' -N
;star = tag {} star 1
;unstar = tag {} star
;lsproj = lsprj
;listprj = lsprj
;listproj = lsprj
......@@ -61,3 +65,35 @@ append_parent_contexts = 0
;listcon = lscon
;listcontext = lscon
;listcontexts = lscon
[columns]
column_width = 40
[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
......@@ -21,7 +21,7 @@ instance based on an argument list.
import sys
from topydo.lib.Config import config
from topydo.lib.Config import config, ConfigError
_SUBCOMMAND_MAP = {
'add': 'AddCommand',
......@@ -71,6 +71,18 @@ def get_subcommand(p_args):
__import__(modulename, globals(), locals(), [classname], 0)
return getattr(sys.modules[modulename], classname)
def join_args(p_cli_args, p_alias_args):
"""
Returns properly joined args from alias definition and from user input.
"""
if '{}' in p_alias_args:
pos = p_alias_args.index('{}')
args = p_alias_args[:pos] + p_cli_args + p_alias_args[pos+1:]
else:
args = p_alias_args + p_cli_args
return args
def resolve_alias(p_alias, p_args):
"""
Resolves a subcommand alias and returns a tuple (Command, args).
......@@ -78,10 +90,14 @@ def get_subcommand(p_args):
If alias resolves to non-existent command, main help message is
returned.
"""
real_subcommand, alias_args = alias_map[p_alias]
try:
real_subcommand, alias_args = alias_map[p_alias]
except ValueError as ve:
raise ConfigError(alias_map[p_alias]) from ve
try:
result = import_subcommand(real_subcommand)
args = alias_args + p_args
args = join_args(p_args, alias_args)
return (result, args)
except KeyError:
return get_subcommand(['help'])
......
......@@ -22,19 +22,17 @@ from datetime import date
from os.path import expanduser
from sys import stdin
from topydo.lib.Command import Command
from topydo.lib.WriteCommand import WriteCommand
from topydo.lib.Config import config
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException
class AddCommand(Command):
class AddCommand(WriteCommand):
def __init__(self, p_args, p_todolist, # pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(AddCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.text = ' '.join(p_args)
self.from_file = None
......@@ -70,56 +68,18 @@ class AddCommand(Command):
return todo_text
def _postprocess_input_todo(p_todo):
"""
Post-processes a parsed todo when adding it to the list.
* It converts relative dates to absolute ones.
* Automatically inserts a creation date if not present.
* Handles more user-friendly dependencies with before:, partof: and
after: tags
"""
def convert_date(p_tag):
value = p_todo.tag_value(p_tag)
if value:
dateobj = relative_date_to_date(value)
if dateobj:
p_todo.set_tag(p_tag, dateobj.isoformat())
def add_dependencies(p_tag):
for value in p_todo.tag_values(p_tag):
try:
dep = self.todolist.todo(value)
if p_tag == 'after':
self.todolist.add_dependency(p_todo, dep)
elif p_tag == 'before' or p_tag == 'partof':
self.todolist.add_dependency(dep, p_todo)
except InvalidTodoException:
pass
p_todo.remove_tag(p_tag, value)
convert_date(config().tag_start())
convert_date(config().tag_due())
add_dependencies('partof')
add_dependencies('before')
add_dependencies('after')
if config().auto_creation_date():
p_todo.set_creation_date(date.today())
todo_text = _preprocess_input_todo(p_todo_text)
todo = self.todolist.add(todo_text)
_postprocess_input_todo(todo)
self.postprocess_input_todo(todo)
if config().auto_creation_date():
todo.set_creation_date(date.today())
self.out(self.printer.print_todo(todo))
def execute(self):
""" Adds a todo item to the list. """
if not super(AddCommand, self).execute():
if not super().execute():
return False
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
......@@ -138,23 +98,22 @@ class AddCommand(Command):
def usage(self):
return """Synopsis:
add <text>
add -f <file>
add -f -"""
add <TEXT>
add -f <FILE> | -"""
def help(self):
return """\
This subcommand automatically adds the creation date to the added item.
<text> may contain:
TEXT may contain:
* Priorities mid-sentence. Example: add "Water flowers (C)"
* Dependencies using before, after and partof tags. They are translated to the
corresponding 'id' and 'p' tags. The values of these tags correspond to the
todo number (not the dependency number).
* Dependencies using before, after, partof, parents-of and children-of tags.
These are translated to the corresponding 'id' and 'p' tags. The values of
these tags correspond to the todo number (not the dependency number).
Example: add "Subtask partof:1"
-f : Add todo items from specified <file> or from standard input.
-f : Add todo items from specified FILE or from standard input.\
"""
......@@ -14,21 +14,23 @@
# 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 topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config
from topydo.lib.Command import InvalidCommandArgument
from topydo.lib.WriteCommand import WriteCommand
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.TodoParser import parse_line
class AppendCommand(Command):
class AppendCommand(WriteCommand):
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(AppendCommand, self).__init__(p_args, p_todolist, p_out, p_err,
super().__init__(p_args, p_todolist, p_out, p_err,
p_prompt)
def execute(self):
if not super(AppendCommand, self).execute():
if not super().execute():
return False
try:
......@@ -37,7 +39,13 @@ class AppendCommand(Command):
if text:
todo = self.todolist.todo(number)
new_text_parsed = parse_line(text)
new_tags = new_text_parsed['tags']
for tag in (config().tag_start(), config().tag_due()):
if tag in new_tags:
todo.remove_tag(tag)
self.todolist.append(todo, text)
self.postprocess_input_todo(todo)
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.out(self.printer.print_todo(todo))
......@@ -49,9 +57,9 @@ class AppendCommand(Command):
self.error("Invalid todo number given.")
def usage(self):
return """Synopsis: append <number> <text>"""
return """Synopsis: append <NUMBER> <TEXT>"""
def help(self):
return """\
Adds the given <text> to the end of the todo indicated by <number>.
Adds the given TEXT to the end of the todo indicated by NUMBER.\
"""
......@@ -27,7 +27,7 @@ class ArchiveCommand(Command):
TodoListBase class which does no dependency checking, so a better
choice for huge done.txt files.
"""
super(ArchiveCommand, self).__init__([], p_todolist)
super().__init__([], p_todolist)
self.archive = p_archive_list
def execute(self):
......
......@@ -22,7 +22,7 @@ class DeleteCommand(DCommand):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(DeleteCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
def prompt_text(self):
......@@ -40,15 +40,15 @@ class DeleteCommand(DCommand):
def usage(self):
return """\
Synopsis: del [-f] <NUMBER1> [<NUMBER2> ...]
del [-x] -e <EXPRESSION>
Synopsis: del [-f] <NUMBER 1> [<NUMBER 2> ...]
del [-x] -e <EXPRESSION>\
"""
def help(self):
return """\
Deletes the todo item(s) with the given number(s) from the list.
It is also possible to delete items as complete with an expression using
the -e flag. Use -x to also process todo items that are normally invisible
(with the 'ls' subcommand).
It is also possible to delete items that match EXPRESSION using the -e flag.
Use -x to also process todo items that are normally invisible (as with the 'ls'
subcommand).\
"""
......@@ -29,7 +29,7 @@ class DepCommand(Command):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(DepCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
try:
......@@ -40,44 +40,66 @@ class DepCommand(Command):
self.printer = pretty_printer_factory(self.todolist)
def _handle_add(self):
(from_todo, to_todo) = self._get_todos()
if from_todo and to_todo:
for from_todo, to_todo in self._get_todos():
self.todolist.add_dependency(from_todo, to_todo)
def _handle_rm(self):
(from_todo, to_todo) = self._get_todos()
if from_todo and to_todo:
for from_todo, to_todo in self._get_todos():
self.todolist.remove_dependency(from_todo, to_todo)
def _get_todos(self):
from_todo = None
to_todo = None
result = []
def get_parent_dependencies():
child_todo = first_todo
sibling_todo = second_todo
return [(parent, child_todo) for parent in self.todolist.parents(sibling_todo)]
def get_child_dependencies():
parent_todo = first_todo
sibling_todo = second_todo
return [(parent_todo, child) for child in self.todolist.children(sibling_todo)]
get_before_dependency = lambda: [(second_todo, first_todo)]
get_after_dependency = lambda: [(first_todo, second_todo)]
operators = {
"after": get_after_dependency,
"before": get_before_dependency,
"child-of": get_child_dependencies,
"childof": get_child_dependencies,
"children-of": get_child_dependencies,
"childrenof": get_child_dependencies,
"parent-of": get_parent_dependencies,
"parentof": get_parent_dependencies,
"parents-of": get_parent_dependencies,
"parentsof": get_parent_dependencies,
"partof": get_before_dependency,
"to": get_after_dependency,
}
try:
first_todo_nr = self.argument(1)
operator = self.argument(2)
if operator == 'before' or operator == 'partof':
from_todo_nr = self.argument(3)
to_todo_nr = self.argument(1)
elif operator == 'to' or operator == 'after':
from_todo_nr = self.argument(1)
to_todo_nr = self.argument(3)
if operator in operators:
second_todo_nr = self.argument(3)
else:
# the operator was omitted, assume 2nd argument is target task
# default to 'to' behavior
from_todo_nr = self.argument(1)
to_todo_nr = self.argument(2)
second_todo_nr = self.argument(2)
operator = "to"
first_todo = self.todolist.todo(first_todo_nr)
second_todo = self.todolist.todo(second_todo_nr)
from_todo = self.todolist.todo(from_todo_nr)
to_todo = self.todolist.todo(to_todo_nr)
result = operators[operator]()
except (InvalidTodoException):
self.error("Invalid todo number given.")
except InvalidCommandArgument:
self.error(self.usage())
return (from_todo, to_todo)
return result
def _handle_ls(self):
""" Handles the ls subsubcommand. """
......@@ -127,7 +149,7 @@ class DepCommand(Command):
def execute(self):
if not super(DepCommand, self).execute():
if not super().execute():
return False
dispatch = {
......@@ -148,7 +170,7 @@ class DepCommand(Command):
def usage(self):
return """Synopsis:
dep <add|rm> <NUMBER> [to] <NUMBER>
dep add <NUMBER> <before|partof|after> <NUMBER>
dep add <NUMBER> <before|partof|after|parents-of|children-of> <NUMBER>
dep ls <NUMBER> to
dep ls to <NUMBER>
dep dot <NUMBER>
......@@ -156,10 +178,11 @@ class DepCommand(Command):
def help(self):
return """\
* add : Adds a dependency. Using 1 before 2 creates a dependency
from todo item 2 to 1.
* add : Adds a dependency. `dep add 1 2` denotes that todo item 1
is dependant on todo item 2, i.e. item 2 is a subitem of
item 1.
* rm (alias: del) : Removes a dependency.
* ls : Lists all dependencies to or from a certain todo.
* dot : Prints a dependency tree as a Dot graph.
* clean (alias: gc) : Removes redundant id or p tags.
* clean (alias: gc) : Removes redundant id or p tags.\
"""
......@@ -23,7 +23,7 @@ class DepriCommand(MultiCommand):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(DepriCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
def _execute_multi_specific(self):
......@@ -37,14 +37,14 @@ class DepriCommand(MultiCommand):
def usage(self):
return """\
Synopsis: depri <NUMBER1> [<NUMBER2> ...]
depri [-x] -e <EXPRESSION>
Synopsis: depri <NUMBER 1> [<NUMBER 2> ...]
depri [-x] -e <EXPRESSION>\
"""
def help(self):
return """Removes the priority of the given todo item(s).
It is also possible to deprioritize items as complete with an expression using
the -e flag. Use -x to also process todo items that are normally invisible
(with the 'ls' subcommand).
It is also possible to deprioritize items as complete with an EXPRESSION using
the -e flag. Use -x to also process todo items that are normally invisible (as
with the 'ls' subcommand).\
"""
......@@ -33,17 +33,17 @@ class DoCommand(DCommand):
self.strict_recurrence = False
self.completion_date = date.today()
super(DoCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
def get_flags(self):
""" Additional flags. """
opts, long_opts = super(DoCommand, self).get_flags()
opts, long_opts = super().get_flags()
return ("d:s" + opts, ["date=", "strict"] + long_opts)
def process_flag(self, p_opt, p_value):
super(DoCommand, self).process_flag(p_opt, p_value)
super().process_flag(p_opt, p_value)
if p_opt == "-s" or p_opt == "--strict":
self.strict_recurrence = True
......@@ -105,28 +105,28 @@ class DoCommand(DCommand):
def usage(self):
return """\
Synopsis: do [--date] [--force] [--strict] <NUMBER1> [<NUMBER2> ...]
do [-x] -e <EXPRESSION>
Synopsis: do [--date <DATE>] [--force] [--strict] <NUMBER 1> [<NUMBER 2> ...]
do [-x] -e <EXPRESSION>\
"""
def help(self):
return """Marks the todo(s) with given number(s) as complete.
return """Marks the todo(s) with given NUMBER(s) as complete.
It is also possible to mark todo items as complete with an expression using the
-e flag. Use -x to also process todo items that are normally invisible (with
It is also possible to mark todo items as complete with an EXPRESSION using the
-e flag. Use -x to also process todo items that are normally invisible (as with
the 'ls' subcommand).
In case a todo has subitems, a question is asked whether the subitems should be
marked as completed as well. When --force is given, no interaction is required
and the subitems are not marked completed.
In case a todo has subitems (dependencies), a question is asked whether the
subitems should be marked as completed as well. When --force is given, no
interaction is required and the subitems are not marked completed.
In case a completed todo is recurring, a new todo will be added to the list,
while the given todo item is marked as complete. The new date is calculated
based on the todo item's due date. If the due date is in the past, today's date
is used to calculate the new recurrence date. Using --strict prevents this,
is used to calculate the new recurrence date. Using --strict prevents this, and
then the actual due date of the todo item is used to calculate the new
recurrence date. Note that a future due date is always used as such to
calculate the new due date.
Use --date to set a custom completion date.
Use --date to set a custom completion date.\
"""
......@@ -27,10 +27,6 @@ from topydo.lib.TodoList import TodoList
# the true and only editor
DEFAULT_EDITOR = 'vi'
# Access the base class of the TodoList instance kept inside EditCommand. We
# cannot use super() inside the class itself
BASE_TODOLIST = lambda tl: super(TodoList, tl)
def _get_file_mtime(p_file):
return os.stat(p_file.name).st_mtime
......@@ -39,7 +35,7 @@ def _is_edited(p_orig_mtime, p_file):
class EditCommand(MultiCommand):
def __init__(self, p_args, p_todolist, p_output, p_error, p_input):
super(EditCommand, self).__init__(p_args, p_todolist, p_output,
super().__init__(p_args, p_todolist, p_output,
p_error, p_input)
if len(self.args) == 0:
......@@ -115,7 +111,7 @@ class EditCommand(MultiCommand):
if _is_edited(orig_mtime, temp_todos):
for todo in self.todos:
BASE_TODOLIST(self.todolist).delete(todo)
self.todolist.delete(todo, p_leave_tags=True)
for todo in new_todos:
self.todolist.add_todo(todo)
......@@ -138,8 +134,8 @@ class EditCommand(MultiCommand):
def usage(self):
return """Synopsis:
edit
edit <NUMBER1> [<NUMBER2> ...]
edit -e [-x] [expression]
edit <NUMBER 1> [<NUMBER 2> ...]
edit -e [-x] [EXPRESSION]
edit -d"""
def help(self):
......@@ -147,15 +143,15 @@ class EditCommand(MultiCommand):
Launches a text editor to edit todos.
Without any arguments it will just open the todo.txt file. Alternatively it can
edit todo item(s) with the given number(s) or edit relevant todos matching
the given expression. See `topydo help ls` for more information on relevant
edit todo item(s) with the given NUMBER(s) or edit relevant todos matching
the given EXPRESSION. See `topydo help ls` for more information on relevant
todo items. It is also possible to open the archive file.
By default it will use $EDITOR in your environment, otherwise it will fall back
to 'vi'.
By default it will look to your environment variable $EDITOR, otherwise it will
fall back to 'vi'.
-e : Treat the subsequent arguments as an expression.
-x : Edit *all* todos matching the expression (i.e. do not filter on
-e : Treat the subsequent arguments as an EXPRESSION.
-x : Edit *all* todos matching the EXPRESSION (i.e. do not filter on
dependencies or relevance).
-d : Open the archive file.
-d : Open the archive file.\
"""
......@@ -26,11 +26,11 @@ class ExitCommand(Command):
"""
def __init__(self, p_args, p_todolist, p_output, p_error, p_input):
super(ExitCommand, self).__init__(p_args, p_todolist, p_output, p_error,
super().__init__(p_args, p_todolist, p_output, p_error,
p_input)
def execute(self):
if not super(ExitCommand, self).execute():
if not super().execute():
return False
sys.exit(0)
......@@ -20,6 +20,7 @@ from topydo.lib.Filter import InstanceFilter
from topydo.lib.printers.PrettyPrinter import pretty_printer_factory
from topydo.lib.prettyprinters.Format import PrettyPrinterFormatFilter
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import get_terminal_size
class ListCommand(ExpressionCommand):
......@@ -27,7 +28,7 @@ class ListCommand(ExpressionCommand):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(ListCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.printer = None
......@@ -50,7 +51,7 @@ class ListCommand(ExpressionCommand):
return True
def _process_flags(self):
opts, args = self.getopt('f:F:i:n:s:x')
opts, args = self.getopt('f:F:i:n:Ns:x')
for opt, value in opts:
if opt == '-x':
......@@ -65,22 +66,26 @@ class ListCommand(ExpressionCommand):
if self._poke_icalendar():
from topydo.lib.printers.Ical import IcalPrinter
self.printer = IcalPrinter(self.todolist)
# a graph without dependencies is not so useful, hence
# show all
self.show_all = True
elif value == 'dot':
from topydo.lib.printers.Dot import DotPrinter
self.printer = DotPrinter(self.todolist)
# a graph without dependencies is not so useful, hence
# show all
self.show_all = True
else:
self.printer = None
elif opt == '-F':
self.format = value
elif opt == '-N':
# 2 lines are assumed to be taken up by printing the next prompt
# display at least one item
self.limit = max(get_terminal_size().lines - 2, 1)
elif opt == '-n':
try:
self.limit = int(value)
except ValueError:
pass # use default value in configuration
pass # use default value in configuration
elif opt == '-i':
self.ids = value.split(',')
......@@ -94,7 +99,7 @@ class ListCommand(ExpressionCommand):
Additional filters to select particular todo items given with the -i
flag.
"""
filters = super(ListCommand, self)._filters()
filters = super()._filters()
if self.ids:
def get_todo(p_id):
......@@ -124,7 +129,6 @@ class ListCommand(ExpressionCommand):
# create a standard printer with some filters
indent = config().list_indent()
final_format = ' ' * indent + self.format
hidden_tags = config().hidden_tags()
filters = []
filters.append(PrettyPrinterFormatFilter(self.todolist, final_format))
......@@ -134,7 +138,7 @@ class ListCommand(ExpressionCommand):
self.out(self.printer.print_list(self._view().todos))
def execute(self):
if not super(ListCommand, self).execute():
if not super().execute():
return False
try:
......@@ -148,20 +152,21 @@ class ListCommand(ExpressionCommand):
return True
def usage(self):
return """Synopsis: ls [-x] [-s <sort_expression>] [-f <output format>]
[-F <format string>] [expression]"""
return """Synopsis: ls [-x] [-s <SORT EXPRESSION>] [-f <OUTPUT FORMAT>]
[-F <FORMAT STRING>] [-i <NUMBER 1>[,<NUMBER 2> ...]] [-N | -n <INTEGER>]
[EXPRESSION]"""
def help(self):
return """\
Lists all relevant todos. A todo is relevant when:
* has not been completed yet;
* the start date (if present) has passed;
* has not been completed yet,
* the start date (if present) has passed, and
* there are no subitems that need to be completed.
When an expression is given, only the todos matching that expression are shown.
When an EXPRESSION is given, only the todos matching that EXPRESSION are shown.
-f : Specify the output format, being 'text' (default), 'dot' or 'ical' or
-f : Specify the OUTPUT format, being 'text' (default), 'dot' or 'ical' or
'json'.
* 'text' - Text output with colors and indentation if applicable.
......@@ -172,6 +177,7 @@ When an expression is given, only the todos matching that expression are shown.
an 'ical' tag with a unique ID. Completed todo items may be
archived.
* 'json' - Javascript Object Notation (JSON)
-F : Specify the format of the text ('text' format), which may contain
placeholders that may be expanded if the todo has such attribute. If such
attribute does not exist, then it expands to an empty string.
......@@ -187,6 +193,7 @@ When an expression is given, only the todos matching that expression are shown.
%k: List of tags separated by spaces (excluding hidden tags).
%K: List of all tags separated by spaces.
%p: Priority.
%P: Priority or placeholder space if no priority.
%s: Todo text.
%S: Todo text, truncated such that an item fits on one line.
%t: Absolute creation date.
......@@ -198,12 +205,14 @@ When an expression is given, only the todos matching that expression are shown.
Conditional characters can be added with blocks surrounded by curly
braces, they will only appear when a placeholder expanded to a value.
E.g. %{(}p{)} will print (C) when the todo item has priority C, or ''
E.g. %{(}p{)} will print '(C)' when the todo item has priority C, or ''
(empty string) when an item has no priority set.
A tab character serves as a marker to start right alignment.
-i : Comma separated list of todo IDs to print.
-s : Sort the list according to a sort expression. Defaults to the expression
-n : Number of items to display. Defaults to the value in the configuration.
-N : Limit number of items displayed such that they fit on the terminal.
-s : Sort the list according to a SORT EXPRESSION. Defaults to the expression
in the configuration.
-x : Show all todos (i.e. do not filter on dependencies or relevance).
-x : Show all todos (i.e. do not filter on dependencies or relevance).\
"""
......@@ -22,11 +22,11 @@ class ListContextCommand(Command):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(ListContextCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
def execute(self):
if not super(ListContextCommand, self).execute():
if not super().execute():
return False
for context in sorted(self.todolist.contexts(), key=lambda s: s.lower()):
......
......@@ -22,11 +22,11 @@ class ListProjectCommand(Command):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(ListProjectCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
def execute(self):
if not super(ListProjectCommand, self).execute():
if not super().execute():
return False
for project in sorted(self.todolist.projects(), key=lambda s: s.lower()):
......
......@@ -28,7 +28,7 @@ class PostponeCommand(MultiCommand):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(PostponeCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.move_start_date = False
......@@ -69,7 +69,7 @@ class PostponeCommand(MultiCommand):
# pylint: disable=E1103
todo.set_tag(config().tag_due(), new_due.isoformat())
self.todolist.set_dirty()
self.todolist.dirty = True
self.out(self.printer.print_todo(todo))
else:
self.error("Invalid date pattern given.")
......@@ -77,23 +77,23 @@ class PostponeCommand(MultiCommand):
def usage(self):
return """\
Synopsis: postpone [-s] <NUMBER> [<NUMBER2> ...] <PATTERN>"
postpone [-x] -e <EXPRESSION>
Synopsis: postpone [-s] <NUMBER> [<NUMBER2> ...] <PATTERN>
postpone [-x] -e <EXPRESSION>\
"""
def help(self):
return """\
Postpone the todo item(s) with the given number(s) and the given pattern.
Postpone the todo item(s) with the given NUMBER(s) and the given PATTERN.
Postponing is done by adjusting the due date(s) of the todo(s), and if the -s
flag is given, the start date accordingly.
It is also possible to postpone items as complete with an expression using
the -e flag. Use -x to also process todo items that are normally invisible
(with the 'ls' subcommand).
It is also possible to postpone items as complete with an EXPRESSION using
the -e flag. Use -x to also process todo items that are normally invisible (as
with the 'ls' subcommand).
The pattern is a relative date, written in the format <COUNT><PERIOD> where
count is a number and <PERIOD> is either 'd', 'w', 'm' or 'y', which stands for
days, weeks, months and years respectively. Example: 'postpone 1 1w' postpones
todo number 1 for 1 week.
The PATTERN is a relative date, written in the format <COUNT> <PERIOD> where
COUNT is a number and PERIOD is either 'd', 'w', 'm' or 'y', which stands for
days, weeks, months and years respectively. Example: 'postpone 1 2w' postpones
todo number 1 for 2 weeks.\
"""
......@@ -22,18 +22,18 @@ from topydo.lib.Utils import is_valid_priority
class PriorityCommand(MultiCommand):
def __init__(self, p_args, p_todolist, #pragma: no branch
def __init__(self, p_args, p_todolist, # pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(PriorityCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.last_argument = True
def _execute_multi_specific(self):
def normalize_priority(p_priority):
match = re.search(r'\b([A-Z])\b', p_priority)
match = re.search(r'\b([A-Z])\b', p_priority.upper())
return match.group(1) if match else p_priority
priority = normalize_priority(self.args[-1])
......@@ -56,15 +56,15 @@ class PriorityCommand(MultiCommand):
def usage(self):
return """\
Synopsis: pri <NUMBER1> [<NUMBER2> ...] <PRIORITY>
pri [-x] -e <EXPRESSION>
Synopsis: pri <NUMBER 1> [<NUMBER 2> ...] <PRIORITY>
pri [-x] -e <EXPRESSION>\
"""
def help(self):
return """\
Sets the priority of todo(s) the given number(s) to the given priority.
Sets the priority of todo(s) the given NUMBER(s) to the given PRIORITY.
It is also possible to prioritize items as complete with an expression using
the -e flag. Use -x to also process todo items that are normally invisible
(with the 'ls' subcommand).
It is also possible to prioritize items with an EXPRESSION using the -e flag.
Use -x to also process todo items that are normally invisible (as with the 'ls'
subcommand).\
"""
......@@ -25,11 +25,11 @@ class RevertCommand(Command):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(RevertCommand, self).__init__(p_args, p_todolist, p_out, p_err,
super().__init__(p_args, p_todolist, p_out, p_err,
p_prompt)
def execute(self):
if not super(RevertCommand, self).execute():
if not super().execute():
return False
archive_file = TodoFile.TodoFile(config().archive())
......@@ -54,6 +54,4 @@ class RevertCommand(Command):
return """Synopsis: revert"""
def help(self):
return """\
Reverts the last command.
"""
return """Reverts the last command."""
......@@ -24,11 +24,11 @@ class SortCommand(Command):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(SortCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
def execute(self):
if not super(SortCommand, self).execute():
if not super().execute():
return False
try:
......@@ -42,14 +42,14 @@ class SortCommand(Command):
self.todolist.replace(sorted_todos)
def usage(self):
return """Synopsis: sort [expression]"""
return """Synopsis: sort [<EXPRESSION>]"""
def help(self):
return """\
Sorts the file according to the expression. If no expression is given, the
Sorts the todo file according to the EXPRESSION. If no EXPRESSION is given, the
expression in the configuration is used.
The expression is a comma separated list of attributes to sort on. The list is
The EXPRESSION is a comma separated list of attributes to sort on. The list is
evaluated in order, which means that the first attribute takes higher
precedence, then the second, etc.
......@@ -61,12 +61,12 @@ The following sort attributes are supported:
* importance - Sort by importance
* importance-avg - Sort by average importance (based on parent items)
* text - Sort by text
* <tag> - Sort by values of the given tag
* <TAG> - Sort by values of the given TAG
Each item can optionally be prefixed with asc: and desc: to specify ascending
or descending sort respectively. If not specified, ascending sort is assumed.
Example:
desc:importance,due,desc:priority
desc:importance,due,desc:priority\
"""
......@@ -26,7 +26,7 @@ class TagCommand(Command):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(TagCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.force = False
......@@ -106,10 +106,10 @@ class TagCommand(Command):
self.todo.set_tag(self.tag, self.value, self.force_add, p_old_value)
if old_src != self.todo.source():
self.todolist.set_dirty()
self.todolist.dirty = True
def _set(self):
if len(self.current_values) > 1:
if len(self.current_values) > 1 and not self.force_add:
answer = self._choose()
if answer == "all":
......@@ -124,7 +124,7 @@ class TagCommand(Command):
self._print()
def execute(self):
if not super(TagCommand, self).execute():
if not super().execute():
return False
self._process_args()
......@@ -133,15 +133,15 @@ class TagCommand(Command):
self._set()
def usage(self):
return """Synopsis: tag [-a] [-f] <NUMBER> <tag> [<value>]"""
return """Synopsis: tag [-a] [-f] <NUMBER> <TAG> [<VALUE>]"""
def help(self):
return """\
Sets the given tag to the given todo number with the given value. If the value
is omitted, the tag is removed from the todo item.
Sets the given TAG on the given todo NUMBER with the given VALUE. If the VALUE
is omitted, the TAG is removed from the todo item.
-a : Do not change the current value of the tag if it exists, but add a new
value.
-f : Force setting/removing all values of the tag. Prevents interaction with
the user.
-a : Do not change the current value of the TAG if it exists, but add a new
VALUE for the given TAG.
-f : Force setting/removing all values of the TAG. Prevents interaction with
the user.\
"""
# 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,
}
# Source: https://gist.github.com/jasonm23/2868981
html_color_dict = {
0: "#000000", 1: "#800000", 2: "#008000", 3: "#808000", 4: "#000080",
5: "#800080", 6: "#008080", 7: "#c0c0c0", 8: "#808080", 9: "#ff0000",
10: "#00ff00", 11: "#ffff00", 12: "#0000ff", 13: "#ff00ff", 14: "#00ffff",
15: "#ffffff", 16: "#000000", 17: "#00005f", 18: "#000087", 19: "#0000af",
20: "#0000d7", 21: "#0000ff", 22: "#005f00", 23: "#005f5f", 24: "#005f87",
25: "#005faf", 26: "#005fd7", 27: "#005fff", 28: "#008700", 29: "#00875f",
30: "#008787", 31: "#0087af", 32: "#0087d7", 33: "#0087ff", 34: "#00af00",
35: "#00af5f", 36: "#00af87", 37: "#00afaf", 38: "#00afd7", 39: "#00afff",
40: "#00d700", 41: "#00d75f", 42: "#00d787", 43: "#00d7af", 44: "#00d7d7",
45: "#00d7ff", 46: "#00ff00", 47: "#00ff5f", 48: "#00ff87", 49: "#00ffaf",
50: "#00ffd7", 51: "#00ffff", 52: "#5f0000", 53: "#5f005f", 54: "#5f0087",
55: "#5f00af", 56: "#5f00d7", 57: "#5f00ff", 58: "#5f5f00", 59: "#5f5f5f",
60: "#5f5f87", 61: "#5f5faf", 62: "#5f5fd7", 63: "#5f5fff", 64: "#5f8700",
65: "#5f875f", 66: "#5f8787", 67: "#5f87af", 68: "#5f87d7", 69: "#5f87ff",
70: "#5faf00", 71: "#5faf5f", 72: "#5faf87", 73: "#5fafaf", 74: "#5fafd7",
75: "#5fafff", 76: "#5fd700", 77: "#5fd75f", 78: "#5fd787", 79: "#5fd7af",
80: "#5fd7d7", 81: "#5fd7ff", 82: "#5fff00", 83: "#5fff5f", 84: "#5fff87",
85: "#5fffaf", 86: "#5fffd7", 87: "#5fffff", 88: "#870000", 89: "#87005f",
90: "#870087", 91: "#8700af", 92: "#8700d7", 93: "#8700ff", 94: "#875f00",
95: "#875f5f", 96: "#875f87", 97: "#875faf", 98: "#875fd7", 99: "#875fff",
100: "#878700", 101: "#87875f", 102: "#878787", 103: "#8787af", 104: "#8787d7",
105: "#8787ff", 106: "#87af00", 107: "#87af5f", 108: "#87af87", 109: "#87afaf",
110: "#87afd7", 111: "#87afff", 112: "#87d700", 113: "#87d75f", 114: "#87d787",
115: "#87d7af", 116: "#87d7d7", 117: "#87d7ff", 118: "#87ff00", 119: "#87ff5f",
120: "#87ff87", 121: "#87ffaf", 122: "#87ffd7", 123: "#87ffff", 124: "#af0000",
125: "#af005f", 126: "#af0087", 127: "#af00af", 128: "#af00d7", 129: "#af00ff",
130: "#af5f00", 131: "#af5f5f", 132: "#af5f87", 133: "#af5faf", 134: "#af5fd7",
135: "#af5fff", 136: "#af8700", 137: "#af875f", 138: "#af8787", 139: "#af87af",
140: "#af87d7", 141: "#af87ff", 142: "#afaf00", 143: "#afaf5f", 144: "#afaf87",
145: "#afafaf", 146: "#afafd7", 147: "#afafff", 148: "#afd700", 149: "#afd75f",
150: "#afd787", 151: "#afd7af", 152: "#afd7d7", 153: "#afd7ff", 154: "#afff00",
155: "#afff5f", 156: "#afff87", 157: "#afffaf", 158: "#afffd7", 159: "#afffff",
160: "#d70000", 161: "#d7005f", 162: "#d70087", 163: "#d700af", 164: "#d700d7",
165: "#d700ff", 166: "#d75f00", 167: "#d75f5f", 168: "#d75f87", 169: "#d75faf",
170: "#d75fd7", 171: "#d75fff", 172: "#d78700", 173: "#d7875f", 174: "#d78787",
175: "#d787af", 176: "#d787d7", 177: "#d787ff", 178: "#dfaf00", 179: "#dfaf5f",
180: "#dfaf87", 181: "#dfafaf", 182: "#dfafdf", 183: "#dfafff", 184: "#dfdf00",
185: "#dfdf5f", 186: "#dfdf87", 187: "#dfdfaf", 188: "#dfdfdf", 189: "#dfdfff",
190: "#dfff00", 191: "#dfff5f", 192: "#dfff87", 193: "#dfffaf", 194: "#dfffdf",
195: "#dfffff", 196: "#ff0000", 197: "#ff005f", 198: "#ff0087", 199: "#ff00af",
200: "#ff00df", 201: "#ff00ff", 202: "#ff5f00", 203: "#ff5f5f", 204: "#ff5f87",
205: "#ff5faf", 206: "#ff5fdf", 207: "#ff5fff", 208: "#ff8700", 209: "#ff875f",
210: "#ff8787", 211: "#ff87af", 212: "#ff87df", 213: "#ff87ff", 214: "#ffaf00",
215: "#ffaf5f", 216: "#ffaf87", 217: "#ffafaf", 218: "#ffafdf", 219: "#ffafff",
220: "#ffdf00", 221: "#ffdf5f", 222: "#ffdf87", 223: "#ffdfaf", 224: "#ffdfdf",
225: "#ffdfff", 226: "#ffff00", 227: "#ffff5f", 228: "#ffff87", 229: "#ffffaf",
230: "#ffffdf", 231: "#ffffff", 232: "#080808", 233: "#121212", 234: "#1c1c1c",
235: "#262626", 236: "#303030", 237: "#3a3a3a", 238: "#444444", 239: "#4e4e4e",
240: "#585858", 241: "#626262", 242: "#6c6c6c", 243: "#767676", 244: "#808080",
245: "#8a8a8a", 246: "#949494", 247: "#9e9e9e", 248: "#a8a8a8", 249: "#b2b2b2",
250: "#bcbcbc", 251: "#c6c6c6", 252: "#d0d0d0", 253: "#dadada", 254: "#e4e4e4",
255: "#eeeeee",
}
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
)
def as_html(self):
try:
return Color.html_color_dict[self.color]
except KeyError:
return '#ffffff'
# 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'
def int_to_ansi(p_int, p_decorator='normal', p_safe=True, p_background=''):
"""
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 p_background:
p_background = ';4{}'.format(p_background)
if 8 > int(p_int) >= 0:
return '\033[{};3{}{}m'.format(decoration, str(p_int), p_background)
elif 16 > int(p_int):
p_int = int(p_int) - 8
return '\033[{};1;3{}{}m'.format(decoration, str(p_int), p_background)
if 256 > int(p_int) >= 0:
if p_background:
p_background = ';48;5;{}'.format(str(p_int))
return '\033[{};38;5;{}{}m'.format(decoration, str(p_int), p_background)
else:
return NEUTRAL_COLOR
except ValueError:
return None
def _name_to_int(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(p_color_name, p_decorator):
""" Returns ansi color code from color name. """
number = _name_to_int(p_color_name)
return int_to_ansi(number, p_decorator)
def _get_ansi(p_color, p_decorator):
""" Returns ansi color code from color name or xterm color id. """
if p_color == '':
ansi = ''
else:
ansi = int_to_ansi(p_color, p_decorator, False)
if not ansi:
ansi = _name_to_ansi(p_color, p_decorator)
return ansi
def _get_priority_colors():
pri_ansi_colors = dict()
pri_colors = config().priority_colors()
for pri in pri_colors:
color = _get_ansi(pri_colors[pri], 'normal')
if color == '':
color = NEUTRAL_COLOR
pri_ansi_colors[pri] = color
return pri_ansi_colors
class Colors(object):
def __init__(self):
self.priority_colors = _get_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 get_project_color(self):
return _get_ansi(self.project_color, 'bold')
def get_context_color(self):
return _get_ansi(self.context_color, 'bold')
def get_metadata_color(self):
return _get_ansi(self.metadata_color, 'bold')
def get_link_color(self):
return _get_ansi(self.link_color, 'underline')
def get_priority_color(self, p_priority):
try:
priority_color = self.priority_colors[p_priority]
except KeyError:
priority_color = NEUTRAL_COLOR
return priority_color
......@@ -71,8 +71,8 @@ class Command(object):
""" Retrieves a value from the argument list at the given position. """
try:
return self.args[p_number]
except IndexError:
raise InvalidCommandArgument
except IndexError as ie:
raise InvalidCommandArgument from ie
def getopt(self, p_flags, p_long=None):
p_long = p_long or []
......
......@@ -16,8 +16,17 @@
import configparser
import os
import re
import shlex
from itertools import accumulate
from string import ascii_lowercase
from topydo.lib.Color import Color
def home_config_path(p_filename):
return os.path.join(os.path.expanduser('~'), p_filename)
class ConfigError(Exception):
def __init__(self, p_text):
self.text = p_text
......@@ -43,6 +52,7 @@ class _Config:
'add',
'aliases',
'colorscheme',
'columns',
'dep',
'ls',
'sort',
......@@ -53,7 +63,8 @@ class _Config:
self.defaults = {
'topydo': {
'default_command': 'ls',
'colors': '1',
'colors': 'auto',
'force_colors': '0',
'filename': 'todo.txt',
'archive_filename': 'done.txt',
'identifiers': 'linenumber',
......@@ -106,11 +117,47 @@ class _Config:
'listcontext': 'lscon',
'listcontexts': 'lscon',
},
'columns': {
'column_width': '40',
},
'column_keymap': {
'gg': 'home',
'G': 'end',
'j': 'down',
'k': 'up',
'd': 'cmd del {}',
'e': 'cmd edit {}',
'u': 'cmd revert',
'x': 'cmd do {}',
'm': 'mark',
'.': 'repeat',
'pp': 'postpone',
'ps': 'postpone_s',
'pr': 'pri',
'0': 'first_column',
'$': 'last_column',
'h': 'prev_column',
'l': 'next_column',
'A': 'append_column',
'I': 'insert_column',
'E': 'edit_column',
'D': 'delete_column',
'Y': 'copy_column',
'L': 'swap_left',
'R': 'swap_right',
'<Left>': 'prev_column',
'<Right>': 'next_column',
'<Esc>': 'reset',
},
}
self.config = {}
self.cp = configparser.RawConfigParser()
# allow uppercase config keys
self.cp.optionxform = lambda option: option
for section in self.defaults:
self.cp.add_section(section)
......@@ -120,7 +167,8 @@ class _Config:
files = [
"/etc/topydo.conf",
self._home_config_path(),
home_config_path('.config/topydo/config'),
home_config_path('.topydo'),
".topydo",
"topydo.conf",
"topydo.ini",
......@@ -143,17 +191,44 @@ class _Config:
if not self.cp.has_section(section):
self.cp.add_section(section)
def _home_config_path(self):
return os.path.join(os.path.expanduser('~'), '.topydo')
def default_command(self):
return self.cp.get('topydo', 'default_command')
def colors(self):
def colors(self, p_hint_possible=True):
"""
Returns 0, 16 or 256 representing the number of colors that should be
used in the output.
A hint can be passed whether the device that will output the text
supports colors.
"""
lookup = {
'false': 0,
'no': 0,
'0': 0,
'1': 16,
'true': 16,
'yes': 16,
'16': 16,
'256': 256,
}
try:
return self.cp.getboolean('topydo', 'colors')
forced = self.cp.get('topydo', 'force_colors') == '1'
except ValueError:
return self.defaults['topydo']['colors'] == '1'
forced = self.defaults['topydo']['force_colors'] == '1'
try:
colors = lookup[self.cp.get('topydo', 'colors').lower()] # pylint: disable=no-member
except ValueError:
colors = lookup[self.defaults['topydo']['colors'].lower()] # pylint: disable=no-member
except KeyError:
# for invalid values or 'auto'
colors = 16 if p_hint_possible else 0
# disable colors when no colors are enforced on the commandline and
# color support is determined automatically
return 0 if not forced and not p_hint_possible else colors
def todotxt(self):
return os.path.expanduser(self.cp.get('topydo', 'filename'))
......@@ -236,53 +311,53 @@ class _Config:
return [] if hidden_tags == '' else [tag.strip() for tag in
hidden_tags.split(',')]
def priority_colors(self):
def priority_color(self, p_priority):
"""
Returns a dict with priorities as keys and color numbers as value.
"""
pri_colors_str = self.cp.get('colorscheme', 'priority_colors')
def _str_to_dict(p_string):
pri_colors_dict = dict()
for pri_color in p_string.split(','):
pri, color = pri_color.split(':')
pri_colors_dict[pri] = color
pri_colors_dict[pri] = Color(color)
return pri_colors_dict
try:
pri_colors_str = self.cp.get('colorscheme', 'priority_colors')
if pri_colors_str == '':
pri_colors_dict = {'A': '', 'B': '', 'C': ''}
pri_colors_dict = _str_to_dict('A:-1,B:-1,C:-1')
else:
pri_colors_dict = _str_to_dict(pri_colors_str)
except ValueError:
pri_colors_dict = _str_to_dict(self.defaults['colorscheme']['priority_colors'])
return pri_colors_dict
return pri_colors_dict[p_priority] if p_priority in pri_colors_dict else Color('NEUTRAL')
def project_color(self):
try:
return self.cp.get('colorscheme', 'project_color')
return Color(self.cp.getint('colorscheme', 'project_color'))
except ValueError:
return int(self.defaults['colorscheme']['project_color'])
return Color(self.cp.get('colorscheme', 'project_color'))
def context_color(self):
try:
return self.cp.get('colorscheme', 'context_color')
return Color(self.cp.getint('colorscheme', 'context_color'))
except ValueError:
return int(self.defaults['colorscheme']['context_color'])
return Color(self.cp.get('colorscheme', 'context_color'))
def metadata_color(self):
try:
return self.cp.get('colorscheme', 'metadata_color')
return Color(self.cp.getint('colorscheme', 'metadata_color'))
except ValueError:
return int(self.defaults['colorscheme']['metadata_color'])
return Color(self.cp.get('colorscheme', 'metadata_color'))
def link_color(self):
try:
return self.cp.get('colorscheme', 'link_color')
return Color(self.cp.getint('colorscheme', 'link_color'))
except ValueError:
return int(self.defaults['colorscheme']['link_color'])
return Color(self.cp.get('colorscheme', 'link_color'))
def auto_creation_date(self):
try:
......@@ -299,10 +374,13 @@ class _Config:
alias_dict = dict()
for alias, meaning in aliases:
meaning = shlex.split(meaning)
real_subcommand = meaning[0]
alias_args = meaning[1:]
alias_dict[alias] = (real_subcommand, alias_args)
try:
meaning = shlex.split(meaning)
real_subcommand = meaning[0]
alias_args = meaning[1:]
alias_dict[alias] = (real_subcommand, alias_args)
except ValueError as verr:
alias_dict[alias] = str(verr)
return alias_dict
......@@ -310,6 +388,40 @@ class _Config:
""" Returns the list format used by `ls` """
return self.cp.get('ls', 'list_format')
def column_width(self):
try:
width = self.cp.getint('columns', 'column_width')
if width < 1:
# read default
raise ValueError
return width
except ValueError:
return int(self.defaults['columns']['column_width'])
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):
"""
......@@ -327,7 +439,7 @@ def config(p_path=None, p_overrides=None):
try:
config.instance = _Config(p_path, p_overrides)
except configparser.ParsingError as perr:
raise ConfigError(str(perr))
raise ConfigError(str(perr)) from perr
return config.instance
......
......@@ -31,7 +31,7 @@ class DCommand(MultiCommand):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(DCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.force = False
......
......@@ -32,7 +32,7 @@ class ExpressionCommand(Command):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(ExpressionCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.sort_expression = config().sort_string()
......@@ -45,38 +45,12 @@ class ExpressionCommand(Command):
def _filters(self):
filters = []
def arg_filters():
result = []
if self.last_argument:
args = self.args[:-1]
else:
args = self.args
for arg in args:
# when a word starts with -, it should be negated
is_negated = len(arg) > 1 and arg[0] == '-'
arg = arg[1:] if is_negated else arg
if re.match(Filter.ORDINAL_TAG_MATCH, arg):
argfilter = Filter.OrdinalTagFilter(arg)
elif re.match(Filter.PRIORITY_MATCH, arg):
argfilter = Filter.PriorityFilter(arg)
else:
argfilter = Filter.GrepFilter(arg)
if is_negated:
argfilter = Filter.NegationFilter(argfilter)
result.append(argfilter)
return result
if not self.show_all:
filters.append(Filter.DependencyFilter(self.todolist))
filters.append(Filter.RelevanceFilter())
filters += arg_filters()
args = self.args[:-1] if self.last_argument else self.args
filters += Filter.get_filter_list(args)
if not self.show_all:
filters.append(Filter.LimitFilter(self.limit))
......
......@@ -62,7 +62,7 @@ class GrepFilter(Filter):
""" Matches when the todo text contains a text. """
def __init__(self, p_expression, p_case_sensitive=None):
super(GrepFilter, self).__init__()
super().__init__()
# convert to string in case we receive integers
self.expression = p_expression
......@@ -96,13 +96,15 @@ class RelevanceFilter(Filter):
"""
def match(self, p_todo):
is_due = p_todo.is_active()
is_due |= p_todo.due_date() == None
is_due |= p_todo.priority() == 'A'
is_due |= p_todo.priority() == 'B' and p_todo.days_till_due() <= 30
is_due |= p_todo.priority() == 'C' and p_todo.days_till_due() <= 14
active = p_todo.is_active()
return p_todo.is_active() and is_due
is_due = active
is_due = is_due or p_todo.due_date() == None
is_due = is_due or p_todo.priority() == 'A'
is_due = is_due or (p_todo.priority() == 'B' and p_todo.days_till_due() <= 30)
is_due = is_due or (p_todo.priority() == 'C' and p_todo.days_till_due() <= 14)
return active and is_due
class DependencyFilter(Filter):
......@@ -115,7 +117,7 @@ class DependencyFilter(Filter):
Pass on a TodoList instance such that the dependencies can be
looked up.
"""
super(DependencyFilter, self).__init__()
super().__init__()
self.todolist = p_todolist
def match(self, p_todo):
......@@ -138,7 +140,7 @@ class InstanceFilter(Filter):
This is handy for constructing a view given a plain list of Todo items.
"""
super(InstanceFilter, self).__init__()
super().__init__()
self.todos = p_todos
def match(self, p_todo):
......@@ -154,20 +156,20 @@ class InstanceFilter(Filter):
class LimitFilter(Filter):
def __init__(self, p_limit):
super(LimitFilter, self).__init__()
super().__init__()
self.limit = p_limit
def filter(self, p_todos):
return p_todos[:self.limit] if self.limit >= 0 else p_todos
OPERATOR_MATCH = r"(?P<operator><=?|=|>=?|!)?"
_OPERATOR_MATCH = r"(?P<operator><=?|=|>=?|!)?"
class OrdinalFilter(Filter):
""" Base class for ordinal filters. """
def __init__(self, p_expression, p_pattern):
super(OrdinalFilter, self).__init__()
super().__init__()
self.expression = p_expression
......@@ -200,12 +202,13 @@ class OrdinalFilter(Filter):
return False
ORDINAL_TAG_MATCH = r"(?P<key>[^:]*):" + OPERATOR_MATCH + r"(?P<value>\S+)"
_VALUE_MATCH = r"(?P<value>\S+)"
_ORDINAL_TAG_MATCH = r"(?P<key>[^:]*):" + _OPERATOR_MATCH + _VALUE_MATCH
class OrdinalTagFilter(OrdinalFilter):
def __init__(self, p_expression):
super(OrdinalTagFilter, self).__init__(p_expression, ORDINAL_TAG_MATCH)
super().__init__(p_expression, _ORDINAL_TAG_MATCH)
def match(self, p_todo):
"""
......@@ -243,12 +246,55 @@ class OrdinalTagFilter(OrdinalFilter):
return self.compare_operands(operand1, operand2)
PRIORITY_MATCH = r"\(" + OPERATOR_MATCH + r"(?P<value>[A-Z]{1})\)"
class _DateAttributeFilter(OrdinalFilter):
def __init__(self, p_expression, p_match, p_getter):
super().__init__(p_expression, p_match)
self.getter = p_getter
def match(self, p_todo):
operand1 = self.getter(p_todo)
operand2 = relative_date_to_date(self.value)
if not operand2:
operand2 = date_string_to_date(self.value)
if operand1 and operand2:
return self.compare_operands(operand1, operand2)
else:
return False
_CREATED_MATCH = r'creat(ion|ed?):' + _OPERATOR_MATCH + _VALUE_MATCH
class CreationFilter(_DateAttributeFilter):
def __init__(self, p_expression):
super().__init__(
p_expression,
_CREATED_MATCH,
lambda t: t.creation_date() # pragma: no branch
)
_COMPLETED_MATCH = r'complet(ed?|ion):' + _OPERATOR_MATCH + _VALUE_MATCH
class CompletionFilter(_DateAttributeFilter):
def __init__(self, p_expression):
super().__init__(
p_expression,
_COMPLETED_MATCH,
lambda t: t.completion_date() # pragma: no branch
)
_PRIORITY_MATCH = r"\(" + _OPERATOR_MATCH + r"(?P<value>[A-Z]{1})\)"
class PriorityFilter(OrdinalFilter):
def __init__(self, p_expression):
super(PriorityFilter, self).__init__(p_expression, PRIORITY_MATCH)
super().__init__(p_expression, _PRIORITY_MATCH)
def match(self, p_todo):
"""
......@@ -265,3 +311,39 @@ class PriorityFilter(OrdinalFilter):
operand2 = p_todo.priority() or 'ZZ'
return self.compare_operands(operand1, operand2)
MATCHES = [
(_CREATED_MATCH, CreationFilter),
(_COMPLETED_MATCH, CompletionFilter),
(_ORDINAL_TAG_MATCH, OrdinalTagFilter),
(_PRIORITY_MATCH, PriorityFilter),
]
def get_filter_list(p_expression):
"""
Returns a list of GrepFilters, OrdinalTagFilters or NegationFilters based
on the given filter expression.
The filter expression is a list of strings.
"""
result = []
for arg in p_expression:
# when a word starts with -, it should be negated
is_negated = len(arg) > 1 and arg[0] == '-'
arg = arg[1:] if is_negated else arg
argfilter = None
for match, _filter in MATCHES:
if re.match(match, arg):
argfilter = _filter(arg)
break
if not argfilter:
argfilter = GrepFilter(arg)
if is_negated:
argfilter = NegationFilter(argfilter)
result.append(argfilter)
return result
......@@ -83,12 +83,14 @@ def average_importance(p_todo, p_ignore_weekend=config().ignore_weekends()):
average = 0
parents = []
if 'parents' in p_todo.attributes:
try:
sum_importance = own_importance
parents = p_todo.attributes['parents']
parents = p_todo.parents()
for parent in parents:
sum_importance += importance(parent, p_ignore_weekend)
average = float(sum_importance) / float(1 + len(parents))
except AttributeError:
pass
return max(own_importance, average)
......@@ -20,7 +20,7 @@ import arrow
import re
from topydo.lib.Config import config
from topydo.lib.Colorblock import color_block
from topydo.lib.ProgressColor import progress_color
from topydo.lib.Utils import get_terminal_size, escape_ansi, humanize_date
MAIN_PATTERN = (r'^({{(?P<before>.+?)}})?'
......@@ -123,6 +123,12 @@ def _right_align(p_str):
return p_str
def color_block(p_todo):
return '{} {}'.format(
progress_color(p_todo).as_ansi(p_background=True),
config().priority_color(p_todo.priority()).as_ansi(),
)
class ListFormatParser(object):
""" Parser of format string. """
def __init__(self, p_todolist, p_format=None):
......@@ -147,6 +153,7 @@ class ListFormatParser(object):
# relative dates in form: creation, due, start
'H': lambda t: humanize_dates(t.due_date(), t.start_date(), t.creation_date()),
# todo ID
'i': lambda t: str(self.todolist.number(t)),
......@@ -167,6 +174,12 @@ class ListFormatParser(object):
# priority
'p': lambda t: t.priority() if t.priority() else '',
# priority (or placeholder space)
'P': lambda t: t.priority() if t.priority() else ' ',
# raw text
'r': lambda t: t.source(),
# text
's': lambda t: t.text(),
......@@ -186,8 +199,6 @@ class ListFormatParser(object):
'X': lambda t: 'x ' + humanize_date(t.completion_date()) if t.is_completed() else '',
'z': lambda t: color_block(t) if config().colors() else ' ',
'Z': lambda t: color_block(t, p_safe=False) if config().colors() else ' ',
}
self.format_list = self._preprocess_format()
......
......@@ -27,7 +27,7 @@ class MultiCommand(ExpressionCommand):
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(MultiCommand, self).__init__(
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.todos = []
......@@ -114,7 +114,7 @@ class MultiCommand(ExpressionCommand):
raise NotImplementedError
def execute(self):
if not super(MultiCommand, self).execute():
if not super().execute():
return False
self._process_flags()
......
......@@ -29,4 +29,3 @@ class PrettyPrinterFilter(object):
Applies a filter to p_todo_str and returns a modified version of it.
"""
raise NotImplementedError
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# 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
......@@ -16,106 +16,91 @@
import re
from topydo.lib.Colors import int_to_ansi, Colors
from topydo.lib.Color import Color
from topydo.lib.Config import config
from topydo.lib.Recurrence import relative_date_to_date
COLOR16_RANGE = [
(10, '#00ff00', '#000000'), # light green
(2, '#008700', '#ffffff'), # green
(3, '#ffff00', '#000000'), # yellow
(1, '#ff0000', '#ffffff'), # red
]
# https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
# a gradient from green to yellow to red
COLOR256_RANGE = [
(22, '#005f00', '#ffffff'),
(28, '#008700', '#ffffff'),
(34, '#00af00', '#ffffff'),
(40, '#00d700', '#000000'),
(46, '#00ff00', '#000000'),
(82, '#5fff00', '#000000'),
(118, '#87ff00', '#000000'),
(154, '#afff00', '#000000'),
(190, '#dfff00', '#000000'),
(226, '#ffff00', '#000000'),
(220, '#ffd700', '#000000'),
(214, '#ffaf00', '#000000'),
(208, '#ff8700', '#000000'),
(202, '#ff5f00', '#ffffff'),
(196, '#ff0000', '#ffffff'),
]
def _progress_to_color(p_todo, p_safe=True):
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.
"""
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
# when a todo item has not enough information to determine the length, assume
# this length
ASSUMED_TODO_LENGTH = 14 # days
def diff_days(p_start, p_end):
if p_start < p_end:
diff = p_end - p_start
return diff.days
def progress_color(p_todo):
color16_range = [
2, # green
10, # light green
3, # yellow
1, # red
]
return 0
# 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]
if p_todo.has_tag('rec') and p_todo.due_date():
# add negation, offset is based on due date
recurrence_pattern = p_todo.tag_value('rec')
neg_recurrence_pattern = re.sub('^\+?', '-', recurrence_pattern)
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
start = relative_date_to_date(
neg_recurrence_pattern, p_todo.due_date())
due = p_todo.due_date()
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()
result = diff_days(start, due)
else:
result = p_todo.length()
# a todo item is at least one day long
return max(1, result)
return 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() or 14
length = get_length()
return max((length - days_till_due), 0) / length
else:
return 0
color_range = COLOR16_RANGE if p_safe else COLOR256_RANGE
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_range[-1]
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_range[pos]
def progress_color_code(p_todo, p_safe=True):
return _progress_to_color(p_todo, p_safe)[0]
def progress_html_color(p_todo):
""" Returns a tuple (foreground, background) color """
_, background, foreground = _progress_to_color(p_todo, p_safe=False)
return (foreground, background)
def color_block(p_todo, p_safe=True):
color_code = progress_color_code(p_todo, p_safe)
ansi_code = int_to_ansi(color_code, p_safe=p_safe, p_background=color_code)
priority_color = Colors().get_priority_color(p_todo.priority())
return '{} {}'.format(ansi_code, priority_color)
return Color(color_range[pos])
......@@ -37,9 +37,26 @@ def _add_months(p_sourcedate, p_months):
return date(year, month, day)
def _add_business_days(p_sourcedate, p_bdays):
""" Adds a number of business days to the source date. """
result = p_sourcedate
delta = 1 if p_bdays > 0 else -1
while abs(p_bdays) > 0:
result += timedelta(delta)
weekday = result.weekday()
if weekday >= 5:
continue
p_bdays = p_bdays - 1 if delta > 0 else p_bdays + 1
return result
def _convert_pattern(p_length, p_periodunit, p_offset=None):
"""
Converts a pattern in the form [0-9][dwmy] and returns a date from the
Converts a pattern in the form [0-9][dwmyb] and returns a date from the
offset with the period of time added to it.
"""
result = None
......@@ -55,6 +72,8 @@ def _convert_pattern(p_length, p_periodunit, p_offset=None):
result = _add_months(p_offset, p_length)
elif p_periodunit == 'y':
result = _add_months(p_offset, p_length * 12)
elif p_periodunit == 'b':
result = _add_business_days(p_offset, p_length)
return result
......@@ -98,7 +117,8 @@ def relative_date_to_date(p_date, p_offset=None):
p_date = p_date.lower()
p_offset = p_offset or date.today()
relative = re.match('(?P<length>-?[0-9]+)(?P<period>[dwmy])$', p_date, re.I)
relative = re.match('(?P<length>-?[0-9]+)(?P<period>[dwmyb])$',
p_date, re.I)
monday = 'mo(n(day)?)?$'
tuesday = 'tu(e(sday)?)?$'
......
......@@ -52,6 +52,8 @@ def get_field_function(p_field):
result = importance
elif p_field == 'importance-avg' or p_field == 'importance-average':
result = average_importance
elif p_field == 'length':
result = lambda a: a.length()
elif p_field == 'project' or p_field == 'projects':
result = lambda a: sorted([c.lower() for c in a.projects()])
elif p_field == 'text':
......
......@@ -48,17 +48,14 @@ class TodoBase(object):
Returns a tag value associated with p_key. Returns p_default if p_key
does not exist (which defaults to None).
"""
values = self.tag_values(p_key)
return values[0] if len(values) else p_default
return self.tag_values(p_key)[0] if p_key in self.fields['tags'] else p_default
def tag_values(self, p_key):
"""
Returns a list of all tag values associated with p_key. Returns
empty list if p_key does not exist.
"""
tags = self.fields['tags']
matches = [tag[1] for tag in tags if tag[0] == p_key]
return matches if len(matches) else []
return self.fields['tags'][p_key] if p_key in self.fields['tags'] else []
def has_tag(self, p_key, p_value=""):
"""
......@@ -66,14 +63,28 @@ class TodoBase(object):
value is passed, it will only return true when there exists a tag with
the given key-value combination.
"""
result = [t for t in self.tag_values(p_key)
if p_value == "" or t == p_value]
return len(result) > 0
tags = self.fields['tags']
return p_key in tags and (p_value == "" or p_value in tags[p_key])
def add_tag(self, p_key, p_value):
""" Adds a tag to the todo. """
self.set_tag(p_key, p_value, True)
def _remove_tag_helper(self, p_key, p_value):
"""
Removes a tag from the internal todo dictionary. Only those instances
with the given value are removed. If the value is empty, all tags with
the given key are removed.
"""
tags = self.fields['tags']
try:
tags[p_key] = [t for t in tags[p_key] if p_value != "" and t != p_value]
if len(tags[p_key]) == 0:
del tags[p_key]
except KeyError:
pass
def set_tag(self, p_key, p_value="", p_force_add=False, p_old_value=""):
"""
Sets a occurrence of the tag identified by p_key. Sets an arbitrary
......@@ -92,12 +103,11 @@ class TodoBase(object):
self.remove_tag(p_key, p_old_value)
return
tags = self.fields['tags']
value = p_old_value if p_old_value else self.tag_value(p_key)
if not p_force_add and value:
# remove old value from the tags
self.fields['tags'] = [t for t in self.fields['tags']
if not (t[0] == p_key and t[1] == value)]
self._remove_tag_helper(p_key, value)
self.src = re.sub(
r'\b' + p_key + ':' + value + r'\b',
......@@ -107,7 +117,10 @@ class TodoBase(object):
else:
self.src += ' ' + p_key + ':' + p_value
self.fields['tags'].append((p_key, p_value))
try:
tags[p_key].append(p_value)
except KeyError:
tags[p_key] = [p_value]
def remove_tag(self, p_key, p_value=""):
"""
......@@ -116,12 +129,7 @@ class TodoBase(object):
removed.
Else, only those tags with the value will be removed.
"""
# Build a new list that excludes the specified tag, match by value when
# p_value is given.
self.fields['tags'] = [t for t in self.fields['tags']
if not (t[0] == p_key and (p_value == "" or
t[1] == p_value))]
self._remove_tag_helper(p_key, p_value)
# when value == "", match any value having key p_key
value = p_value if p_value != "" else r'\S+'
......@@ -132,7 +140,8 @@ class TodoBase(object):
Returns a list of tuples with key-value pairs representing tags in
this todo item.
"""
return self.fields['tags']
tags = self.fields['tags']
return [(t, v) for t in tags for v in tags[t]]
def set_priority(self, p_priority):
"""
......
......@@ -18,11 +18,35 @@
A list of todo items.
"""
import types
from topydo.lib.Config import config
from topydo.lib.Graph import DirectedGraph
from topydo.lib.TodoListBase import TodoListBase
def _needs_dependencies(p_function):
"""
A decorator that triggers the population of the dependency tree in a
TodoList (and other administration). The decorator should be applied to
methods of TodoList that require dependency information.
"""
def build_dependency_information(p_todolist):
for todo in p_todolist._todos:
p_todolist._register_todo(todo)
def inner(self, *args, **kwargs):
if not self._initialized:
self._initialized = True
from topydo.lib.Graph import DirectedGraph
self._depgraph = DirectedGraph()
build_dependency_information(self)
return p_function(self, *args, **kwargs)
return inner
class TodoList(TodoListBase):
"""
Provides operations for a todo list, such as adding items, removing them,
......@@ -37,21 +61,27 @@ class TodoList(TodoListBase):
Should be given a list of strings, each element a single todo string.
The string will be parsed.
"""
self._initialized = False # whether dependency information was
# initialized
# initialize these first because the constructor calls add_list
self._tododict = {} # hash(todo) to todo lookup
self._depgraph = DirectedGraph()
self._parentdict = {} # dependency id => parent todo
self._depgraph = None
super(TodoList, self).__init__(p_todostrings)
super().__init__(p_todostrings)
@_needs_dependencies
def todo_by_dep_id(self, p_dep_id):
"""
Returns the todo that has the id tag set to the value p_dep_id.
There is only one such task, the behavior is undefined when a tag has
more than one id tag.
There is only one such task, the behavior is undefined when a todo item
has more than one id tag.
"""
hits = [t for t in self._todos if t.tag_value('id') == p_dep_id]
return hits[0] if len(hits) else None
try:
return self._parentdict[p_dep_id]
except KeyError:
return None
def _maintain_dep_graph(self, p_todo):
"""
......@@ -61,6 +91,7 @@ class TodoList(TodoListBase):
dep_id = p_todo.tag_value('id')
# maintain dependency graph
if dep_id:
self._parentdict[dep_id] = p_todo
self._depgraph.add_node(hash(p_todo))
# connect all tasks we have in memory so far that refer to this
......@@ -68,33 +99,43 @@ class TodoList(TodoListBase):
for dep in \
[dep for dep in self._todos if dep.has_tag('p', dep_id)]:
self._depgraph.add_edge(hash(p_todo), hash(dep), dep_id)
self._add_edge(p_todo, dep, dep_id)
for dep_id in p_todo.tag_values('p'):
try:
parent = self._parentdict[dep_id]
self._add_edge(parent, p_todo, dep_id)
except KeyError:
pass
for child in p_todo.tag_values('p'):
parent = self.todo_by_dep_id(child)
if parent:
self._depgraph.add_edge(hash(parent), hash(p_todo), child)
def _register_todo(self, p_todo):
self._maintain_dep_graph(p_todo)
self._tododict[hash(p_todo)] = p_todo
def add_todos(self, p_todos):
for todo in p_todos:
self._todos.append(todo)
self._tododict[hash(todo)] = todo
self._maintain_dep_graph(todo)
super().add_todos(p_todos)
self._update_todo_ids()
self._update_parent_cache()
self.dirty = True
for todo in self._todos:
todo.parents = types.MethodType(lambda i: self.parents(i), todo)
# only do administration when the dependency info is initialized,
# otherwise we postpone it until it's really needed (through the
# _needs_dependencies decorator)
if self._initialized:
self._register_todo(todo)
def delete(self, p_todo):
def delete(self, p_todo, p_leave_tags=False):
""" Deletes a todo item from the list. """
try:
number = self._todos.index(p_todo)
for child in self.children(p_todo):
self.remove_dependency(p_todo, child)
if p_todo.has_tag('id'):
for child in self.children(p_todo):
self.remove_dependency(p_todo, child, p_leave_tags)
for parent in self.parents(p_todo):
self.remove_dependency(parent, p_todo)
if p_todo.has_tag('p'):
for parent in self.parents(p_todo):
self.remove_dependency(parent, p_todo, p_leave_tags)
del self._todos[number]
self._update_todo_ids()
......@@ -104,6 +145,11 @@ class TodoList(TodoListBase):
# todo item couldn't be found, ignore
pass
def _add_edge(self, p_from_todo, p_to_todo, p_dep_id):
self._parentdict[p_dep_id] = p_from_todo
self._depgraph.add_edge(hash(p_from_todo), hash(p_to_todo), p_dep_id)
@_needs_dependencies
def add_dependency(self, p_from_todo, p_to_todo):
""" Adds a dependency from task 1 to task 2. """
def find_next_id():
......@@ -117,7 +163,8 @@ class TodoList(TodoListBase):
Returns True if there exists a todo with the given parent ID.
"""
for todo in self._todos:
if todo.has_tag('id', str(p_id)):
number = str(p_id)
if todo.has_tag('id', number) or todo.has_tag('p', number):
return True
return False
......@@ -157,26 +204,29 @@ class TodoList(TodoListBase):
p_from_todo.set_tag('id', dep_id)
p_to_todo.add_tag('p', dep_id)
self._depgraph.add_edge(hash(p_from_todo), hash(p_to_todo), dep_id)
self._update_parent_cache()
self._add_edge(p_from_todo, p_to_todo, dep_id)
append_projects_to_subtodo()
append_contexts_to_subtodo()
self.dirty = True
def remove_dependency(self, p_from_todo, p_to_todo):
@_needs_dependencies
def remove_dependency(self, p_from_todo, p_to_todo, p_leave_tags=False):
""" Removes a dependency between two todos. """
dep_id = p_from_todo.tag_value('id')
if dep_id:
p_to_todo.remove_tag('p', dep_id)
self._depgraph.remove_edge(hash(p_from_todo), hash(p_to_todo))
self._update_parent_cache()
self.dirty = True
# clean dangling dependency tags
if dep_id and not p_leave_tags:
p_to_todo.remove_tag('p', dep_id)
if not self.children(p_from_todo, True):
p_from_todo.remove_tag('id')
del self._parentdict[dep_id]
self.dirty = True
@_needs_dependencies
def parents(self, p_todo, p_only_direct=False):
"""
Returns a list of parent todos that (in)directly depend on the
......@@ -186,6 +236,7 @@ class TodoList(TodoListBase):
hash(p_todo), not p_only_direct)
return [self._tododict[parent] for parent in parents]
@_needs_dependencies
def children(self, p_todo, p_only_direct=False):
"""
Returns a list of child todos that the given todo (in)directly depends
......@@ -195,6 +246,7 @@ class TodoList(TodoListBase):
self._depgraph.outgoing_neighbors(hash(p_todo), not p_only_direct)
return [self._tododict[child] for child in children]
@_needs_dependencies
def clean_dependencies(self):
"""
Cleans the dependency graph.
......@@ -219,6 +271,7 @@ class TodoList(TodoListBase):
value = todo.tag_value('id')
if not self._depgraph.has_edge_id(value):
remove_tag(todo, 'id', value)
del self._parentdict[value]
def clean_orphan_relations():
"""
......@@ -237,12 +290,3 @@ class TodoList(TodoListBase):
clean_parent_relations()
clean_orphan_relations()
def _update_parent_cache(self):
"""
Sets the attribute to the list of parents, such that others may access
it outside this todo list.
This is used for calculating the average importance, that requires
access to a todo's parents.
"""
for todo in self._todos:
todo.attributes['parents'] = self.parents(todo)
......@@ -52,7 +52,13 @@ class TodoListBase(object):
self._id_todo_map = {}
self.add_list(p_todostrings)
self.dirty = False
self._dirty = False
def __iter__(self):
"""
Allows use of `for my_todo in todolist` constructs.
"""
return iter(self._todos)
def todo(self, p_identifier):
"""
......@@ -95,11 +101,11 @@ class TodoListBase(object):
# the expression is a string and no leading zeroes,
# treat it as an integer
raise TypeError
except TypeError:
except TypeError as te:
try:
result = self._todos[int(p_identifier) - 1]
except IndexError:
raise InvalidTodoException
except (ValueError, IndexError):
raise InvalidTodoException from te
return result
......@@ -221,11 +227,13 @@ class TodoListBase(object):
"""
return View(p_sorter, p_filters, self)
def is_dirty(self):
return self.dirty
@property
def dirty(self):
return self._dirty
def set_dirty(self):
self.dirty = True
@dirty.setter
def dirty(self, p_flag):
self._dirty = p_flag
def todos(self):
return self._todos
......@@ -249,8 +257,8 @@ class TodoListBase(object):
return self._todo_id_map[p_todo]
else:
return self._todos.index(p_todo) + 1
except (ValueError, KeyError):
raise InvalidTodoException
except (ValueError, KeyError) as ex:
raise InvalidTodoException from ex
def _update_todo_ids(self):
# the idea is to have a hash that is independent of the position of the
......@@ -271,4 +279,4 @@ class TodoListBase(object):
this list.
"""
printer = PrettyPrinter()
return printer.print_list(self._todos)
return "\n".join([str(s) for s in printer.print_list(self._todos)])
......@@ -33,7 +33,7 @@ _NORMAL_HEAD_MATCH = re.compile(
r'(\((?P<priority>[A-Z])\) )?' + '((?P<creationDate>' + _DATE_MATCH +
') )?(?P<rest>.*)')
_TAG_MATCH = re.compile('(?P<key>[^:]+):(?P<value>.+)')
_TAG_MATCH = re.compile('(?P<tag>[^:]+):(?P<value>.+)')
_PROJECT_MATCH = re.compile(r'\+(\S*\w)')
_CONTEXT_MATCH = re.compile(r'@(\S*\w)')
......@@ -57,7 +57,7 @@ def parse_line(p_string):
'text': "",
'projects': [],
'contexts': [],
'tags': []
'tags': {},
}
completed_head = _COMPLETED_HEAD_MATCH.match(p_string)
......@@ -94,10 +94,14 @@ def parse_line(p_string):
tag = _TAG_MATCH.match(word)
if tag:
result['tags'].append((tag.group('key'), tag.group('value')))
continue
result['text'] += word + ' '
tag_name = tag.group('tag')
tag_value = tag.group('value')
try:
result['tags'][tag_name].append(tag_value)
except KeyError:
result['tags'][tag_name] = [tag_value]
else:
result['text'] += word + ' '
# strip trailing space from resulting text
result['text'] = result['text'][:-1]
......
# 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
......@@ -55,28 +55,65 @@ def escape_ansi(p_string):
escape_ansi.pattern = re.compile(r'\x1b[^m]*m')
def get_terminal_size():
def get_terminal_size(p_getter=None):
"""
Try to determine terminal size at run time. If that is not possible,
returns the default size of 80x24.
By default, the size is determined with provided get_terminal_size by
shutil. Sometimes an UI may want to specify the desired width, then it can
provide a getter that returns a named tuple (columns, lines) with the size.
"""
from shutil import get_terminal_size # pylint: disable=no-name-in-module
try:
sz = get_terminal_size()
except ValueError:
"""
This can result from the 'underlying buffer being detached', which
occurs during running the unittest on Windows (but not on Linux?)
"""
terminal_size = namedtuple('Terminal_Size', 'columns lines')
sz = terminal_size((80, 24))
return get_terminal_size.getter()
except AttributeError:
if p_getter:
get_terminal_size.getter = p_getter
else:
def inner():
try:
# shutil.get_terminal_size was added to the standard
# library in Python 3.3
try:
from shutil import get_terminal_size as _get_terminal_size # pylint: disable=no-name-in-module
except ImportError:
from backports.shutil_get_terminal_size import get_terminal_size as _get_terminal_size # pylint: disable=import-error
sz = _get_terminal_size()
except ValueError:
"""
This can result from the 'underlying buffer being detached', which
occurs during running the unittest on Windows (but not on Linux?)
"""
terminal_size = namedtuple('Terminal_Size', 'columns lines')
sz = terminal_size(80, 24)
return sz
get_terminal_size.getter = inner
return get_terminal_size.getter()
def translate_key_to_config(p_key):
"""
Translates urwid key event to form understandable by topydo config parser.
"""
if len(p_key) > 1:
key = p_key.capitalize()
if key.startswith('Ctrl') or key.startswith('Meta'):
key = key[0] + '-' + key[5:]
key = '<' + key + '>'
else:
key = p_key
return sz
return key
def humanize_date(p_datetime):
""" Returns a relative date string from a datetime object. """
now = arrow.now()
date = now.replace(day=p_datetime.day, month=p_datetime.month, year=p_datetime.year)
return date.humanize()
return date.humanize().replace('just now', 'today')
""" Version of Topydo. """
VERSION = '0.8'
VERSION = '0.9'
LICENSE = """Copyright (C) 2014-2015 Bram Schoenmakers
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
......
......@@ -25,14 +25,14 @@ class View(object):
"""
def __init__(self, p_sorter, p_filters, p_todolist):
self._todolist = p_todolist
self.todolist = p_todolist
self._sorter = p_sorter
self._filters = p_filters
@property
def todos(self):
""" Returns a sorted and filtered list of todos in this view. """
result = self._sorter.sort(self._todolist.todos())
result = self._sorter.sort(self.todolist.todos())
for _filter in self._filters:
result = _filter.filter(result)
......
# 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 topydo.lib.Command import Command
from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException
class WriteCommand(Command):
def postprocess_input_todo(self, p_todo):
"""
Post-processes a parsed todo when adding it to the list.
* It converts relative dates to absolute ones.
* Automatically inserts a creation date if not present.
* Handles more user-friendly dependencies with before:, partof: and
after: tags
"""
def convert_date(p_tag):
value = p_todo.tag_value(p_tag)
if value:
dateobj = relative_date_to_date(value)
if dateobj:
p_todo.set_tag(p_tag, dateobj.isoformat())
def add_dependencies(p_tag):
for value in p_todo.tag_values(p_tag):
try:
dep = self.todolist.todo(value)
if p_tag == 'after':
self.todolist.add_dependency(p_todo, dep)
elif p_tag == 'before' or p_tag == 'partof':
self.todolist.add_dependency(dep, p_todo)
elif p_tag.startswith('parent'):
for parent in self.todolist.parents(dep):
self.todolist.add_dependency(parent, p_todo)
elif p_tag.startswith('child'):
for child in self.todolist.children(dep):
self.todolist.add_dependency(p_todo, child)
except InvalidTodoException:
pass
p_todo.remove_tag(p_tag, value)
convert_date(config().tag_start())
convert_date(config().tag_due())
keywords = [
'after',
'before',
'child-of',
'childof',
'children-of',
'childrenof',
'parent-of',
'parentof',
'parents-of',
'parentsof',
'partof',
]
for keyword in keywords:
add_dependencies(keyword)
......@@ -18,9 +18,10 @@
import re
from topydo.lib.Colors import NEUTRAL_COLOR, Colors
from topydo.lib.Color import AbstractColor
from topydo.lib.Config import config
from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter
from topydo.lib.TopydoString import TopydoString
class PrettyPrinterColorFilter(PrettyPrinterFilter):
......@@ -33,35 +34,26 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
def filter(self, p_todo_str, p_todo):
""" Applies the colors. """
if config().colors():
colorscheme = Colors()
priority_color = colorscheme.get_priority_color(p_todo.priority())
project_color = colorscheme.get_project_color()
context_color = colorscheme.get_context_color()
metadata_color = colorscheme.get_metadata_color()
link_color = colorscheme.get_link_color()
p_todo_str = TopydoString(p_todo_str, p_todo)
# color projects / contexts
p_todo_str = re.sub(
r'\B(\+|@)(\S*\w)',
lambda m: (
context_color if m.group(0)[0] == "@"
else project_color) + m.group(0) + priority_color,
p_todo_str)
priority_color = config().priority_color(p_todo.priority())
# tags
p_todo_str = re.sub(r'\b\S+:[^/\s]\S*\b',
metadata_color + r'\g<0>' + priority_color,
p_todo_str)
colors = [
(r'\B@(\S*\w)', AbstractColor.CONTEXT),
(r'\B\+(\S*\w)', AbstractColor.PROJECT),
(r'\b\S+:[^/\s]\S*\b', AbstractColor.META),
(r'(^|\s)(\w+:){1}(//\S+)', AbstractColor.LINK),
]
# add link_color to any valid URL specified outside of the tag.
p_todo_str = re.sub(r'(^|\s)(\w+:){1}(//\S+)',
r'\1' + link_color + r'\2\3' + priority_color,
p_todo_str)
# color by priority
p_todo_str.set_color(0, priority_color)
p_todo_str += NEUTRAL_COLOR
for pattern, color in colors:
for match in re.finditer(pattern, p_todo_str.data):
p_todo_str.set_color(match.start(), color)
p_todo_str.set_color(match.end(), priority_color)
# color by priority
p_todo_str = priority_color + p_todo_str
p_todo_str.append('', AbstractColor.NEUTRAL)
return p_todo_str
......@@ -25,7 +25,7 @@ from topydo.lib.ListFormat import ListFormatParser
class PrettyPrinterFormatFilter(PrettyPrinterFilter):
def __init__(self, p_todolist, p_format=None):
super(PrettyPrinterFormatFilter, self).__init__()
super().__init__()
self.parser = ListFormatParser(p_todolist, p_format)
def filter(self, p_todo_str, p_todo):
......
......@@ -23,7 +23,7 @@ class PrettyPrinterNumbers(PrettyPrinterFilter):
""" Prepends the todo's number, retrieved from the todolist. """
def __init__(self, p_todolist):
super(PrettyPrinterNumbers, self).__init__()
super().__init__()
self.todolist = p_todolist
def filter(self, p_todo_str, p_todo):
......
......@@ -21,8 +21,8 @@ notation. Useful for displaying dependencies.
from textwrap import wrap
from topydo.lib.Colorblock import progress_html_color
from topydo.lib.printers.PrettyPrinter import Printer
from topydo.lib.ProgressColor import progress_color
from topydo.lib.Utils import humanize_date
......@@ -89,13 +89,13 @@ class DotPrinter(Printer):
# print todos
for todo in p_todos:
foreground, background = progress_html_color(todo)
color = progress_color(todo).as_html()
result += ' {} [label={} style=filled fillcolor="{}" fontcolor="{}"]\n'.format(
node_name(todo),
node_label(todo),
background,
foreground,
color,
'#000000',
)
# print edges
......
......@@ -66,7 +66,7 @@ class IcalPrinter(Printer):
"""
def __init__(self, p_todolist):
super(IcalPrinter, self).__init__()
super().__init__()
self.todolist = p_todolist
try:
......@@ -112,7 +112,7 @@ class IcalPrinter(Printer):
if not uid:
uid = generate_uid()
p_todo.set_tag('ical', uid)
self.todolist.set_dirty()
self.todolist.dirty = True
return uid
......
......@@ -52,7 +52,7 @@ class JsonPrinter(Printer):
"""
def __init__(self):
super(JsonPrinter, self).__init__()
super().__init__()
def print_todo(self, p_todo):
return json.dumps(_convert_todo(p_todo), ensure_ascii=False,
......
......@@ -16,6 +16,7 @@
from topydo.lib.prettyprinters.Colors import PrettyPrinterColorFilter
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.TopydoString import TopydoString
class Printer(object):
......@@ -29,11 +30,8 @@ class Printer(object):
raise NotImplementedError
def print_list(self, p_todos):
"""
Given a list of todo items, pretty print it and return a list of
formatted strings.
"""
return "\n".join([self.print_todo(todo) for todo in p_todos])
for todo in p_todos:
self.print_todo(todo)
class PrettyPrinter(Printer):
......@@ -50,7 +48,7 @@ class PrettyPrinter(Printer):
"""
Constructor.
"""
super(PrettyPrinter, self).__init__()
super().__init__()
self.filters = []
def add_filter(self, p_filter):
......@@ -68,7 +66,15 @@ class PrettyPrinter(Printer):
for ppf in self.filters:
todo_str = ppf.filter(todo_str, p_todo)
return todo_str
return TopydoString(todo_str)
def print_list(self, p_todos):
"""
Given a list of todo items, pretty print it and return a list of
formatted TopydoStrings. The output function in the UI should convert
the colors inside properly.
"""
return [self.print_todo(todo) for todo in p_todos]
def pretty_printer_factory(p_todolist, p_additional_filters=None):
......
......@@ -22,7 +22,10 @@ I/O on the command-line.
import getopt
import sys
MAIN_OPTS = "ac:d:ht:v"
from topydo.lib.Color import AbstractColor, Color
from topydo.lib.TopydoString import TopydoString
MAIN_OPTS = "ac:C:d:ht:v"
READ_ONLY_COMMANDS = ('List', 'ListContext', 'ListProject')
......@@ -30,12 +33,14 @@ def usage():
""" Prints the command-line usage of topydo. """
print("""\
Synopsis: topydo [-a] [-c <config>] [-d <archive>] [-t <todo.txt>] subcommand [help|args]
Synopsis: topydo [-a] [-c <config>] [-C <colormode>] [-d <archive>] [-t <todo.txt>] subcommand [help|args]
topydo -h
topydo -v
-a : Do not archive todo items on completion.
-c : Specify an alternative configuration file.
-C : Specify color mode (0 = disable, 1 = enable 16 colors,
16 = enable 16 colors, 256 = enable 256 colors, auto (default))
-d : Specify an alternative archive file (done.txt)
-h : This help text
-t : Specify an alternative todo file
......@@ -59,23 +64,62 @@ Available commands:
* sort
* tag
Run `topydo help <subcommand>` for command-specific help.
Run `topydo help <subcommand>` for command-specific help.\
""")
def write(p_file, p_string):
"""
Write p_string to file p_file, trailed by a newline character.
ANSI codes are removed when the file is not a TTY.
ANSI codes are removed when the file is not a TTY (and colors are
automatically determined).
"""
if not p_file.isatty():
if not config().colors(p_file.isatty()):
p_string = escape_ansi(p_string)
if p_string:
p_file.write(p_string + "\n")
def lookup_color(p_color):
"""
Converts an AbstractColor to a normal Color. Returns the Color itself
when a normal color is passed.
"""
if not lookup_color.colors:
lookup_color.colors[AbstractColor.NEUTRAL] = Color('NEUTRAL')
lookup_color.colors[AbstractColor.PROJECT] = config().project_color()
lookup_color.colors[AbstractColor.CONTEXT] = config().context_color()
lookup_color.colors[AbstractColor.META] = config().metadata_color()
lookup_color.colors[AbstractColor.LINK] = config().link_color()
try:
return lookup_color.colors[p_color]
except KeyError:
return p_color
lookup_color.colors = {}
def insert_ansi(p_string):
""" Returns a string with color information at the right positions. """
result = p_string.data
for pos, color in sorted(p_string.colors.items(), reverse=True):
color = lookup_color(color)
result = result[:pos] + color.as_ansi() + result[pos:]
return result
def output(p_string):
if isinstance(p_string, list):
p_string = "\n".join([insert_ansi(s) for s in p_string])
elif isinstance(p_string, TopydoString):
# convert color codes to ANSI
p_string = insert_ansi(p_string)
write(sys.stdout, p_string)
def error(p_string):
""" Writes an error on the standard error. """
write(sys.stderr, p_string)
......@@ -140,6 +184,9 @@ class CLIApplicationBase(object):
self.do_archive = False
elif opt == "-c":
alt_config_path = value
elif opt == "-C":
overrides[('topydo', 'force_colors')] = '1'
overrides[('topydo', 'colors')] = value
elif opt == "-t":
overrides[('topydo', 'filename')] = value
elif opt == "-d":
......@@ -174,7 +221,7 @@ class CLIApplicationBase(object):
command = ArchiveCommand(self.todolist, archive)
command.execute()
if archive.is_dirty():
if archive.dirty:
archive_file.write(archive.print_todos())
def _help(self, args):
......@@ -189,21 +236,24 @@ class CLIApplicationBase(object):
READ_ONLY_COMMANDS)
return p_command.__module__.endswith(read_only_commands)
def _execute(self, p_command, p_args):
"""
Execute a subcommand with arguments. p_command is a class (not an
object).
"""
def _backup(self, p_command, p_args):
if config().backup_count() > 0 and p_command and not self.is_read_only(p_command):
call = [p_command.__module__.lower()[16:-7]] + p_args # strip "topydo.commands" and "Command"
from topydo.lib.ChangeSet import ChangeSet
self.backup = ChangeSet(self.todolist, p_call=call)
def _execute(self, p_command, p_args):
"""
Execute a subcommand with arguments. p_command is a class (not an
object).
"""
self._backup(p_command, p_args)
command = p_command(
p_args,
self.todolist,
lambda o: write(sys.stdout, o),
output,
error,
input)
......@@ -219,9 +269,9 @@ class CLIApplicationBase(object):
to the todo.txt file.
"""
# do not archive when the value of the filename is an empty string
# (i.e. explicitly left empty in the configuration
if self.todolist.is_dirty():
if self.todolist.dirty:
# do not archive when the value of the filename is an empty string
# (i.e. explicitly left empty in the configuration
if self.do_archive and config().archive():
self._archive()
......@@ -233,6 +283,7 @@ class CLIApplicationBase(object):
self.backup.save(self.todolist)
self.todofile.write(self.todolist.print_todos())
self.todolist.dirty = False
self.backup = None
......
......@@ -19,8 +19,8 @@
import getopt
import sys
from topydo.cli.CLI import CLIApplication
from topydo.cli.CLIApplicationBase import MAIN_OPTS, error
from topydo.ui.cli.CLI import CLIApplication
from topydo.ui.CLIApplicationBase import MAIN_OPTS, error
# enable color on windows CMD
if "win32" in sys.platform:
......@@ -41,10 +41,16 @@ def main():
if args[0] == 'prompt':
try:
from topydo.cli.Prompt import PromptApplication
from topydo.ui.prompt.Prompt import PromptApplication
PromptApplication().run()
except ImportError:
error("You have to install prompt-toolkit to run prompt mode.")
elif args[0] == 'columns':
try:
from topydo.ui.columns.Main import UIApplication
UIApplication().run()
except ImportError:
error("You have to install urwid to run column mode.")
else:
CLIApplication().run()
except IndexError:
......
......@@ -18,7 +18,7 @@
import sys
from topydo.cli.CLIApplicationBase import CLIApplicationBase, error
from topydo.ui.CLIApplicationBase import CLIApplicationBase, error
from topydo.lib import TodoFile
from topydo.lib.Config import config, ConfigError
......@@ -41,7 +41,7 @@ class CLIApplication(CLIApplicationBase):
"""
def __init__(self):
super(CLIApplication, self).__init__()
super().__init__()
def run(self):
""" Main entry function. """
......@@ -50,7 +50,11 @@ class CLIApplication(CLIApplicationBase):
self.todofile = TodoFile.TodoFile(config().todotxt())
self.todolist = TodoList.TodoList(self.todofile.read())
(subcommand, args) = get_subcommand(args)
try:
(subcommand, args) = get_subcommand(args)
except ConfigError as ce:
error('Error: ' + str(ce) + '. Check your aliases configuration')
sys.exit(1)
if subcommand is None:
self._usage()
......
# 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.columns.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.Commands import get_subcommand
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
from topydo.ui.CLIApplicationBase import CLIApplicationBase
from topydo.ui.columns.CommandLineWidget import CommandLineWidget
from topydo.ui.columns.ConsoleWidget import ConsoleWidget
from topydo.ui.columns.KeystateWidget import KeystateWidget
from topydo.ui.columns.TodoListWidget import TodoListWidget
from topydo.ui.columns.Utils import PaletteItem, to_urwid_color
from topydo.ui.columns.ViewWidget import ViewWidget
from topydo.ui.columns.ColumnLayout import columns
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.column_width = config().column_width()
self.todofile = TodoFile.TodoFile(config().todotxt())
self.todolist = TodoList.TodoList(self.todofile.read())
self.marked_todos = []
self.columns = urwid.Columns([], dividechars=0,
min_width=config().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=config().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.lib.Utils import translate_key_to_config
from topydo.ui.columns.TodoWidget import TodoWidget
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.columns.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
......@@ -20,8 +20,8 @@ import os.path
import shlex
import sys
from topydo.cli.CLIApplicationBase import CLIApplicationBase, error, usage
from topydo.cli.TopydoCompleter import TopydoCompleter
from topydo.ui.CLIApplicationBase import CLIApplicationBase, error, usage
from topydo.ui.prompt.TopydoCompleter import TopydoCompleter
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.history import InMemoryHistory
......@@ -59,7 +59,7 @@ class PromptApplication(CLIApplicationBase):
"""
def __init__(self):
super(PromptApplication, self).__init__()
super().__init__()
self._process_flags()
self.mtime = None
......@@ -95,11 +95,21 @@ class PromptApplication(CLIApplicationBase):
completer=self.completer,
complete_while_typing=False)
user_input = shlex.split(user_input)
except (EOFError, KeyboardInterrupt):
except EOFError:
sys.exit(0)
except KeyboardInterrupt:
continue
except ValueError as verr:
error('Error: ' + str(verr))
continue
mtime_after = _todotxt_mtime()
(subcommand, args) = get_subcommand(user_input)
try:
(subcommand, args) = get_subcommand(user_input)
except ConfigError as ce:
error('Error: ' + str(ce) + '. Check your aliases configuration')
continue
# refuse to perform operations such as 'del' and 'do' if the
# todo.txt file has been changed in the background.
......
[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