Commit ae7dbd1f authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'master' into stable

parents eac76901 5422906f
[run]
source = topydo
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if 0:
if __name__ == .__main__.:
omit =
topydo/lib/ExitCommand.py
topydo/lib/Version.py
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
build build
dist dist
install install
.coverage
0.5
---
* Remove 'ical' subcommand in favor of 'topydo ls -f ical'
* Remove options highlight_projects_colors in favor of colorscheme options. In case you wish to disable the project/context colors, assign an empty value in the configuration file:
[colorscheme]
project_color =
context_color =
* `del`, `depri`, `do`, `pri`, `postpone` now support now expression like `ls`
does, using the `-e` flag (Jacek Sowiński, @mruwek).
* Fix `ls` when searching for a certain key:value where value is a string.
* Disable auto archive when the option archive_filename is empty.
* Add option auto_creation_date to enable/disable the creation date being added
to new todo items.
* Calculate relative dates correctly in long-running `prompt` sessions.
* `pri` also accepts priorities in the form (A), [A] or any other bracket.
* Add `listcontext` and `listcontexts` as aliases of `lscon`.
* Highlight tags when the value is one character long.
* Cleanups
0.4.1 0.4.1
----- -----
......
If you're reading this, you may have interest in enhancing topydo. Thank you! If you're reading this, you may have interest in enhancing topydo. Thank you!
Please read the following guidelines to get your enhancement / bug fixes Please read the following guidelines to get your enhancement / bug fixes
smoothly into topydo: smoothly into topydo.
### General
* This Github page defaults to the **stable** branch which is for **bug fixes * This Github page defaults to the **stable** branch which is for **bug fixes
only**. If you would like to add a new feature, make sure to make a Pull only**. If you would like to add a new feature, make sure to make a Pull
Request on the `master` branch. Request on the `master` branch.
* Use descriptive commit messages.
### Coding style
* Please try to adhere to the coding style dictated by `pylint` as much
possible. I won't be very picky about long lines, but please try to avoid
them.
* I strongly prefer simple and short functions, doing only one thing. I'll
request you to refactor functions with massive indentation or don't fit
otherwise on a screen.
### Testing
* Run tests with: * Run tests with:
./run-tests.sh [python2|python3] ./run-tests.sh [python2|python3]
...@@ -21,13 +37,17 @@ smoothly into topydo: ...@@ -21,13 +37,17 @@ smoothly into topydo:
ever again. ever again.
* Features: add testcases that checks various inputs and outputs of your * Features: add testcases that checks various inputs and outputs of your
feature. Be creative in trying to break the feature you've just implemented. feature. Be creative in trying to break the feature you've just implemented.
* Use descriptive commit messages. * Check the test coverage of your contributed code, in particular if you
touched code in the topydo.lib or topydo.command packages:
### Coding style pip install coverage
coverage run setup.py test
coverage report
Or alternatively, for a more friendly output, run:
coverage html
Which will generate annotated files in the *htmlcov* folder. The new code
should be marked green (i.e. covered).
* Please try to adhere to the coding style dictated by `pylint` as much
possible. I won't be very picky about long lines, but please try to avoid
them.
* I strongly prefer simple and short functions, doing only one thing. I'll
request you to refactor functions with massive indentation or don't fit
otherwise on a screen.
...@@ -22,10 +22,17 @@ use topydo. ...@@ -22,10 +22,17 @@ use topydo.
Install Install
------- -------
Install simply with: Simply install with:
pip install topydo pip install topydo
### Optional dependencies
* icalendar : To print your todo.txt file as an iCalendar file
(not supported for Python 3.2).
* prompt-toolkit : For topydo's _prompt_ mode, which offers a shell-like
interface with auto-completion.
Demo Demo
---- ----
......
...@@ -3,7 +3,7 @@ from setuptools import setup, find_packages ...@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
setup( setup(
name = "topydo", name = "topydo",
packages = find_packages(exclude=["test"]), packages = find_packages(exclude=["test"]),
version = "0.4.1", version = "0.5",
description = "A command-line todo list application using the todo.txt format.", description = "A command-line todo list application using the todo.txt format.",
author = "Bram Schoenmakers", author = "Bram Schoenmakers",
author_email = "me@bramschoenmakers.nl", author_email = "me@bramschoenmakers.nl",
...@@ -20,7 +20,7 @@ setup( ...@@ -20,7 +20,7 @@ setup(
'console_scripts': ['topydo = topydo.cli.UILoader:main'], 'console_scripts': ['topydo = topydo.cli.UILoader:main'],
}, },
classifiers = [ classifiers = [
"Development Status :: 4 - Beta", "Development Status :: 5 - Production/Stable",
"Environment :: Console", "Environment :: Console",
"Intended Audience :: End Users/Desktop", "Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
......
...@@ -29,7 +29,7 @@ from io import StringIO ...@@ -29,7 +29,7 @@ from io import StringIO
from topydo.commands import AddCommand from topydo.commands import AddCommand
from topydo.commands import ListCommand from topydo.commands import ListCommand
from test.CommandTest import CommandTest, utf8 from test.CommandTest import CommandTest
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import TodoList from topydo.lib import TodoList
...@@ -127,7 +127,7 @@ class AddCommandTest(CommandTest): ...@@ -127,7 +127,7 @@ class AddCommandTest(CommandTest):
self.assertFalse(self.todolist.todo(1).has_tag("after")) self.assertFalse(self.todolist.todo(1).has_tag("after"))
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo") self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo")
self.assertEqual(self.output, "| 1| " + str(self.todolist.todo(1)) + "\n") self.assertEqual(self.output, "| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep5(self): def test_add_dep5(self):
...@@ -137,7 +137,7 @@ class AddCommandTest(CommandTest): ...@@ -137,7 +137,7 @@ class AddCommandTest(CommandTest):
self.assertFalse(self.todolist.todo(1).has_tag("after")) self.assertFalse(self.todolist.todo(1).has_tag("after"))
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo") self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo")
self.assertEqual(self.output, "| 1| " + str(self.todolist.todo(1)) + "\n") self.assertEqual(self.output, "| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep6(self): def test_add_dep6(self):
...@@ -248,7 +248,7 @@ class AddCommandTest(CommandTest): ...@@ -248,7 +248,7 @@ class AddCommandTest(CommandTest):
command = AddCommand.AddCommand([u("Special \u25c4")], self.todolist, self.out, self.error) command = AddCommand.AddCommand([u("Special \u25c4")], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output, utf8(u("| 1| {} Special \u25c4\n").format(self.today))) self.assertEqual(self.output, u("| 1| {} Special \u25c4\n").format(self.today))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
@mock.patch("topydo.commands.AddCommand.stdin", StringIO(u("Fo\u00f3 due:tod id:1\nB\u0105r before:1"))) @mock.patch("topydo.commands.AddCommand.stdin", StringIO(u("Fo\u00f3 due:tod id:1\nB\u0105r before:1")))
...@@ -256,14 +256,24 @@ class AddCommandTest(CommandTest): ...@@ -256,14 +256,24 @@ class AddCommandTest(CommandTest):
command = AddCommand.AddCommand(["-f", "-"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["-f", "-"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output, utf8(u("| 1| {tod} Fo\u00f3 due:{tod} id:1\n| 2| {tod} B\u0105r p:1\n".format(tod=self.today)))) self.assertEqual(self.output, u("| 1| {tod} Fo\u00f3 due:{tod} id:1\n| 2| {tod} B\u0105r p:1\n".format(tod=self.today)))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_from_file(self): def test_add_from_file(self):
command = AddCommand.AddCommand(["-f", "test/data/AddCommandTest-from_file.txt"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["-f", "test/data/AddCommandTest-from_file.txt"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output, utf8(u("| 1| {tod} Foo @fo\u00f3b\u0105r due:{tod} id:1\n| 2| {tod} Bar +baz t:{tod} p:1\n".format(tod=self.today)))) self.assertEqual(self.output, u("| 1| {tod} Foo @fo\u00f3b\u0105r due:{tod} id:1\n| 2| {tod} Bar +baz t:{tod} p:1\n".format(tod=self.today)))
self.assertEqual(self.errors, "")
def test_add_task_without_date(self):
config(p_overrides={('add', 'auto_creation_date'): '0'})
args = ["New todo"]
command = AddCommand.AddCommand(args, self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), "New todo")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_help(self): def test_help(self):
......
...@@ -31,8 +31,8 @@ class ArchiveCommandTest(CommandTest): ...@@ -31,8 +31,8 @@ class ArchiveCommandTest(CommandTest):
self.assertTrue(todolist.is_dirty()) self.assertTrue(todolist.is_dirty())
self.assertTrue(archive.is_dirty()) self.assertTrue(archive.is_dirty())
self.assertEqual(str(todolist), "x Not complete\n(C) Active") self.assertEqual(todolist.print_todos(), "x Not complete\n(C) Active")
self.assertEqual(str(archive), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete") self.assertEqual(archive.print_todos(), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest import unittest
from six import PY2
from topydo.lib.Utils import escape_ansi from topydo.lib.Utils import escape_ansi
from test.TopydoTest import TopydoTest from test.TopydoTest import TopydoTest
...@@ -34,13 +33,5 @@ class CommandTest(TopydoTest): ...@@ -34,13 +33,5 @@ class CommandTest(TopydoTest):
if p_error: if p_error:
self.errors += escape_ansi(p_error + "\n") self.errors += escape_ansi(p_error + "\n")
# utility for several commands
def utf8(p_string):
""" Converts a Unicode string to UTF-8 in case of Python 2. """
if PY2:
p_string = p_string.encode('utf-8')
return p_string
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -35,6 +35,8 @@ class DeleteCommandTest(CommandTest): ...@@ -35,6 +35,8 @@ class DeleteCommandTest(CommandTest):
todos = [ todos = [
"Foo id:1", "Foo id:1",
"Bar p:1", "Bar p:1",
"a @test with due:2015-06-03",
"a @test with +project",
] ]
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
...@@ -62,7 +64,7 @@ class DeleteCommandTest(CommandTest): ...@@ -62,7 +64,7 @@ class DeleteCommandTest(CommandTest):
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 0) self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Bar\nRemoved: Foo\n") self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Bar\nRemoved: Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -71,7 +73,7 @@ class DeleteCommandTest(CommandTest): ...@@ -71,7 +73,7 @@ class DeleteCommandTest(CommandTest):
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 1) # force won't delete subtasks self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n") self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -80,7 +82,7 @@ class DeleteCommandTest(CommandTest): ...@@ -80,7 +82,7 @@ class DeleteCommandTest(CommandTest):
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 1) # force won't delete subtasks self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n") self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -116,7 +118,9 @@ class DeleteCommandTest(CommandTest): ...@@ -116,7 +118,9 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["8to"], self.todolist, self.out, self.error) command = DeleteCommand(["8to"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(str(self.todolist), "Foo") result = "Foo\na @test with due:2015-06-03\na @test with +project"
self.assertEqual(self.todolist.print_todos(), result)
self.assertRaises(InvalidTodoException, self.todolist.todo, 'b0n') self.assertRaises(InvalidTodoException, self.todolist.todo, 'b0n')
def test_multi_del1(self): def test_multi_del1(self):
...@@ -124,14 +128,20 @@ class DeleteCommandTest(CommandTest): ...@@ -124,14 +128,20 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _no_prompt) command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _no_prompt)
command.execute() command.execute()
self.assertEqual(self.todolist.count(), 0) result = "a @test with due:2015-06-03\na @test with +project"
self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.todolist.print_todos(), result)
def test_multi_del2(self): def test_multi_del2(self):
""" Test deletion of multiple items. """ """ Test deletion of multiple items. """
command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _yes_prompt) command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _yes_prompt)
command.execute() command.execute()
self.assertEqual(self.todolist.count(), 0) result = "a @test with due:2015-06-03\na @test with +project"
self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.todolist.print_todos(), result)
def test_multi_del3(self): def test_multi_del3(self):
""" Fail if any of supplied todo numbers is invalid. """ """ Fail if any of supplied todo numbers is invalid. """
...@@ -160,6 +170,50 @@ class DeleteCommandTest(CommandTest): ...@@ -160,6 +170,50 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
def test_expr_del1(self):
command = DeleteCommand(["-e", "@test"], self.todolist, self.out, self.error, None)
command.execute()
result = "Removed: a @test with due:2015-06-03\nRemoved: a @test with +project\n"
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_expr_del2(self):
command = DeleteCommand(["-e", "@test", "due:2015-06-03"], self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "Removed: a @test with due:2015-06-03\n")
self.assertEqual(self.errors, "")
def test_expr_del3(self):
command = DeleteCommand(["-e", "@test", "due:2015-06-03", "+project"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_del4(self):
""" Remove only relevant todo items. """
command = DeleteCommand(["-e", ""], self.todolist, self.out, self.error, None)
command.execute()
result = "Foo"
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 1)
self.assertEqual(self.todolist.print_todos(), result)
def test_expr_del5(self):
""" Force deleting unrelevant items with additional -x flag. """
command = DeleteCommand(["-xe", ""], self.todolist, self.out, self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 0)
def test_empty(self): def test_empty(self):
command = DeleteCommand([], self.todolist, self.out, self.error) command = DeleteCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -211,6 +211,14 @@ class DepCommandTest(CommandTest): ...@@ -211,6 +211,14 @@ class DepCommandTest(CommandTest):
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_ls7(self):
command = DepCommand(["ls", "top", "99"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
def gc_helper(self, p_subcommand): def gc_helper(self, p_subcommand):
command = DepCommand([p_subcommand], self.todolist, self.out, self.error) command = DepCommand([p_subcommand], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -28,6 +28,9 @@ class DepriCommandTest(CommandTest): ...@@ -28,6 +28,9 @@ class DepriCommandTest(CommandTest):
"(A) Foo", "(A) Foo",
"Bar", "Bar",
"(B) Baz", "(B) Baz",
"(E) a @test with due:2015-06-03",
"(Z) a @test with +project p:1",
"(D) Bax id:1",
] ]
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
...@@ -69,6 +72,50 @@ class DepriCommandTest(CommandTest): ...@@ -69,6 +72,50 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.output, "Priority removed.\n| 1| Foo\nPriority removed.\n| 3| Baz\n") self.assertEqual(self.output, "Priority removed.\n| 1| Foo\nPriority removed.\n| 3| Baz\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_depri1(self):
command = DepriCommand(["-e", "@test"], self.todolist, self.out, self.error, None)
command.execute()
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.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_expr_depri2(self):
command = DepriCommand(["-e", "@test", "due:2015-06-03"], self.todolist, self.out, self.error, None)
command.execute()
result = "Priority removed.\n| 4| a @test with due:2015-06-03\n"
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_expr_depri3(self):
command = DepriCommand(["-e", "@test", "due:2015-06-03", "+project"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_depri4(self):
""" Don't remove priority from unrelevant todo items. """
command = DepriCommand(["-e", "Bax"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_depri5(self):
""" Force unprioritizing unrelevant items with additional -x flag. """
command = DepriCommand(["-xe", "Bax"], self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "Priority removed.\n| 6| Bax id:1\n")
self.assertEqual(self.errors, "")
def test_invalid1(self): def test_invalid1(self):
command = DepriCommand(["99"], self.todolist, self.out, self.error) command = DepriCommand(["99"], self.todolist, self.out, self.error)
......
...@@ -41,6 +41,8 @@ class DoCommandTest(CommandTest): ...@@ -41,6 +41,8 @@ class DoCommandTest(CommandTest):
"Subtodo of inactive p:2", "Subtodo of inactive p:2",
"Strict due:2014-01-01 rec:1d", "Strict due:2014-01-01 rec:1d",
"Invalid rec:1", "Invalid rec:1",
"a @test with due:2015-06-03",
"a @test with +project",
] ]
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
...@@ -123,7 +125,7 @@ class DoCommandTest(CommandTest): ...@@ -123,7 +125,7 @@ class DoCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.count(), 10) self.assertEqual(self.todolist.count(), 12)
def test_recurrence(self): def test_recurrence(self):
self.assertFalse(self.todolist.todo(4).has_tag('due')) self.assertFalse(self.todolist.todo(4).has_tag('due'))
...@@ -131,7 +133,7 @@ class DoCommandTest(CommandTest): ...@@ -131,7 +133,7 @@ class DoCommandTest(CommandTest):
self._recurrence_helper(["4"]) self._recurrence_helper(["4"])
self.assertTrue(self.todolist.todo(4).is_completed()) self.assertTrue(self.todolist.todo(4).is_completed())
result = "| 10| {today} Recurring! rec:1d due:{tomorrow}\nCompleted: x {today} Recurring! rec:1d\n".format(today=self.today, tomorrow=self.tomorrow) result = "| 12| {today} Recurring! rec:1d due:{tomorrow}\nCompleted: x {today} Recurring! rec:1d\n".format(today=self.today, tomorrow=self.tomorrow)
self.assertEqual(self.output, result) self.assertEqual(self.output, result)
todo = self.todolist.todo(10) todo = self.todolist.todo(10)
...@@ -140,13 +142,13 @@ class DoCommandTest(CommandTest): ...@@ -140,13 +142,13 @@ class DoCommandTest(CommandTest):
def test_strict_recurrence1(self): def test_strict_recurrence1(self):
self._recurrence_helper(["-s", "8"]) self._recurrence_helper(["-s", "8"])
result = "| 10| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {today} Strict due:2014-01-01 rec:1d\n".format(today=self.today) result = "| 12| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {today} Strict due:2014-01-01 rec:1d\n".format(today=self.today)
self.assertEqual(self.output, result) self.assertEqual(self.output, result)
def test_strict_recurrence2(self): def test_strict_recurrence2(self):
self._recurrence_helper(["--strict", "8"]) self._recurrence_helper(["--strict", "8"])
result = "| 10| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {today} Strict due:2014-01-01 rec:1d\n".format(today=self.today) result = "| 12| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {today} Strict due:2014-01-01 rec:1d\n".format(today=self.today)
self.assertEqual(self.output, result) self.assertEqual(self.output, result)
def test_invalid1(self): def test_invalid1(self):
...@@ -254,7 +256,7 @@ class DoCommandTest(CommandTest): ...@@ -254,7 +256,7 @@ class DoCommandTest(CommandTest):
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 10| {today} Recurring! rec:1d due:{today}\nCompleted: x {yesterday} Recurring! rec:1d\n".format(today=self.today, yesterday=self.yesterday)) self.assertEqual(self.output, "| 12| {today} Recurring! rec:1d due:{today}\nCompleted: x {yesterday} Recurring! rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_do_custom_date6(self): def test_do_custom_date6(self):
...@@ -267,7 +269,7 @@ class DoCommandTest(CommandTest): ...@@ -267,7 +269,7 @@ class DoCommandTest(CommandTest):
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 10| {today} Recurring! rec:1d due:{today}\nCompleted: x {yesterday} Recurring! rec:1d\n".format(today=self.today, yesterday=self.yesterday)) self.assertEqual(self.output, "| 12| {today} Recurring! rec:1d due:{today}\nCompleted: x {yesterday} Recurring! rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_do_custom_date7(self): def test_do_custom_date7(self):
...@@ -279,7 +281,7 @@ class DoCommandTest(CommandTest): ...@@ -279,7 +281,7 @@ class DoCommandTest(CommandTest):
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 10| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {yesterday} Strict due:2014-01-01 rec:1d\n".format(today=self.today, yesterday=self.yesterday)) self.assertEqual(self.output, "| 12| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {yesterday} Strict due:2014-01-01 rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_multi_do1(self): def test_multi_do1(self):
...@@ -320,10 +322,10 @@ class DoCommandTest(CommandTest): ...@@ -320,10 +322,10 @@ class DoCommandTest(CommandTest):
""" """
Check output when all supplied todo numbers are invalid. Check output when all supplied todo numbers are invalid.
""" """
command = DoCommand(["99", "10"], self.todolist, self.out, self.error, _no_prompt) command = DoCommand(["99", "15"], self.todolist, self.out, self.error, _no_prompt)
command.execute() command.execute()
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 10.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 15.\n")
def test_multi_do6(self): def test_multi_do6(self):
""" Throw an error with invalid argument containing special characters. """ """ Throw an error with invalid argument containing special characters. """
...@@ -333,6 +335,46 @@ class DoCommandTest(CommandTest): ...@@ -333,6 +335,46 @@ class DoCommandTest(CommandTest):
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
def test_expr_do1(self):
command = DoCommand(["-e", "@test"], self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_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, "")
def test_expr_do2(self):
command = DoCommand(["-e", "@test", "due:2015-06-03"], self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "Completed: x {} a @test with due:2015-06-03\n".format(self.today))
self.assertEqual(self.errors, "")
def test_expr_do3(self):
command = DoCommand(["-e", "@test", "due:2015-06-03", "+project"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_do4(self):
""" Don't do anything with unrelevant todo items. """
command = DoCommand(["-e", "Foo"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_do5(self):
""" Force marking unrelevant items as done with additional -x flag. """
command = DoCommand(["-xe", "Foo"], self.todolist, self.out, self.error, _yes_prompt)
command.execute()
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.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_invalid_recurrence(self): def test_invalid_recurrence(self):
""" Show error message when an item has an invalid recurrence pattern. """ """ Show error message when an item has an invalid recurrence pattern. """
command = DoCommand(["9"], self.todolist, self.out, self.error, _no_prompt) command = DoCommand(["9"], self.todolist, self.out, self.error, _no_prompt)
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
import unittest import unittest
# We're searching for 'mock' # We're searching for 'mock'
# pylint: disable=no-name-in-module # pylint: disable=no-name-in-module
try: try:
from unittest import mock from unittest import mock
except ImportError: except ImportError:
...@@ -27,7 +27,7 @@ from six import u ...@@ -27,7 +27,7 @@ from six import u
import os import os
from topydo.commands.EditCommand import EditCommand from topydo.commands.EditCommand import EditCommand
from test.CommandTest import CommandTest, utf8 from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from topydo.lib.Config import config from topydo.lib.Config import config
...@@ -44,7 +44,6 @@ class EditCommandTest(CommandTest): ...@@ -44,7 +44,6 @@ class EditCommandTest(CommandTest):
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit1(self, mock_open_in_editor): def test_edit1(self, mock_open_in_editor):
""" Preserve dependencies after editing. """ """ Preserve dependencies after editing. """
...@@ -55,7 +54,7 @@ class EditCommandTest(CommandTest): ...@@ -55,7 +54,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), utf8(u("Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1"))) self.assertEqual(self.todolist.print_todos(), u("Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1"))
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp') @mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
...@@ -69,7 +68,7 @@ class EditCommandTest(CommandTest): ...@@ -69,7 +68,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat"))) self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat"))
def test_edit3(self): def test_edit3(self):
""" Throw an error after invalid todo number given as argument. """ """ Throw an error after invalid todo number given as argument. """
...@@ -99,7 +98,7 @@ class EditCommandTest(CommandTest): ...@@ -99,7 +98,7 @@ class EditCommandTest(CommandTest):
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Number of edited todos is not equal to number of supplied todo IDs.\n") self.assertEqual(self.errors, "Number of edited todos is not equal to number of supplied todo IDs.\n")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a"))) self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nBar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a"))
def test_edit6(self): def test_edit6(self):
""" Throw an error with invalid argument containing special characters. """ """ Throw an error with invalid argument containing special characters. """
...@@ -121,7 +120,7 @@ class EditCommandTest(CommandTest): ...@@ -121,7 +120,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat"))) self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat"))
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp') @mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
...@@ -133,14 +132,14 @@ class EditCommandTest(CommandTest): ...@@ -133,14 +132,14 @@ class EditCommandTest(CommandTest):
command = EditCommand(["-e", "@test"], self.todolist, self.out, self.error, None) command = EditCommand(["-e", "@test"], self.todolist, self.out, self.error, None)
command.execute() command.execute()
expected = utf8(u("| 3| Lazy Cat\n| 4| Lazy Dog\n")) expected = u("| 3| Lazy Cat\n| 4| Lazy Dog\n")
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(self.output, expected) self.assertEqual(self.output, expected)
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog"))) self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog"))
@mock.patch('topydo.commands.EditCommand.call') @mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_archive(self, mock_call): def test_edit_archive(self, mock_call):
""" Edit archive file. """ """ Edit archive file. """
mock_call.return_value = 0 mock_call.return_value = 0
...@@ -149,11 +148,29 @@ class EditCommandTest(CommandTest): ...@@ -149,11 +148,29 @@ class EditCommandTest(CommandTest):
os.environ['EDITOR'] = editor os.environ['EDITOR'] = editor
archive = config().archive() archive = config().archive()
command = EditCommand([u("-d")], self.todolist, self.out, self.error, None) command = EditCommand(["-d"], self.todolist, self.out, self.error, None)
command.execute() command.execute()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
mock_call.assert_called_once_with([editor, archive]) mock_call.assert_called_once_with([editor, archive])
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_todotxt(self, mock_call):
""" Edit todo file. """
mock_call.return_value = 0
editor = 'vi'
os.environ['EDITOR'] = editor
todotxt = config().todotxt()
result = self.todolist.print_todos() # copy TodoList content *before* executing command
command = EditCommand([], self.todolist, self.out, self.error, None)
command.execute()
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), result)
mock_call.assert_called_once_with([editor, todotxt])
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -185,13 +185,6 @@ class FilterTest(TopydoTest): ...@@ -185,13 +185,6 @@ class FilterTest(TopydoTest):
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter19(self):
todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter(1)
filtered_todos = grep.filter(todos)
self.assertEqual(filtered_todos, [])
def test_filter20(self): def test_filter20(self):
todos = load_file('test/data/FilterTest3.txt') todos = load_file('test/data/FilterTest3.txt')
otf = Filter.OrdinalTagFilter('due:<2014-11-10') otf = Filter.OrdinalTagFilter('due:<2014-11-10')
...@@ -330,7 +323,7 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -330,7 +323,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo1) self.assertEqual(result[0].source(), self.todo1)
def test_filter2(self): def test_filter2(self):
otf = Filter.OrdinalTagFilter('due:=today') otf = Filter.OrdinalTagFilter('due:=today')
...@@ -338,7 +331,7 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -338,7 +331,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo1) self.assertEqual(result[0].source(), self.todo1)
def test_filter3(self): def test_filter3(self):
otf = Filter.OrdinalTagFilter('due:>today') otf = Filter.OrdinalTagFilter('due:>today')
...@@ -346,7 +339,7 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -346,7 +339,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo2) self.assertEqual(result[0].source(), self.todo2)
def test_filter4(self): def test_filter4(self):
otf = Filter.OrdinalTagFilter('due:<1w') otf = Filter.OrdinalTagFilter('due:<1w')
...@@ -354,8 +347,8 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -354,8 +347,8 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 2) self.assertEqual(len(result), 2)
self.assertEqual(str(result[0]), self.todo1) self.assertEqual(result[0].source(), self.todo1)
self.assertEqual(str(result[1]), self.todo2) self.assertEqual(result[1].source(), self.todo2)
def test_filter5(self): def test_filter5(self):
otf = Filter.OrdinalTagFilter('due:!today') otf = Filter.OrdinalTagFilter('due:!today')
...@@ -363,7 +356,7 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -363,7 +356,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo2) self.assertEqual(result[0].source(), self.todo2)
def test_filter6(self): def test_filter6(self):
otf = Filter.OrdinalTagFilter('due:non') otf = Filter.OrdinalTagFilter('due:non')
......
...@@ -14,30 +14,20 @@ ...@@ -14,30 +14,20 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" from topydo.lib.JsonPrinter import JsonPrinter
Stub for the former 'ical' subcommand, now replaced with 'ls -f ical'. from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
To be removed. class JsonPrinterTest(TopydoTest):
""" """
Tests the functionality of printing a single todo item. Printing a list is
already covered by the ListCommand tests.
"""
def test_json(self):
""" Print a single todo item. """
printer = JsonPrinter()
todo = Todo('2015-06-06 Foo due:2015-05-32')
from topydo.lib.Command import Command result = printer.print_todo(todo)
class IcalCommand(Command): self.assertEqual(result, '{"completed": false, "completion_date": null, "contexts": [], "creation_date": "2015-06-06", "priority": null, "projects": [], "source": "2015-06-06 Foo due:2015-05-32", "tags": [["due", "2015-05-32"]], "text": "Foo"}')
def __init__(self, p_args, p_todolist,
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(IcalCommand, self).__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
def execute(self):
self.error("The 'ical' subcommand is deprecated, please use 'ls -f ical' instead.")
return False
def usage(self):
return """Synopsis: ical"""
def help(self):
return """\
Deprecated. Use 'ls -f ical' instead.
"""
...@@ -22,7 +22,7 @@ import unittest ...@@ -22,7 +22,7 @@ import unittest
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.commands.ListCommand import ListCommand from topydo.commands.ListCommand import ListCommand
from test.CommandTest import CommandTest, utf8 from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist from test.TestFacilities import load_file_to_todolist
class ListCommandTest(CommandTest): class ListCommandTest(CommandTest):
...@@ -219,12 +219,11 @@ class ListCommandUnicodeTest(CommandTest): ...@@ -219,12 +219,11 @@ class ListCommandUnicodeTest(CommandTest):
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
expected = utf8(u("| 1| (C) And some sp\u00e9cial tag:\u25c4\n")) expected = u("| 1| (C) And some sp\u00e9cial tag:\u25c4\n")
self.assertEqual(self.output, expected) self.assertEqual(self.output, expected)
class ListCommandJsonTest(CommandTest): class ListCommandJsonTest(CommandTest):
def test_json(self): def test_json(self):
todolist = load_file_to_todolist("test/data/ListCommandTest.txt") todolist = load_file_to_todolist("test/data/ListCommandTest.txt")
...@@ -252,7 +251,7 @@ class ListCommandJsonTest(CommandTest): ...@@ -252,7 +251,7 @@ class ListCommandJsonTest(CommandTest):
with codecs.open('test/data/ListCommandUnicodeTest.json', 'r', encoding='utf-8') as json: with codecs.open('test/data/ListCommandUnicodeTest.json', 'r', encoding='utf-8') as json:
jsontext = json.read() jsontext = json.read()
self.assertEqual(self.output, utf8(jsontext)) self.assertEqual(self.output, jsontext)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def replace_ical_tags(p_text): def replace_ical_tags(p_text):
...@@ -265,11 +264,14 @@ def replace_ical_tags(p_text): ...@@ -265,11 +264,14 @@ def replace_ical_tags(p_text):
IS_PYTHON_32 = (sys.version_info.major, sys.version_info.minor) == (3, 2) IS_PYTHON_32 = (sys.version_info.major, sys.version_info.minor) == (3, 2)
class ListCommandIcalTest(CommandTest): class ListCommandIcalTest(CommandTest):
def setUp(self):
self.maxDiff = None
@unittest.skipIf(IS_PYTHON_32, "icalendar is not supported for Python 3.2") @unittest.skipIf(IS_PYTHON_32, "icalendar is not supported for Python 3.2")
def test_ical(self): def test_ical(self):
todolist = load_file_to_todolist("test/data/ListCommandTest.txt") todolist = load_file_to_todolist("test/data/ListCommandIcalTest.txt")
command = ListCommand(["-f", "ical"], todolist, self.out, self.error) command = ListCommand(["-x", "-f", "ical"], todolist, self.out, self.error)
command.execute() command.execute()
self.assertTrue(todolist.is_dirty()) self.assertTrue(todolist.is_dirty())
...@@ -308,7 +310,7 @@ class ListCommandIcalTest(CommandTest): ...@@ -308,7 +310,7 @@ class ListCommandIcalTest(CommandTest):
with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r', encoding='utf-8') as ical: with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r', encoding='utf-8') as ical:
icaltext = ical.read() icaltext = ical.read()
self.assertEqual(replace_ical_tags(self.output), utf8(replace_ical_tags(icaltext))) self.assertEqual(replace_ical_tags(self.output), replace_ical_tags(icaltext))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -37,6 +37,7 @@ class PostponeCommandTest(CommandTest): ...@@ -37,6 +37,7 @@ class PostponeCommandTest(CommandTest):
"Baz due:{} t:{}".format(self.today.isoformat(), self.start.isoformat()), "Baz due:{} t:{}".format(self.today.isoformat(), self.start.isoformat()),
"Past due:{}".format(self.past.isoformat()), "Past due:{}".format(self.past.isoformat()),
"Future due:{} t:{}".format(self.future.isoformat(), self.future_start.isoformat()), "Future due:{} t:{}".format(self.future.isoformat(), self.future_start.isoformat()),
"FutureStart t:{}".format(self.future.isoformat())
] ]
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
...@@ -233,6 +234,55 @@ class PostponeCommandTest(CommandTest): ...@@ -233,6 +234,55 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
def test_expr_postpone1(self):
command = PostponeCommand(["-e", "due:tod", "2w"], self.todolist, self.out, self.error, None)
command.execute()
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.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_expr_postpone2(self):
cmd_args = ["-e", "t:{}".format(self.start.isoformat()), "due:tod", "1w"]
command = PostponeCommand(cmd_args, self.todolist, self.out, self.error, None)
command.execute()
due = self.today + timedelta(7)
result = "| 3| Baz due:{} t:{}\n".format(due.isoformat(), self.start.isoformat())
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_expr_postpone3(self):
command = PostponeCommand(["-e", "@test", "due:tod", "+project", "C"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_postpone4(self):
""" Don't postpone unrelevant todo items. """
command = PostponeCommand(["-e", "FutureStart", "1w"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_postpone5(self):
""" Force postponing unrelevant items with additional -x flag. """
command = PostponeCommand(["-xe", "FutureStart", "1w"], self.todolist, self.out, self.error, None)
command.execute()
due = self.today + timedelta(7)
result = "| 6| FutureStart t:{} due:{}\n".format(self.future.isoformat(), due.isoformat())
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_help(self): def test_help(self):
command = PostponeCommand(["help"], self.todolist, self.out, self.error) command = PostponeCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -27,6 +27,9 @@ class PriorityCommandTest(CommandTest): ...@@ -27,6 +27,9 @@ class PriorityCommandTest(CommandTest):
todos = [ todos = [
"(A) Foo", "(A) Foo",
"Bar", "Bar",
"(B) a @test with due:2015-06-03",
"a @test with +project p:1",
"Baz id:1",
] ]
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
...@@ -71,6 +74,58 @@ class PriorityCommandTest(CommandTest): ...@@ -71,6 +74,58 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n") self.assertEqual(self.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_prio6(self):
""" Allow priority to be set including parentheses. """
command = PriorityCommand(["Foo", "2", "(C)"], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_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_expr_prio1(self):
command = PriorityCommand(["-e", "@test", "C"], self.todolist, self.out, self.error, None)
command.execute()
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.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_expr_prio2(self):
command = PriorityCommand(["-e", "@test", "due:2015-06-03", "C"], self.todolist, self.out, self.error, None)
command.execute()
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.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_expr_prio3(self):
command = PriorityCommand(["-e", "@test", "due:2015-06-03", "+project", "C"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_prio4(self):
""" Don't prioritize unrelevant todo items. """
command = PriorityCommand(["-e", "Baz", "C"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
def test_expr_prio5(self):
""" Force prioritizing unrelevant items with additional -x flag. """
command = PriorityCommand(["-xe", "Baz", "D"], self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "Priority set to D.\n| 5| (D) Baz id:1\n")
self.assertEqual(self.errors, "")
def test_invalid1(self): def test_invalid1(self):
command = PriorityCommand(["99", "A"], self.todolist, self.out, self.error) command = PriorityCommand(["99", "A"], self.todolist, self.out, self.error)
command.execute() command.execute()
...@@ -128,6 +183,29 @@ class PriorityCommandTest(CommandTest): ...@@ -128,6 +183,29 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
def test_invalid8(self):
"""
Test that there's only one capital surrounded by non-word
characters that makes up a priority.
"""
command = PriorityCommand(["2", "(Aa)"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n")
def test_invalid9(self):
"""
Test that there's only one capital surrounded by non-word
characters that makes up a priority.
"""
command = PriorityCommand(["2", "Aa"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n")
def test_empty(self): def test_empty(self):
command = PriorityCommand([], self.todolist, self.out, self.error) command = PriorityCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -31,13 +31,13 @@ class SortCommandTest(CommandTest): ...@@ -31,13 +31,13 @@ class SortCommandTest(CommandTest):
command = SortCommand(["text"], self.todolist, self.out, self.error) command = SortCommand(["text"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(str(self.todolist), "First\n(A) Foo\n2014-06-14 Last") self.assertEqual(self.todolist.print_todos(), "First\n(A) Foo\n2014-06-14 Last")
def test_sort2(self): def test_sort2(self):
command = SortCommand([], self.todolist, self.out, self.error) command = SortCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(str(self.todolist), "(A) Foo\n2014-06-14 Last\nFirst") self.assertEqual(self.todolist.print_todos(), "(A) Foo\n2014-06-14 Last\nFirst")
def test_sort3(self): def test_sort3(self):
""" Check that order does not influence the UID of a todo. """ """ Check that order does not influence the UID of a todo. """
......
...@@ -19,7 +19,7 @@ import unittest ...@@ -19,7 +19,7 @@ import unittest
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from test.TestFacilities import load_file, todolist_to_string, load_file_to_todolist from test.TestFacilities import load_file, todolist_to_string, load_file_to_todolist, print_view
from test.TopydoTest import TopydoTest from test.TopydoTest import TopydoTest
class SorterTest(TopydoTest): class SorterTest(TopydoTest):
...@@ -128,7 +128,7 @@ class SorterTest(TopydoTest): ...@@ -128,7 +128,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, []) view = todolist.view(sorter, [])
result = load_file('test/data/SorterTest10-result.txt') result = load_file('test/data/SorterTest10-result.txt')
self.assertEqual(str(view), todolist_to_string(result)) self.assertEqual(print_view(view), todolist_to_string(result))
def test_sort15(self): def test_sort15(self):
""" """
...@@ -141,7 +141,7 @@ class SorterTest(TopydoTest): ...@@ -141,7 +141,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, []) view = todolist.view(sorter, [])
result = load_file('test/data/SorterTest11-result.txt') result = load_file('test/data/SorterTest11-result.txt')
self.assertEqual(str(view), todolist_to_string(result)) self.assertEqual(print_view(view), todolist_to_string(result))
def test_sort16(self): def test_sort16(self):
""" """
...@@ -153,7 +153,7 @@ class SorterTest(TopydoTest): ...@@ -153,7 +153,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, []) view = todolist.view(sorter, [])
result = load_file('test/data/SorterTest12-result.txt') result = load_file('test/data/SorterTest12-result.txt')
self.assertEqual(str(view), todolist_to_string(result)) self.assertEqual(print_view(view), todolist_to_string(result))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from topydo.lib.TodoFile import TodoFile from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
...@@ -42,4 +43,8 @@ def load_file_to_todolist(p_filename): ...@@ -42,4 +43,8 @@ def load_file_to_todolist(p_filename):
def todolist_to_string(p_list): def todolist_to_string(p_list):
""" Converts a todo list to a single string. """ """ Converts a todo list to a single string. """
return '\n'.join([str(t) for t in p_list]) return '\n'.join([t.source() for t in p_list])
def print_view(p_view):
printer = PrettyPrinter()
return printer.print_list(p_view.todos)
...@@ -24,6 +24,7 @@ from topydo.lib.Todo import Todo ...@@ -24,6 +24,7 @@ from topydo.lib.Todo import Todo
from topydo.lib.TodoFile import TodoFile from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from topydo.lib.TodoListBase import TodoListBase
from test.TopydoTest import TopydoTest from test.TopydoTest import TopydoTest
class TodoListTester(TopydoTest): class TodoListTester(TopydoTest):
...@@ -34,7 +35,7 @@ class TodoListTester(TopydoTest): ...@@ -34,7 +35,7 @@ class TodoListTester(TopydoTest):
lines = [line for line in self.todofile.read() \ lines = [line for line in self.todofile.read() \
if re.search(r'\S', line)] if re.search(r'\S', line)]
self.text = ''.join(lines) self.text = ''.join(lines)
self.todolist = TodoList(lines) self.todolist = TodoListBase(lines)
def test_contexts(self): def test_contexts(self):
self.assertEqual(set(['Context1', 'Context2']), \ self.assertEqual(set(['Context1', 'Context2']), \
...@@ -101,6 +102,16 @@ class TodoListTester(TopydoTest): ...@@ -101,6 +102,16 @@ class TodoListTester(TopydoTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertRaises(InvalidTodoException, self.todolist.number, todo) self.assertRaises(InvalidTodoException, self.todolist.number, todo)
def test_delete2(self):
""" Try to remove a todo item that does not exist. """
count = self.todolist.count()
todo = Todo('Not in the list')
self.todolist.delete(todo)
self.assertEqual(self.todolist.count(), count)
self.assertFalse(self.todolist.is_dirty())
def test_append1(self): def test_append1(self):
todo = self.todolist.todo(3) todo = self.todolist.todo(3)
self.todolist.append(todo, "@Context3") self.todolist.append(todo, "@Context3")
...@@ -133,22 +144,10 @@ class TodoListTester(TopydoTest): ...@@ -133,22 +144,10 @@ class TodoListTester(TopydoTest):
self.assertRaises(InvalidTodoException, self.todolist.todo, count + 100) self.assertRaises(InvalidTodoException, self.todolist.todo, count + 100)
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_string(self):
# readlines() always ends a string with \n, but join() in str(todolist)
# doesn't necessarily.
self.assertEqual(str(self.todolist) + '\n', self.text)
def test_count(self): def test_count(self):
""" Test that empty lines are not counted. """ """ Test that empty lines are not counted. """
self.assertEqual(self.todolist.count(), 5) self.assertEqual(self.todolist.count(), 5)
def test_todo_by_dep_id(self):
""" Tests that todos can be retrieved by their id tag. """
self.todolist.add("(C) Foo id:1")
self.assertTrue(self.todolist.todo_by_dep_id('1'))
self.assertFalse(self.todolist.todo_by_dep_id('2'))
def test_todo_number1(self): def test_todo_number1(self):
todo = Todo("No number") todo = Todo("No number")
self.todolist.add_todo(todo) self.todolist.add_todo(todo)
...@@ -354,6 +353,14 @@ class TodoListDependencyTester(TopydoTest): ...@@ -354,6 +353,14 @@ class TodoListDependencyTester(TopydoTest):
self.assertEqual(todo1.source(), 'Foo id:1') self.assertEqual(todo1.source(), 'Foo id:1')
self.assertEqual(todo2.source(), 'Bar p:1') self.assertEqual(todo2.source(), 'Bar p:1')
def test_todo_by_dep_id(self):
""" Tests that todos can be retrieved by their id tag. """
todolist = TodoList([])
todolist.add("(C) Foo id:1")
self.assertTrue(todolist.todo_by_dep_id('1'))
self.assertFalse(todolist.todo_by_dep_id('2'))
class TodoListCleanDependencyTester(TopydoTest): class TodoListCleanDependencyTester(TopydoTest):
def setUp(self): def setUp(self):
super(TodoListCleanDependencyTester, self).setUp() super(TodoListCleanDependencyTester, self).setUp()
......
...@@ -18,7 +18,7 @@ import unittest ...@@ -18,7 +18,7 @@ import unittest
from topydo.lib import Filter from topydo.lib import Filter
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from test.TestFacilities import load_file, todolist_to_string from test.TestFacilities import load_file, todolist_to_string, print_view
from topydo.lib.TodoFile import TodoFile from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from test.TopydoTest import TopydoTest from test.TopydoTest import TopydoTest
...@@ -34,7 +34,7 @@ class ViewTest(TopydoTest): ...@@ -34,7 +34,7 @@ class ViewTest(TopydoTest):
todofilter = Filter.GrepFilter('+Project') todofilter = Filter.GrepFilter('+Project')
view = todolist.view(sorter, [todofilter]) view = todolist.view(sorter, [todofilter])
self.assertEqual(str(view), todolist_to_string(ref)) self.assertEqual(print_view(view), todolist_to_string(ref))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
......
(C) Foo @Context2 Not@Context +Project1 Not+Project
(D) Bar @Context1 +Project2 p:1 due:2015-06-06
(C) Baz @Context1 +Project1 key:value id:1
(C) 2015-06-06 Drink beer @ home
(G) 13 + 29 = 42
(C) Only a start date t:2015-06-06
x 2015-06-06 A completed item due:2015-05-05
This diff was suppressed by a .gitattributes entry.
...@@ -5,9 +5,11 @@ default_command = ls ...@@ -5,9 +5,11 @@ default_command = ls
; filename = todo.txt ; filename = todo.txt
; archive_filename = done.txt ; archive_filename = done.txt
colors = 1 colors = 1
highlight_projects_contexts = 1
identifiers = linenumber ; or: text identifiers = linenumber ; or: text
[add]
auto_creation_date = 1
[ls] [ls]
hide_tags = id,p,ical hide_tags = id,p,ical
indent = 0 indent = 0
......
...@@ -33,10 +33,11 @@ _SUBCOMMAND_MAP = { ...@@ -33,10 +33,11 @@ _SUBCOMMAND_MAP = {
'do': 'DoCommand', 'do': 'DoCommand',
'edit': 'EditCommand', 'edit': 'EditCommand',
'exit': 'ExitCommand', # used for the prompt 'exit': 'ExitCommand', # used for the prompt
'ical': 'IcalCommand', # deprecated
'ls': 'ListCommand', 'ls': 'ListCommand',
'lscon': 'ListContextCommand', 'lscon': 'ListContextCommand',
'listcon': 'ListContextCommand', 'listcon': 'ListContextCommand',
'listcontext': 'ListContextCommand',
'listcontexts': 'ListContextCommand',
'lsprj': 'ListProjectCommand', 'lsprj': 'ListProjectCommand',
'lsproj': 'ListProjectCommand', 'lsproj': 'ListProjectCommand',
'listprj': 'ListProjectCommand', 'listprj': 'ListProjectCommand',
......
...@@ -24,16 +24,17 @@ import sys ...@@ -24,16 +24,17 @@ import sys
from six import PY2 from six import PY2
from six.moves import input from six.moves import input
MAIN_OPTS = "c:d:ht:v" MAIN_OPTS = "ac:d:ht:v"
def usage(): def usage():
""" Prints the command-line usage of topydo. """ """ Prints the command-line usage of topydo. """
print("""\ print("""\
Synopsis: topydo [-c <config>] [-d <archive>] [-t <todo.txt>] subcommand [help|args] Synopsis: topydo [-a] [-c <config>] [-d <archive>] [-t <todo.txt>] subcommand [help|args]
topydo -h topydo -h
topydo -v topydo -v
-a : Do not archive todo items on completion.
-c : Specify an alternative configuration file. -c : Specify an alternative configuration file.
-d : Specify an alternative archive file (done.txt) -d : Specify an alternative archive file (done.txt)
-h : This help text -h : This help text
...@@ -49,7 +50,6 @@ Available commands: ...@@ -49,7 +50,6 @@ Available commands:
* depri * depri
* do * do
* edit * edit
* ical
* ls * ls
* listcon (lscon) * listcon (lscon)
* listprojects (lsprj) * listprojects (lsprj)
...@@ -113,6 +113,7 @@ class CLIApplicationBase(object): ...@@ -113,6 +113,7 @@ class CLIApplicationBase(object):
def __init__(self): def __init__(self):
self.todolist = TodoList.TodoList([]) self.todolist = TodoList.TodoList([])
self.todofile = None self.todofile = None
self.do_archive = True
def _usage(self): def _usage(self):
usage() usage()
...@@ -134,7 +135,9 @@ class CLIApplicationBase(object): ...@@ -134,7 +135,9 @@ class CLIApplicationBase(object):
overrides = {} overrides = {}
for opt, value in opts: for opt, value in opts:
if opt == "-c": if opt == "-a":
self.do_archive = False
elif opt == "-c":
alt_config_path = value alt_config_path = value
elif opt == "-t": elif opt == "-t":
overrides[('topydo', 'filename')] = value overrides[('topydo', 'filename')] = value
...@@ -167,7 +170,7 @@ class CLIApplicationBase(object): ...@@ -167,7 +170,7 @@ class CLIApplicationBase(object):
command.execute() command.execute()
if archive.is_dirty(): if archive.is_dirty():
archive_file.write(str(archive)) archive_file.write(archive.print_todos())
def _help(self, args): def _help(self, args):
if args == None: if args == None:
...@@ -204,13 +207,17 @@ class CLIApplicationBase(object): ...@@ -204,13 +207,17 @@ class CLIApplicationBase(object):
completed. It will do some maintenance and write out the final result completed. It will do some maintenance and write out the final result
to the todo.txt file. 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.is_dirty():
self._archive() if self.do_archive and config().archive():
self._archive()
if config().keep_sorted(): if config().keep_sorted():
self._execute(SortCommand, []) self._execute(SortCommand, [])
self.todofile.write(str(self.todolist)) self.todofile.write(self.todolist.print_todos())
def run(self): def run(self):
raise NotImplementedError raise NotImplementedError
......
...@@ -87,7 +87,7 @@ class TopydoCompleter(Completer): ...@@ -87,7 +87,7 @@ class TopydoCompleter(Completer):
yield Completion(reldate, -len(value), display_meta=to_absolute(reldate)) yield Completion(reldate, -len(value), display_meta=to_absolute(reldate))
def get_completions(self, p_document, p_complete_event): def get_completions(self, p_document, _):
# include all characters except whitespaces (for + and @) # include all characters except whitespaces (for + and @)
word_before_cursor = p_document.get_word_before_cursor(True) word_before_cursor = p_document.get_word_before_cursor(True)
is_first_word = not re.match(r'\s*\S+\s', p_document.current_line_before_cursor) is_first_word = not re.match(r'\s*\S+\s', p_document.current_line_before_cursor)
......
...@@ -27,7 +27,7 @@ def main(): ...@@ -27,7 +27,7 @@ def main():
args = sys.argv[1:] args = sys.argv[1:]
try: try:
opts, args = getopt.getopt(args, MAIN_OPTS) _, args = getopt.getopt(args, MAIN_OPTS)
except getopt.GetoptError as e: except getopt.GetoptError as e:
error(str(e)) error(str(e))
sys.exit(1) sys.exit(1)
......
...@@ -47,7 +47,6 @@ class AddCommand(Command): ...@@ -47,7 +47,6 @@ class AddCommand(Command):
self.args = args self.args = args
def get_todos_from_file(self): def get_todos_from_file(self):
if self.from_file == '-': if self.from_file == '-':
f = stdin f = stdin
...@@ -107,7 +106,8 @@ class AddCommand(Command): ...@@ -107,7 +106,8 @@ class AddCommand(Command):
add_dependencies('before') add_dependencies('before')
add_dependencies('after') add_dependencies('after')
p_todo.set_creation_date(date.today()) if config().auto_creation_date():
p_todo.set_creation_date(date.today())
todo_text = _preprocess_input_todo(p_todo_text) todo_text = _preprocess_input_todo(p_todo_text)
todo = self.todolist.add(todo_text) todo = self.todolist.add(todo_text)
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.commands.DCommand import DCommand from topydo.lib.DCommand import DCommand
class DeleteCommand(DCommand): class DeleteCommand(DCommand):
def __init__(self, p_args, p_todolist, def __init__(self, p_args, p_todolist,
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import Filter from topydo.lib import Filter
from topydo.lib.PrettyPrinter import pretty_printer_factory
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.View import View from topydo.lib.View import View
...@@ -34,6 +35,8 @@ class DepCommand(Command): ...@@ -34,6 +35,8 @@ class DepCommand(Command):
except InvalidCommandArgument: except InvalidCommandArgument:
self.subsubcommand = None self.subsubcommand = None
self.printer = pretty_printer_factory(self.todolist)
def _handle_add(self): def _handle_add(self):
(from_todo, to_todo) = self._get_todos() (from_todo, to_todo) = self._get_todos()
...@@ -97,9 +100,8 @@ class DepCommand(Command): ...@@ -97,9 +100,8 @@ class DepCommand(Command):
if todos: if todos:
sorter = Sorter(config().sort_string()) sorter = Sorter(config().sort_string())
instance_filter = Filter.InstanceFilter(todos) instance_filter = Filter.InstanceFilter(todos)
view = View(sorter, [instance_filter], self.todolist, view = View(sorter, [instance_filter], self.todolist)
self.printer) self.out(self.printer.print_list(view.todos))
self.out(view.pretty_print())
except InvalidTodoException: except InvalidTodoException:
self.error("Invalid todo number given.") self.error("Invalid todo number given.")
except InvalidCommandArgument: except InvalidCommandArgument:
......
...@@ -25,20 +25,14 @@ class DepriCommand(MultiCommand): ...@@ -25,20 +25,14 @@ class DepriCommand(MultiCommand):
super(DepriCommand, self).__init__( super(DepriCommand, self).__init__(
p_args, p_todolist, p_out, p_err, p_prompt) p_args, p_todolist, p_out, p_err, p_prompt)
self.get_todos(self.args) def _execute_multi_specific(self):
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
def execute_multi_specific(self):
try: for todo in self.todos:
self.printer.add_filter(PrettyPrinterNumbers(self.todolist)) if todo.priority() != None:
self.todolist.set_priority(todo, None)
for todo in self.todos: self.out("Priority removed.")
if todo.priority() != None: self.out(self.printer.print_todo(todo))
self.todolist.set_priority(todo, None)
self.out("Priority removed.")
self.out(self.printer.print_todo(todo))
except IndexError:
self.error(self.usage())
def usage(self): def usage(self):
return """Synopsis: depri <NUMBER1> [<NUMBER2> ...]""" return """Synopsis: depri <NUMBER1> [<NUMBER2> ...]"""
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
from datetime import date from datetime import date
from topydo.commands.DCommand import DCommand from topydo.lib.DCommand import DCommand
from topydo.lib.PrettyPrinter import PrettyPrinter from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.Recurrence import advance_recurring_todo, strict_advance_recurring_todo, NoRecurrenceException from topydo.lib.Recurrence import advance_recurring_todo, strict_advance_recurring_todo, NoRecurrenceException
...@@ -36,9 +36,13 @@ class DoCommand(DCommand): ...@@ -36,9 +36,13 @@ class DoCommand(DCommand):
def get_flags(self): def get_flags(self):
""" Additional flags. """ """ Additional flags. """
return ("d:s", ["date=", "strict"]) opts, long_opts = super(DoCommand, self).get_flags()
return ("d:s" + opts, ["date=", "strict"] + long_opts)
def process_flag(self, p_opt, p_value): def process_flag(self, p_opt, p_value):
super(DoCommand, self).process_flag(p_opt, p_value)
if p_opt == "-s" or p_opt == "--strict": if p_opt == "-s" or p_opt == "--strict":
self.strict_recurrence = True self.strict_recurrence = True
elif p_opt == "-d" or p_opt == "--date": elif p_opt == "-d" or p_opt == "--date":
......
...@@ -18,7 +18,7 @@ import os ...@@ -18,7 +18,7 @@ import os
from subprocess import call, check_call, CalledProcessError from subprocess import call, check_call, CalledProcessError
import tempfile import tempfile
from six import text_type, u from six import u
from topydo.lib.ExpressionCommand import ExpressionCommand from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.MultiCommand import MultiCommand from topydo.lib.MultiCommand import MultiCommand
...@@ -34,31 +34,30 @@ DEFAULT_EDITOR = 'vi' ...@@ -34,31 +34,30 @@ DEFAULT_EDITOR = 'vi'
# cannot use super() inside the class itself # cannot use super() inside the class itself
BASE_TODOLIST = lambda tl: super(TodoList, tl) BASE_TODOLIST = lambda tl: super(TodoList, tl)
class EditCommand(MultiCommand, ExpressionCommand): class EditCommand(MultiCommand):
def __init__(self, p_args, p_todolist, p_output, p_error, p_input): def __init__(self, p_args, p_todolist, p_output, p_error, p_input):
super(EditCommand, self).__init__(p_args, p_todolist, p_output, super(EditCommand, self).__init__(p_args, p_todolist, p_output,
p_error, p_input) p_error, p_input)
if len(self.args) == 0:
self.multi_mode = False
self.is_expression = False self.is_expression = False
self.edit_archive = False self.edit_archive = False
self.last_argument = False
def _process_flags(self): def get_flags(self):
opts, args = self.getopt('xed') return ("d", [])
for opt, value in opts:
if opt == '-d':
self.edit_archive = True
elif opt == '-x':
self.show_all = True
elif opt == '-e':
self.is_expression = True
self.args = args def process_flag(self, p_opt, p_value):
if p_opt == '-d':
self.edit_archive = True
self.multi_mode = False
def _todos_to_temp(self): def _todos_to_temp(self):
f = tempfile.NamedTemporaryFile() f = tempfile.NamedTemporaryFile()
for todo in self.todos: for todo in self.todos:
f.write((text_type(todo) + "\n").encode('utf-8')) f.write((todo.source() + "\n").encode('utf-8'))
f.seek(0) f.seek(0)
return f return f
...@@ -73,12 +72,20 @@ class EditCommand(MultiCommand, ExpressionCommand): ...@@ -73,12 +72,20 @@ class EditCommand(MultiCommand, ExpressionCommand):
return todo_objs return todo_objs
def _open_in_editor(self, p_temp_file, p_editor): def _open_in_editor(self, p_file):
try:
editor = os.environ['EDITOR'] or DEFAULT_EDITOR
except(KeyError):
editor = DEFAULT_EDITOR
try: try:
return check_call([p_editor, p_temp_file.name]) return check_call([editor, p_file])
except CalledProcessError: except CalledProcessError:
self.error('Something went wrong in the editor...') self.error('Something went wrong in the editor...')
return 1 return 1
except(OSError):
self.error('There is no such editor as: ' + editor + '. '
'Check your $EDITOR and/or $PATH')
def _catch_todo_errors(self): def _catch_todo_errors(self):
errors = [] errors = []
...@@ -94,59 +101,35 @@ class EditCommand(MultiCommand, ExpressionCommand): ...@@ -94,59 +101,35 @@ class EditCommand(MultiCommand, ExpressionCommand):
else: else:
return None return None
def execute(self): def _execute_multi_specific(self):
if not super(EditCommand, self).execute():
return False
self.printer.add_filter(PrettyPrinterNumbers(self.todolist)) self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
try:
editor = os.environ['EDITOR'] or DEFAULT_EDITOR
except(KeyError):
editor = DEFAULT_EDITOR
try: temp_todos = self._todos_to_temp()
if len(self.args) < 1:
todo = config().todotxt()
return call([editor, todo]) == 0 if not self._open_in_editor(temp_todos.name):
new_todos = self._todos_from_temp(temp_todos)
if len(new_todos) == len(self.todos):
for todo in self.todos:
BASE_TODOLIST(self.todolist).delete(todo)
for todo in new_todos:
self.todolist.add_todo(todo)
self.out(self.printer.print_todo(todo))
else: else:
self._process_flags() self.error('Number of edited todos is not equal to '
'number of supplied todo IDs.')
if self.edit_archive: else:
archive = config().archive() self.error(self.usage())
return call([editor, archive]) == 0 def _execute_not_multi(self):
if self.edit_archive:
if self.is_expression: archive = config().archive()
self.todos = self._view()._viewdata
else: return self._open_in_editor(archive) == 0
self.get_todos(self.args) else:
todo = config().todotxt()
todo_errors = self._catch_todo_errors()
return self._open_in_editor(todo) == 0
if not todo_errors:
temp_todos = self._todos_to_temp()
if not self._open_in_editor(temp_todos, editor):
new_todos = self._todos_from_temp(temp_todos)
if len(new_todos) == len(self.todos):
for todo in self.todos:
BASE_TODOLIST(self.todolist).delete(todo)
for todo in new_todos:
self.todolist.add_todo(todo)
self.out(self.printer.print_todo(todo))
else:
self.error('Number of edited todos is not equal to '
'number of supplied todo IDs.')
else:
self.error(self.usage())
else:
for error in todo_errors:
self.error(error)
except(OSError):
self.error('There is no such editor as: ' + editor + '. '
'Check your $EDITOR and/or $PATH')
def usage(self): def usage(self):
return """Synopsis: return """Synopsis:
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
from topydo.lib.ExpressionCommand import ExpressionCommand from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.PrettyPrinter import pretty_printer_factory
from topydo.lib.PrettyPrinterFilter import ( from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterIndentFilter, PrettyPrinterIndentFilter,
PrettyPrinterHideTagFilter PrettyPrinterHideTagFilter
...@@ -45,7 +46,7 @@ class ListCommand(ExpressionCommand): ...@@ -45,7 +46,7 @@ class ListCommand(ExpressionCommand):
""" """
try: try:
import icalendar as _ import icalendar as _
except ImportError: except ImportError: # pragma: no cover
self.error("icalendar package is not installed.") self.error("icalendar package is not installed.")
return False return False
...@@ -81,10 +82,8 @@ class ListCommand(ExpressionCommand): ...@@ -81,10 +82,8 @@ class ListCommand(ExpressionCommand):
sent to the output. sent to the output.
""" """
def _print_text(): if self.printer == None:
""" # create a standard printer with some filters
Outputs a pretty-printed text format of the todo list.
"""
indent = config().list_indent() indent = config().list_indent()
hidden_tags = config().hidden_tags() hidden_tags = config().hidden_tags()
...@@ -92,14 +91,9 @@ class ListCommand(ExpressionCommand): ...@@ -92,14 +91,9 @@ class ListCommand(ExpressionCommand):
filters.append(PrettyPrinterIndentFilter(indent)) filters.append(PrettyPrinterIndentFilter(indent))
filters.append(PrettyPrinterHideTagFilter(hidden_tags)) filters.append(PrettyPrinterHideTagFilter(hidden_tags))
self.out(self._view().pretty_print(filters)) self.printer = pretty_printer_factory(self.todolist, filters)
if self.printer == None: self.out(self.printer.print_list(self._view().todos))
_print_text()
else:
# we have set a special format, simply use the printer set in
# self.printer
self.out(str(self._view()))
def execute(self): def execute(self):
if not super(ListCommand, self).execute(): if not super(ListCommand, self).execute():
...@@ -107,7 +101,7 @@ class ListCommand(ExpressionCommand): ...@@ -107,7 +101,7 @@ class ListCommand(ExpressionCommand):
try: try:
self._process_flags() self._process_flags()
except SyntaxError: except SyntaxError: # pragma: no cover
# importing icalendar failed, most likely due to Python 3.2 # importing icalendar failed, most likely due to Python 3.2
self.error("icalendar is not supported in this Python version.") self.error("icalendar is not supported in this Python version.")
return False return False
......
...@@ -17,11 +17,9 @@ ...@@ -17,11 +17,9 @@
from datetime import date, timedelta from datetime import date, timedelta
from topydo.lib.MultiCommand import MultiCommand from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.Command import InvalidCommandArgument
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.RelativeDate import relative_date_to_date from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import date_string_to_date from topydo.lib.Utils import date_string_to_date
class PostponeCommand(MultiCommand): class PostponeCommand(MultiCommand):
...@@ -33,19 +31,16 @@ class PostponeCommand(MultiCommand): ...@@ -33,19 +31,16 @@ class PostponeCommand(MultiCommand):
p_args, p_todolist, p_out, p_err, p_prompt) p_args, p_todolist, p_out, p_err, p_prompt)
self.move_start_date = False self.move_start_date = False
self._process_flags() self.last_argument = True
self.get_todos(self.args[:-1])
def _process_flags(self): def get_flags(self):
opts, args = self.getopt('s') return("s", [])
for opt, _ in opts: def process_flag(self, p_opt, p_value):
if opt == '-s': if p_opt == '-s':
self.move_start_date = True self.move_start_date = True
self.args = args def _execute_multi_specific(self):
def execute_multi_specific(self):
def _get_offset(p_todo): def _get_offset(p_todo):
offset = p_todo.tag_value( offset = p_todo.tag_value(
config().tag_due(), date.today().isoformat()) config().tag_due(), date.today().isoformat())
...@@ -56,31 +51,28 @@ class PostponeCommand(MultiCommand): ...@@ -56,31 +51,28 @@ class PostponeCommand(MultiCommand):
return offset_date return offset_date
try: pattern = self.args[-1]
pattern = self.args[-1] self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
for todo in self.todos:
offset = _get_offset(todo)
new_due = relative_date_to_date(pattern, offset)
if new_due: for todo in self.todos:
if self.move_start_date and todo.has_tag(config().tag_start()): offset = _get_offset(todo)
length = todo.length() new_due = relative_date_to_date(pattern, offset)
new_start = new_due - timedelta(length)
# pylint: disable=E1103
todo.set_tag(config().tag_start(), new_start.isoformat())
if new_due:
if self.move_start_date and todo.has_tag(config().tag_start()):
length = todo.length()
new_start = new_due - timedelta(length)
# pylint: disable=E1103 # pylint: disable=E1103
todo.set_tag(config().tag_due(), new_due.isoformat()) todo.set_tag(config().tag_start(), new_start.isoformat())
self.todolist.set_dirty() # pylint: disable=E1103
self.out(self.printer.print_todo(todo)) todo.set_tag(config().tag_due(), new_due.isoformat())
else:
self.error("Invalid date pattern given.") self.todolist.set_dirty()
break self.out(self.printer.print_todo(todo))
except (InvalidCommandArgument, IndexError): else:
self.error(self.usage()) self.error("Invalid date pattern given.")
break
def usage(self): def usage(self):
return "Synopsis: postpone [-s] <NUMBER> [<NUMBER2> ...] <PATTERN>" return "Synopsis: postpone [-s] <NUMBER> [<NUMBER2> ...] <PATTERN>"
......
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
from topydo.lib.MultiCommand import MultiCommand from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import is_valid_priority from topydo.lib.Utils import is_valid_priority
class PriorityCommand(MultiCommand): class PriorityCommand(MultiCommand):
...@@ -27,31 +28,30 @@ class PriorityCommand(MultiCommand): ...@@ -27,31 +28,30 @@ class PriorityCommand(MultiCommand):
super(PriorityCommand, self).__init__( super(PriorityCommand, self).__init__(
p_args, p_todolist, p_out, p_err, p_prompt) p_args, p_todolist, p_out, p_err, p_prompt)
self.get_todos(self.args[:-1]) self.last_argument = True
def execute_multi_specific(self): def _execute_multi_specific(self):
priority = None def normalize_priority(p_priority):
match = re.search(r'\b([A-Z])\b', p_priority)
return match.group(1) if match else p_priority
try: priority = normalize_priority(self.args[-1])
priority = self.args[-1] self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
if is_valid_priority(priority): if is_valid_priority(priority):
for todo in self.todos: for todo in self.todos:
old_priority = todo.priority() old_priority = todo.priority()
self.todolist.set_priority(todo, priority) self.todolist.set_priority(todo, priority)
if old_priority and priority and old_priority != priority: if old_priority and priority and old_priority != priority:
self.out("Priority changed from {} to {}".format( self.out("Priority changed from {} to {}".format(
old_priority, priority)) old_priority, priority))
elif not old_priority: elif not old_priority:
self.out("Priority set to {}.".format(priority)) self.out("Priority set to {}.".format(priority))
self.out(self.printer.print_todo(todo)) self.out(self.printer.print_todo(todo))
else: else:
self.error("Invalid priority given.") self.error("Invalid priority given.")
except IndexError:
self.error(self.usage())
def usage(self): def usage(self):
return """Synopsis: pri <NUMBER1> [<NUMBER2> ...] <PRIORITY>""" return """Synopsis: pri <NUMBER1> [<NUMBER2> ...] <PRIORITY>"""
......
...@@ -39,9 +39,7 @@ class SortCommand(Command): ...@@ -39,9 +39,7 @@ class SortCommand(Command):
sorted_todos = sorter.sort(self.todolist.todos()) sorted_todos = sorter.sort(self.todolist.todos())
self.todolist.erase() self.todolist.erase()
self.todolist.add_todos(sorted_todos)
for todo in sorted_todos:
self.todolist.add_todo(todo)
def usage(self): def usage(self):
return """Synopsis: sort [expression]""" return """Synopsis: sort [expression]"""
......
...@@ -84,8 +84,10 @@ class Command(object): ...@@ -84,8 +84,10 @@ class Command(object):
return result return result
def usage(self): def usage(self):
return "No usage text available for this command." """ Returns a one-line synopsis for this command. """
raise NotImplementedError
def help(self): def help(self):
return "No help text available for this command." """ Returns the help text for this command. """
raise NotImplementedError
...@@ -38,17 +38,27 @@ class _Config: ...@@ -38,17 +38,27 @@ class _Config:
(such as todo.txt location passed with -t). The key is a tuple of (such as todo.txt location passed with -t). The key is a tuple of
(section, option), the value is the option's value. (section, option), the value is the option's value.
""" """
self.sections = ['topydo', 'tags', 'sort', 'ls', 'dep', 'colorscheme'] self.sections = [
'add',
'colorscheme',
'dep',
'ls',
'sort',
'tags',
'topydo',
]
self.defaults = { self.defaults = {
# topydo # topydo
'default_command': 'ls', 'default_command': 'ls',
'colors': '1', 'colors': '1',
'highlight_projects_contexts': '1',
'filename' : 'todo.txt', 'filename' : 'todo.txt',
'archive_filename' : 'done.txt', 'archive_filename' : 'done.txt',
'identifiers': 'linenumber', 'identifiers': 'linenumber',
# add
'auto_creation_date': '1',
# ls # ls
'hide_tags': 'id,p,ical', 'hide_tags': 'id,p,ical',
'indent': 0, 'indent': 0,
...@@ -118,12 +128,6 @@ class _Config: ...@@ -118,12 +128,6 @@ class _Config:
except ValueError: except ValueError:
return self.defaults['colors'] == '1' return self.defaults['colors'] == '1'
def highlight_projects_contexts(self):
try:
return self.cp.getboolean('topydo', 'highlight_projects_contexts')
except ValueError:
return self.defaults['highlight_projects_contexts'] == '1'
def todotxt(self): def todotxt(self):
return os.path.expanduser(self.cp.get('topydo', 'filename')) return os.path.expanduser(self.cp.get('topydo', 'filename'))
...@@ -240,6 +244,12 @@ class _Config: ...@@ -240,6 +244,12 @@ class _Config:
except ValueError: except ValueError:
return int(self.defaults['link_color']) return int(self.defaults['link_color'])
def auto_creation_date(self):
try:
return self.cp.getboolean('add', 'auto_creation_date')
except ValueError:
return self.defaults['auto_creation_date'] == '1'
def config(p_path=None, p_overrides=None): def config(p_path=None, p_overrides=None):
""" """
Retrieve the config instance. Retrieve the config instance.
......
...@@ -19,7 +19,6 @@ import re ...@@ -19,7 +19,6 @@ import re
from topydo.lib.MultiCommand import MultiCommand from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinter import PrettyPrinter from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException
class DCommand(MultiCommand): class DCommand(MultiCommand):
""" """
...@@ -36,29 +35,14 @@ class DCommand(MultiCommand): ...@@ -36,29 +35,14 @@ class DCommand(MultiCommand):
self.force = False self.force = False
self.process_flags()
self.length = len(self.todolist.todos()) # to determine newly activated todos self.length = len(self.todolist.todos()) # to determine newly activated todos
self.get_todos(self.args)
def get_flags(self): def get_flags(self):
""" Default implementation of getting specific flags. """ return ("f", ["force"])
return ("", [])
def process_flag(self, p_option, p_value): def process_flag(self, p_opt, p_value):
""" Default implementation of processing specific flags. """ if p_opt == "-f" or p_opt == "--force":
pass self.force = True
def process_flags(self):
opts, args = self.get_flags()
opts, args = self.getopt("f" + opts, ["force"] + args)
for opt, value in opts:
if opt == "-f" or opt == "--force":
self.force = True
else:
self.process_flag(opt, value)
self.args = args
def _uncompleted_children(self, p_todo): def _uncompleted_children(self, p_todo):
return sorted( return sorted(
...@@ -72,11 +56,10 @@ class DCommand(MultiCommand): ...@@ -72,11 +56,10 @@ class DCommand(MultiCommand):
self.out(printer.print_list(p_todos)) self.out(printer.print_list(p_todos))
def prompt_text(self): def prompt_text(self):
return "Yes or no? [y/N] " raise NotImplementedError
def prefix(self): def prefix(self):
""" Prefix to use when printing a todo. """ raise NotImplementedError
return ""
def _process_subtasks(self, p_todo): def _process_subtasks(self, p_todo):
children = self._uncompleted_children(p_todo) children = self._uncompleted_children(p_todo)
...@@ -110,26 +93,26 @@ class DCommand(MultiCommand): ...@@ -110,26 +93,26 @@ class DCommand(MultiCommand):
return [todo for todo in self.todolist.todos()[:self.length] return [todo for todo in self.todolist.todos()[:self.length]
if not self._uncompleted_children(todo) and todo.is_active()] if not self._uncompleted_children(todo) and todo.is_active()]
def condition(self, p_todo): def condition(self, _):
""" """
An additional condition whether execute_specific should be executed. An additional condition whether execute_specific should be executed.
""" """
return True return True
def condition_failed_text(self): def condition_failed_text(self):
return "" raise NotImplementedError
def execute_specific(self, _): def execute_specific(self, _):
pass raise NotImplementedError
def execute_specific_core(self, p_todo): def execute_specific_core(self, p_todo):
""" """
The core operation on the todo itself. Also used to operate on The core operation on the todo itself. Also used to operate on
child/parent tasks. child/parent tasks.
""" """
pass raise NotImplementedError
def execute_multi_specific(self): def _execute_multi_specific(self):
old_active = self._active_todos() old_active = self._active_todos()
for todo in self.todos: for todo in self.todos:
......
...@@ -35,13 +35,22 @@ class ExpressionCommand(Command): ...@@ -35,13 +35,22 @@ class ExpressionCommand(Command):
self.sort_expression = config().sort_string() self.sort_expression = config().sort_string()
self.show_all = False self.show_all = False
# Commands using last argument differently (i.e as something other than
# todo ID/expression) have to set attribute below to True.
self.last_argument = False
def _filters(self): def _filters(self):
filters = [] filters = []
def arg_filters(): def arg_filters():
result = [] result = []
for arg in self.args:
if self.last_argument:
args = self.args[:-1]
else:
args = self.args
for arg in args:
if re.match(Filter.ORDINAL_TAG_MATCH, arg): if re.match(Filter.ORDINAL_TAG_MATCH, arg):
argfilter = Filter.OrdinalTagFilter(arg) argfilter = Filter.OrdinalTagFilter(arg)
elif len(arg) > 1 and arg[0] == '-': elif len(arg) > 1 and arg[0] == '-':
...@@ -70,4 +79,4 @@ class ExpressionCommand(Command): ...@@ -70,4 +79,4 @@ class ExpressionCommand(Command):
sorter = Sorter(self.sort_expression) sorter = Sorter(self.sort_expression)
filters = self._filters() filters = self._filters()
return View(sorter, filters, self.todolist, self.printer) return View(sorter, filters, self.todolist)
...@@ -15,9 +15,7 @@ ...@@ -15,9 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re import re
from six import text_type
from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.Utils import date_string_to_date from topydo.lib.Utils import date_string_to_date
...@@ -30,9 +28,8 @@ class Filter(object): ...@@ -30,9 +28,8 @@ class Filter(object):
return [t for t in p_todos if self.match(t)] return [t for t in p_todos if self.match(t)]
def match(self, p_todo): def match(self, _):
""" Default match value. """ raise NotImplementedError
return True
class NegationFilter(Filter): class NegationFilter(Filter):
def __init__(self, p_filter): def __init__(self, p_filter):
...@@ -64,7 +61,7 @@ class GrepFilter(Filter): ...@@ -64,7 +61,7 @@ class GrepFilter(Filter):
super(GrepFilter, self).__init__() super(GrepFilter, self).__init__()
# convert to string in case we receive integers # convert to string in case we receive integers
self.expression = text_type(p_expression) self.expression = p_expression
if p_case_sensitive != None: if p_case_sensitive != None:
self.case_sensitive = p_case_sensitive self.case_sensitive = p_case_sensitive
......
...@@ -69,15 +69,12 @@ class IcalPrinter(Printer): ...@@ -69,15 +69,12 @@ class IcalPrinter(Printer):
try: try:
import icalendar import icalendar
self.icalendar = icalendar self.icalendar = icalendar
except (SyntaxError, ImportError): except (SyntaxError, ImportError): # pragma: no cover
# icalendar does not support Python 3.2 resulting in a SyntaxError. Since # icalendar does not support Python 3.2 resulting in a SyntaxError. Since
# this is an optional dependency, dropping Python 3.2 support altogether is # this is an optional dependency, dropping Python 3.2 support altogether is
# too much. Therefore just disable the iCalendar functionality # too much. Therefore just disable the iCalendar functionality
self.icalendar = None self.icalendar = None
def print_todo(self, p_todo):
return self._convert_todo(p_todo).to_ical() if self.icalendar else ""
def print_list(self, p_todos): def print_list(self, p_todos):
result = "" result = ""
......
...@@ -16,10 +16,10 @@ ...@@ -16,10 +16,10 @@
from six import u from six import u
from topydo.lib.Command import Command from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
class MultiCommand(Command): class MultiCommand(ExpressionCommand):
""" """
A common class for operations that can work with multiple todo IDs. A common class for operations that can work with multiple todo IDs.
""" """
...@@ -33,14 +33,49 @@ class MultiCommand(Command): ...@@ -33,14 +33,49 @@ class MultiCommand(Command):
self.todos = [] self.todos = []
self.invalid_numbers = [] self.invalid_numbers = []
self.is_expression = False
self.multi_mode = True
def get_todos(self, p_numbers): def get_flags(self):
""" Default implementation of getting specific flags. """
return ("", [])
def process_flag(self, p_option, p_value):
""" Default implementation of processing specific flags. """
pass
def _process_flags(self):
opts, long_opts = self.get_flags()
opts, args = self.getopt("xe" + opts, long_opts)
for opt, value in opts:
if opt == '-x':
self.show_all = True
elif opt == '-e':
self.is_expression = True
else:
self.process_flag(opt, value)
self.args = args
def get_todos_from_expr(self):
self.todos = self._view().todos
def get_todos(self):
""" Gets todo objects from supplied todo IDs """ """ Gets todo objects from supplied todo IDs """
for number in p_numbers: if self.is_expression:
try: self.get_todos_from_expr()
self.todos.append(self.todolist.todo(number)) else:
except InvalidTodoException: if self.last_argument:
self.invalid_numbers.append(number) numbers = self.args[:-1]
else:
numbers = self.args
for number in numbers:
try:
self.todos.append(self.todolist.todo(number))
except InvalidTodoException:
self.invalid_numbers.append(number)
def _catch_todo_errors(self): def _catch_todo_errors(self):
""" """
...@@ -65,23 +100,36 @@ class MultiCommand(Command): ...@@ -65,23 +100,36 @@ class MultiCommand(Command):
else: else:
return None return None
def execute_multi_specific(self): def _execute_multi_specific(self):
""" """
Operations specific for particular command dealing with multiple todo Operations specific for particular command dealing with multiple todo
IDs. IDs.
""" """
pass pass
def _execute_not_multi(self):
"""
Some commands can do something else besides operating on multiple todo
IDs. This method is a wrapper for those other operations.
"""
pass
def execute(self): def execute(self):
if not super(MultiCommand, self).execute(): if not super(MultiCommand, self).execute():
return False return False
todo_errors = self._catch_todo_errors() self._process_flags()
if not todo_errors: if not self.multi_mode:
self.execute_multi_specific() self._execute_not_multi()
else: else:
for error in todo_errors: self.get_todos()
self.error(error) todo_errors = self._catch_todo_errors()
if not todo_errors:
self._execute_multi_specific()
else:
for error in todo_errors:
self.error(error)
return True return True
...@@ -14,6 +14,11 @@ ...@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterColorFilter,
PrettyPrinterNumbers
)
class Printer(object): class Printer(object):
""" """
An abstract class that turns todo items into strings. An abstract class that turns todo items into strings.
...@@ -21,8 +26,7 @@ class Printer(object): ...@@ -21,8 +26,7 @@ class Printer(object):
Subclasses must at least implement the print_todo method. Subclasses must at least implement the print_todo method.
""" """
def print_todo(self, p_todo): def print_todo(self, p_todo):
""" Base implementation. Simply returns the string conversion. """ raise NotImplementedError
return str(p_todo)
def print_list(self, p_todos): def print_list(self, p_todos):
""" """
...@@ -57,9 +61,26 @@ class PrettyPrinter(Printer): ...@@ -57,9 +61,26 @@ class PrettyPrinter(Printer):
def print_todo(self, p_todo): def print_todo(self, p_todo):
""" Given a todo item, pretty print it. """ """ Given a todo item, pretty print it. """
todo_str = str(p_todo) todo_str = p_todo.source()
for ppf in self.filters: for ppf in self.filters:
todo_str = ppf.filter(todo_str, p_todo) todo_str = ppf.filter(todo_str, p_todo)
return todo_str return todo_str
def pretty_printer_factory(p_todolist, p_additional_filters=None):
""" Returns a pretty printer suitable for the ls and dep subcommands. """
p_additional_filters = p_additional_filters or []
printer = PrettyPrinter()
printer.add_filter(PrettyPrinterNumbers(p_todolist))
for ppf in p_additional_filters:
printer.add_filter(ppf)
# apply colors at the last step, the ANSI codes may confuse the
# preceding filters.
printer.add_filter(PrettyPrinterColorFilter())
return printer
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
""" Provides filters used for pretty printing. """ """ Provides filters used for pretty printing. """
import re import re
from six import u
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.Colors import Colors, NEUTRAL_COLOR from topydo.lib.Colors import Colors, NEUTRAL_COLOR
...@@ -29,8 +30,10 @@ class PrettyPrinterFilter(object): ...@@ -29,8 +30,10 @@ class PrettyPrinterFilter(object):
""" """
def filter(self, p_todo_str, _): def filter(self, p_todo_str, _):
""" Default implementation returns an unmodified todo string. """ """
return p_todo_str Applies a filter to p_todo_str and returns a modified version of it.
"""
raise NotImplementedError
class PrettyPrinterColorFilter(PrettyPrinterFilter): class PrettyPrinterColorFilter(PrettyPrinterFilter):
""" """
...@@ -44,10 +47,10 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter): ...@@ -44,10 +47,10 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
colorscheme = Colors() colorscheme = Colors()
priority_colors = colorscheme.get_priority_colors() priority_colors = colorscheme.get_priority_colors()
project_color = colorscheme.get_project_color() project_color = colorscheme.get_project_color()
context_color = colorscheme.get_context_color() context_color = colorscheme.get_context_color()
metadata_color = colorscheme.get_metadata_color() metadata_color = colorscheme.get_metadata_color()
link_color = colorscheme.get_link_color() link_color = colorscheme.get_link_color()
if config().colors(): if config().colors():
color = NEUTRAL_COLOR color = NEUTRAL_COLOR
...@@ -56,14 +59,16 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter): ...@@ -56,14 +59,16 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
except KeyError: except KeyError:
pass pass
# color by priority
p_todo_str = color + p_todo_str p_todo_str = color + p_todo_str
if config().highlight_projects_contexts():
p_todo_str = re.sub( # color projects / contexts
r'\B(\+|@)(\S*\w)', p_todo_str = re.sub(
lambda m: ( r'\B(\+|@)(\S*\w)',
context_color if m.group(0)[0] == "@" lambda m: (
else project_color) + m.group(0) + color, context_color if m.group(0)[0] == "@"
p_todo_str) else project_color) + m.group(0) + color,
p_todo_str)
# tags # tags
p_todo_str = re.sub(r'\b\S+:[^/\s]\S*\b', p_todo_str = re.sub(r'\b\S+:[^/\s]\S*\b',
...@@ -97,7 +102,7 @@ class PrettyPrinterNumbers(PrettyPrinterFilter): ...@@ -97,7 +102,7 @@ class PrettyPrinterNumbers(PrettyPrinterFilter):
def filter(self, p_todo_str, p_todo): def filter(self, p_todo_str, p_todo):
""" Prepends the number to the todo string. """ """ Prepends the number to the todo string. """
return "|{:>3}| {}".format(self.todolist.number(p_todo), p_todo_str) return u("|{:>3}| {}").format(self.todolist.number(p_todo), p_todo_str)
class PrettyPrinterHideTagFilter(PrettyPrinterFilter): class PrettyPrinterHideTagFilter(PrettyPrinterFilter):
""" Removes all occurences of the given tags from the text. """ """ Removes all occurences of the given tags from the text. """
......
...@@ -37,8 +37,8 @@ def _add_months(p_sourcedate, p_months): ...@@ -37,8 +37,8 @@ def _add_months(p_sourcedate, p_months):
def _convert_pattern(p_length, p_periodunit, p_offset=None): def _convert_pattern(p_length, p_periodunit, p_offset=None):
""" """
Converts a pattern in the form [0-9][dwmy] and returns a date from today Converts a pattern in the form [0-9][dwmy] and returns a date from the
with the period of time added to it. offset with the period of time added to it.
""" """
result = None result = None
......
...@@ -20,12 +20,11 @@ This module contains the class that represents a single todo item. ...@@ -20,12 +20,11 @@ This module contains the class that represents a single todo item.
from datetime import date from datetime import date
import re import re
from six import python_2_unicode_compatible, u from six import u
from topydo.lib.TodoParser import parse_line from topydo.lib.TodoParser import parse_line
from topydo.lib.Utils import is_valid_priority from topydo.lib.Utils import is_valid_priority
@python_2_unicode_compatible
class TodoBase(object): class TodoBase(object):
""" """
This class represents a single todo item in a todo.txt file. It maintains This class represents a single todo item in a todo.txt file. It maintains
...@@ -227,6 +226,3 @@ class TodoBase(object): ...@@ -227,6 +226,3 @@ class TodoBase(object):
""" Returns the creation date of a todo. """ """ Returns the creation date of a todo. """
return self.fields['creationDate'] return self.fields['creationDate']
def __str__(self):
""" A printer for the todo item. """
return self.source()
...@@ -49,7 +49,7 @@ class TodoFile(object): ...@@ -49,7 +49,7 @@ class TodoFile(object):
to the file. to the file.
""" """
todofile = open(self.path, 'w') todofile = codecs.open(self.path, 'w', encoding="utf-8")
if p_todos is list: if p_todos is list:
for todo in p_todos: for todo in p_todos:
......
...@@ -36,14 +36,11 @@ class TodoList(TodoListBase): ...@@ -36,14 +36,11 @@ class TodoList(TodoListBase):
Should be given a list of strings, each element a single todo string. Should be given a list of strings, each element a single todo string.
The string will be parsed. The string will be parsed.
""" """
self._todos = [] # initialize these first because the constructor calls add_list
self._tododict = {} # hash(todo) to todo lookup self._tododict = {} # hash(todo) to todo lookup
self._depgraph = DirectedGraph() self._depgraph = DirectedGraph()
self._todo_id_map = {}
self._id_todo_map = {}
self.add_list(p_todostrings) super(TodoList, self).__init__(p_todostrings)
self.dirty = False
def todo_by_dep_id(self, p_dep_id): def todo_by_dep_id(self, p_dep_id):
""" """
...@@ -231,4 +228,3 @@ class TodoList(TodoListBase): ...@@ -231,4 +228,3 @@ class TodoList(TodoListBase):
for todo in self._todos: for todo in self._todos:
todo.attributes['parents'] = self.parents(todo) todo.attributes['parents'] = self.parents(todo)
...@@ -20,6 +20,7 @@ A list of todo items. ...@@ -20,6 +20,7 @@ A list of todo items.
from datetime import date from datetime import date
import re import re
from six import text_type
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import Filter from topydo.lib import Filter
...@@ -124,7 +125,8 @@ class TodoListBase(object): ...@@ -124,7 +125,8 @@ class TodoListBase(object):
result = todo_by_linenumber(p_identifier) result = todo_by_linenumber(p_identifier)
if not result: if not result:
result = todo_by_regexp(p_identifier) # convert integer to text so we pass on a valid regex
result = todo_by_regexp(text_type(p_identifier))
return result return result
...@@ -255,7 +257,10 @@ class TodoListBase(object): ...@@ -255,7 +257,10 @@ class TodoListBase(object):
self._todo_id_map[todo] = uid self._todo_id_map[todo] = uid
self._id_todo_map[uid] = todo self._id_todo_map[uid] = todo
def __str__(self): def print_todos(self):
"""
Returns a pretty-printed string (without colors) of the todo items in
this list.
"""
printer = PrettyPrinter() printer = PrettyPrinter()
return printer.print_list(self._todos) return printer.print_list(self._todos)
""" Version of Topydo. """ """ Version of Topydo. """
VERSION = '0.4.1' VERSION = '0.5'
LICENSE = """Copyright (C) 2014 - 2015 Bram Schoenmakers LICENSE = """Copyright (C) 2014 - 2015 Bram Schoenmakers
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
......
...@@ -16,62 +16,23 @@ ...@@ -16,62 +16,23 @@
""" A view is a list of todos, sorted and filtered. """ """ A view is a list of todos, sorted and filtered. """
from six import python_2_unicode_compatible
from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterColorFilter,
PrettyPrinterNumbers
)
from topydo.lib.PrettyPrinter import PrettyPrinter
@python_2_unicode_compatible
class View(object): class View(object):
""" """
A view is instantiated by a todo list, usually obtained from a todo.txt A view is instantiated by a todo list, usually obtained from a todo.txt
file. Also a sorter and a list of filters should be given that is applied file. Also a sorter and a list of filters should be given that is applied
to the list. to the list.
A printer can be passed, but it won't be used when pretty_print() is
called, since it will instantiate its own pretty printer instance.
""" """
def __init__(self, p_sorter, p_filters, p_todolist, def __init__(self, p_sorter, p_filters, p_todolist):
p_printer=PrettyPrinter()):
self._todolist = p_todolist self._todolist = p_todolist
self._viewdata = []
self._sorter = p_sorter self._sorter = p_sorter
self._filters = p_filters self._filters = p_filters
self._printer = p_printer
self.update()
def update(self): @property
""" def todos(self):
Updates the view data. Should be called when the backing todo list """ Returns a sorted and filtered list of todos in this view. """
has changed. result = self._sorter.sort(self._todolist.todos())
"""
self._viewdata = self._sorter.sort(self._todolist.todos())
for _filter in self._filters: for _filter in self._filters:
self._viewdata = _filter.filter(self._viewdata) result = _filter.filter(result)
def pretty_print(self, p_pp_filters=None):
""" Pretty prints the view. """
p_pp_filters = p_pp_filters or []
# since we're using filters, always use PrettyPrinter
printer = PrettyPrinter()
printer.add_filter(PrettyPrinterNumbers(self._todolist))
for ppf in p_pp_filters:
printer.add_filter(ppf)
# apply colors at the last step, the ANSI codes may confuse the
# preceding filters.
printer.add_filter(PrettyPrinterColorFilter())
return printer.print_list(self._viewdata)
def __str__(self): return result
return self._printer.print_list(self._viewdata)
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