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 @@
build
dist
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
-----
......
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
smoothly into topydo:
smoothly into topydo.
### General
* 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
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.sh [python2|python3]
......@@ -21,13 +37,17 @@ smoothly into topydo:
ever again.
* Features: add testcases that checks various inputs and outputs of your
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.
Install
-------
Install simply with:
Simply install with:
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
----
......
......@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
setup(
name = "topydo",
packages = find_packages(exclude=["test"]),
version = "0.4.1",
version = "0.5",
description = "A command-line todo list application using the todo.txt format.",
author = "Bram Schoenmakers",
author_email = "me@bramschoenmakers.nl",
......@@ -20,7 +20,7 @@ setup(
'console_scripts': ['topydo = topydo.cli.UILoader:main'],
},
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
......
......@@ -29,7 +29,7 @@ from io import StringIO
from topydo.commands import AddCommand
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 import TodoList
......@@ -127,7 +127,7 @@ class AddCommandTest(CommandTest):
self.assertFalse(self.todolist.todo(1).has_tag("after"))
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, "")
def test_add_dep5(self):
......@@ -137,7 +137,7 @@ class AddCommandTest(CommandTest):
self.assertFalse(self.todolist.todo(1).has_tag("after"))
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, "")
def test_add_dep6(self):
......@@ -248,7 +248,7 @@ class AddCommandTest(CommandTest):
command = AddCommand.AddCommand([u("Special \u25c4")], self.todolist, self.out, self.error)
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, "")
@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):
command = AddCommand.AddCommand(["-f", "-"], self.todolist, self.out, self.error)
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, "")
def test_add_from_file(self):
command = AddCommand.AddCommand(["-f", "test/data/AddCommandTest-from_file.txt"], self.todolist, self.out, self.error)
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, "")
def test_help(self):
......
......@@ -31,8 +31,8 @@ class ArchiveCommandTest(CommandTest):
self.assertTrue(todolist.is_dirty())
self.assertTrue(archive.is_dirty())
self.assertEqual(str(todolist), "x Not complete\n(C) Active")
self.assertEqual(str(archive), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete")
self.assertEqual(todolist.print_todos(), "x Not complete\n(C) Active")
self.assertEqual(archive.print_todos(), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete")
if __name__ == '__main__':
unittest.main()
......
......@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from six import PY2
from topydo.lib.Utils import escape_ansi
from test.TopydoTest import TopydoTest
......@@ -34,13 +33,5 @@ class CommandTest(TopydoTest):
if p_error:
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__':
unittest.main()
......@@ -35,6 +35,8 @@ class DeleteCommandTest(CommandTest):
todos = [
"Foo id:1",
"Bar p:1",
"a @test with due:2015-06-03",
"a @test with +project",
]
self.todolist = TodoList(todos)
......@@ -62,7 +64,7 @@ class DeleteCommandTest(CommandTest):
command.execute()
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.errors, "")
......@@ -71,7 +73,7 @@ class DeleteCommandTest(CommandTest):
command.execute()
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.errors, "")
......@@ -80,7 +82,7 @@ class DeleteCommandTest(CommandTest):
command.execute()
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.errors, "")
......@@ -116,7 +118,9 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["8to"], self.todolist, self.out, self.error)
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')
def test_multi_del1(self):
......@@ -124,14 +128,20 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _no_prompt)
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):
""" Test deletion of multiple items. """
command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _yes_prompt)
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):
""" Fail if any of supplied todo numbers is invalid. """
......@@ -160,6 +170,50 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.output, "")
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):
command = DeleteCommand([], self.todolist, self.out, self.error)
command.execute()
......
......@@ -211,6 +211,14 @@ class DepCommandTest(CommandTest):
self.assertFalse(self.output)
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):
command = DepCommand([p_subcommand], self.todolist, self.out, self.error)
command.execute()
......
......@@ -28,6 +28,9 @@ class DepriCommandTest(CommandTest):
"(A) Foo",
"Bar",
"(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)
......@@ -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.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):
command = DepriCommand(["99"], self.todolist, self.out, self.error)
......
......@@ -41,6 +41,8 @@ class DoCommandTest(CommandTest):
"Subtodo of inactive p:2",
"Strict due:2014-01-01 rec:1d",
"Invalid rec:1",
"a @test with due:2015-06-03",
"a @test with +project",
]
self.todolist = TodoList(todos)
......@@ -123,7 +125,7 @@ class DoCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.count(), 10)
self.assertEqual(self.todolist.count(), 12)
def test_recurrence(self):
self.assertFalse(self.todolist.todo(4).has_tag('due'))
......@@ -131,7 +133,7 @@ class DoCommandTest(CommandTest):
self._recurrence_helper(["4"])
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)
todo = self.todolist.todo(10)
......@@ -140,13 +142,13 @@ class DoCommandTest(CommandTest):
def test_strict_recurrence1(self):
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)
def test_strict_recurrence2(self):
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)
def test_invalid1(self):
......@@ -254,7 +256,7 @@ class DoCommandTest(CommandTest):
command.execute()
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, "")
def test_do_custom_date6(self):
......@@ -267,7 +269,7 @@ class DoCommandTest(CommandTest):
command.execute()
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, "")
def test_do_custom_date7(self):
......@@ -279,7 +281,7 @@ class DoCommandTest(CommandTest):
command.execute()
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, "")
def test_multi_do1(self):
......@@ -320,10 +322,10 @@ class DoCommandTest(CommandTest):
"""
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()
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):
""" Throw an error with invalid argument containing special characters. """
......@@ -333,6 +335,46 @@ class DoCommandTest(CommandTest):
self.assertFalse(self.todolist.is_dirty())
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):
""" Show error message when an item has an invalid recurrence pattern. """
command = DoCommand(["9"], self.todolist, self.out, self.error, _no_prompt)
......
......@@ -17,7 +17,7 @@
import unittest
# We're searching for 'mock'
# pylint: disable=no-name-in-module
# pylint: disable=no-name-in-module
try:
from unittest import mock
except ImportError:
......@@ -27,7 +27,7 @@ from six import u
import os
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.Todo import Todo
from topydo.lib.Config import config
......@@ -44,7 +44,6 @@ class EditCommandTest(CommandTest):
self.todolist = TodoList(todos)
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit1(self, mock_open_in_editor):
""" Preserve dependencies after editing. """
......@@ -55,7 +54,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
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._open_in_editor')
......@@ -69,7 +68,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
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):
""" Throw an error after invalid todo number given as argument. """
......@@ -99,7 +98,7 @@ class EditCommandTest(CommandTest):
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(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):
""" Throw an error with invalid argument containing special characters. """
......@@ -121,7 +120,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
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._open_in_editor')
......@@ -133,14 +132,14 @@ class EditCommandTest(CommandTest):
command = EditCommand(["-e", "@test"], self.todolist, self.out, self.error, None)
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.assertEqual(self.errors, "")
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):
""" Edit archive file. """
mock_call.return_value = 0
......@@ -149,11 +148,29 @@ class EditCommandTest(CommandTest):
os.environ['EDITOR'] = editor
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()
self.assertEqual(self.errors, "")
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__':
unittest.main()
......@@ -185,13 +185,6 @@ class FilterTest(TopydoTest):
self.assertEqual(todolist_to_string(filtered_todos), \
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):
todos = load_file('test/data/FilterTest3.txt')
otf = Filter.OrdinalTagFilter('due:<2014-11-10')
......@@ -330,7 +323,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter2(self):
otf = Filter.OrdinalTagFilter('due:=today')
......@@ -338,7 +331,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter3(self):
otf = Filter.OrdinalTagFilter('due:>today')
......@@ -346,7 +339,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo2)
self.assertEqual(result[0].source(), self.todo2)
def test_filter4(self):
otf = Filter.OrdinalTagFilter('due:<1w')
......@@ -354,8 +347,8 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos)
self.assertEqual(len(result), 2)
self.assertEqual(str(result[0]), self.todo1)
self.assertEqual(str(result[1]), self.todo2)
self.assertEqual(result[0].source(), self.todo1)
self.assertEqual(result[1].source(), self.todo2)
def test_filter5(self):
otf = Filter.OrdinalTagFilter('due:!today')
......@@ -363,7 +356,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo2)
self.assertEqual(result[0].source(), self.todo2)
def test_filter6(self):
otf = Filter.OrdinalTagFilter('due:non')
......
......@@ -14,30 +14,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Stub for the former 'ical' subcommand, now replaced with 'ls -f ical'.
from topydo.lib.JsonPrinter import JsonPrinter
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):
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.
"""
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"}')
......@@ -22,7 +22,7 @@ import unittest
from topydo.lib.Config import config
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
class ListCommandTest(CommandTest):
......@@ -219,12 +219,11 @@ class ListCommandUnicodeTest(CommandTest):
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)
class ListCommandJsonTest(CommandTest):
def test_json(self):
todolist = load_file_to_todolist("test/data/ListCommandTest.txt")
......@@ -252,7 +251,7 @@ class ListCommandJsonTest(CommandTest):
with codecs.open('test/data/ListCommandUnicodeTest.json', 'r', encoding='utf-8') as json:
jsontext = json.read()
self.assertEqual(self.output, utf8(jsontext))
self.assertEqual(self.output, jsontext)
self.assertEqual(self.errors, "")
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)
class ListCommandIcalTest(CommandTest):
def setUp(self):
self.maxDiff = None
@unittest.skipIf(IS_PYTHON_32, "icalendar is not supported for Python 3.2")
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()
self.assertTrue(todolist.is_dirty())
......@@ -308,7 +310,7 @@ class ListCommandIcalTest(CommandTest):
with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r', encoding='utf-8') as ical:
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, "")
if __name__ == '__main__':
......
......@@ -37,6 +37,7 @@ class PostponeCommandTest(CommandTest):
"Baz due:{} t:{}".format(self.today.isoformat(), self.start.isoformat()),
"Past due:{}".format(self.past.isoformat()),
"Future due:{} t:{}".format(self.future.isoformat(), self.future_start.isoformat()),
"FutureStart t:{}".format(self.future.isoformat())
]
self.todolist = TodoList(todos)
......@@ -233,6 +234,55 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "")
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):
command = PostponeCommand(["help"], self.todolist, self.out, self.error)
command.execute()
......
......@@ -27,6 +27,9 @@ class PriorityCommandTest(CommandTest):
todos = [
"(A) Foo",
"Bar",
"(B) a @test with due:2015-06-03",
"a @test with +project p:1",
"Baz id:1",
]
self.todolist = TodoList(todos)
......@@ -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.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):
command = PriorityCommand(["99", "A"], self.todolist, self.out, self.error)
command.execute()
......@@ -128,6 +183,29 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.output, "")
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):
command = PriorityCommand([], self.todolist, self.out, self.error)
command.execute()
......
......@@ -31,13 +31,13 @@ class SortCommandTest(CommandTest):
command = SortCommand(["text"], self.todolist, self.out, self.error)
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):
command = SortCommand([], self.todolist, self.out, self.error)
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):
""" Check that order does not influence the UID of a todo. """
......
......@@ -19,7 +19,7 @@ import unittest
from topydo.lib.Config import config
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
class SorterTest(TopydoTest):
......@@ -128,7 +128,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, [])
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):
"""
......@@ -141,7 +141,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, [])
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):
"""
......@@ -153,7 +153,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, [])
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__':
unittest.main()
......@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.Todo import Todo
from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList
......@@ -42,4 +43,8 @@ def load_file_to_todolist(p_filename):
def todolist_to_string(p_list):
""" 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
from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.TodoList import TodoList
from topydo.lib.TodoListBase import TodoListBase
from test.TopydoTest import TopydoTest
class TodoListTester(TopydoTest):
......@@ -34,7 +35,7 @@ class TodoListTester(TopydoTest):
lines = [line for line in self.todofile.read() \
if re.search(r'\S', line)]
self.text = ''.join(lines)
self.todolist = TodoList(lines)
self.todolist = TodoListBase(lines)
def test_contexts(self):
self.assertEqual(set(['Context1', 'Context2']), \
......@@ -101,6 +102,16 @@ class TodoListTester(TopydoTest):
self.assertTrue(self.todolist.is_dirty())
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):
todo = self.todolist.todo(3)
self.todolist.append(todo, "@Context3")
......@@ -133,22 +144,10 @@ class TodoListTester(TopydoTest):
self.assertRaises(InvalidTodoException, self.todolist.todo, count + 100)
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):
""" Test that empty lines are not counted. """
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):
todo = Todo("No number")
self.todolist.add_todo(todo)
......@@ -354,6 +353,14 @@ class TodoListDependencyTester(TopydoTest):
self.assertEqual(todo1.source(), 'Foo id: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):
def setUp(self):
super(TodoListCleanDependencyTester, self).setUp()
......
......@@ -18,7 +18,7 @@ import unittest
from topydo.lib import Filter
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.TodoList import TodoList
from test.TopydoTest import TopydoTest
......@@ -34,7 +34,7 @@ class ViewTest(TopydoTest):
todofilter = Filter.GrepFilter('+Project')
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__':
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
; filename = todo.txt
; archive_filename = done.txt
colors = 1
highlight_projects_contexts = 1
identifiers = linenumber ; or: text
[add]
auto_creation_date = 1
[ls]
hide_tags = id,p,ical
indent = 0
......
......@@ -33,10 +33,11 @@ _SUBCOMMAND_MAP = {
'do': 'DoCommand',
'edit': 'EditCommand',
'exit': 'ExitCommand', # used for the prompt
'ical': 'IcalCommand', # deprecated
'ls': 'ListCommand',
'lscon': 'ListContextCommand',
'listcon': 'ListContextCommand',
'listcontext': 'ListContextCommand',
'listcontexts': 'ListContextCommand',
'lsprj': 'ListProjectCommand',
'lsproj': 'ListProjectCommand',
'listprj': 'ListProjectCommand',
......
......@@ -24,16 +24,17 @@ import sys
from six import PY2
from six.moves import input
MAIN_OPTS = "c:d:ht:v"
MAIN_OPTS = "ac:d:ht:v"
def usage():
""" Prints the command-line usage of topydo. """
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 -v
-a : Do not archive todo items on completion.
-c : Specify an alternative configuration file.
-d : Specify an alternative archive file (done.txt)
-h : This help text
......@@ -49,7 +50,6 @@ Available commands:
* depri
* do
* edit
* ical
* ls
* listcon (lscon)
* listprojects (lsprj)
......@@ -113,6 +113,7 @@ class CLIApplicationBase(object):
def __init__(self):
self.todolist = TodoList.TodoList([])
self.todofile = None
self.do_archive = True
def _usage(self):
usage()
......@@ -134,7 +135,9 @@ class CLIApplicationBase(object):
overrides = {}
for opt, value in opts:
if opt == "-c":
if opt == "-a":
self.do_archive = False
elif opt == "-c":
alt_config_path = value
elif opt == "-t":
overrides[('topydo', 'filename')] = value
......@@ -167,7 +170,7 @@ class CLIApplicationBase(object):
command.execute()
if archive.is_dirty():
archive_file.write(str(archive))
archive_file.write(archive.print_todos())
def _help(self, args):
if args == None:
......@@ -204,13 +207,17 @@ class CLIApplicationBase(object):
completed. It will do some maintenance and write out the final result
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():
self._archive()
if self.do_archive and config().archive():
self._archive()
if config().keep_sorted():
self._execute(SortCommand, [])
self.todofile.write(str(self.todolist))
self.todofile.write(self.todolist.print_todos())
def run(self):
raise NotImplementedError
......
......@@ -87,7 +87,7 @@ class TopydoCompleter(Completer):
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 @)
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)
......
......@@ -27,7 +27,7 @@ def main():
args = sys.argv[1:]
try:
opts, args = getopt.getopt(args, MAIN_OPTS)
_, args = getopt.getopt(args, MAIN_OPTS)
except getopt.GetoptError as e:
error(str(e))
sys.exit(1)
......
......@@ -47,7 +47,6 @@ class AddCommand(Command):
self.args = args
def get_todos_from_file(self):
if self.from_file == '-':
f = stdin
......@@ -107,7 +106,8 @@ class AddCommand(Command):
add_dependencies('before')
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 = self.todolist.add(todo_text)
......
......@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.commands.DCommand import DCommand
from topydo.lib.DCommand import DCommand
class DeleteCommand(DCommand):
def __init__(self, p_args, p_todolist,
......
......@@ -17,6 +17,7 @@
from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config
from topydo.lib import Filter
from topydo.lib.PrettyPrinter import pretty_printer_factory
from topydo.lib.Sorter import Sorter
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.View import View
......@@ -34,6 +35,8 @@ class DepCommand(Command):
except InvalidCommandArgument:
self.subsubcommand = None
self.printer = pretty_printer_factory(self.todolist)
def _handle_add(self):
(from_todo, to_todo) = self._get_todos()
......@@ -97,9 +100,8 @@ class DepCommand(Command):
if todos:
sorter = Sorter(config().sort_string())
instance_filter = Filter.InstanceFilter(todos)
view = View(sorter, [instance_filter], self.todolist,
self.printer)
self.out(view.pretty_print())
view = View(sorter, [instance_filter], self.todolist)
self.out(self.printer.print_list(view.todos))
except InvalidTodoException:
self.error("Invalid todo number given.")
except InvalidCommandArgument:
......
......@@ -25,20 +25,14 @@ class DepriCommand(MultiCommand):
super(DepriCommand, self).__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.get_todos(self.args)
def execute_multi_specific(self):
try:
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
for todo in self.todos:
if todo.priority() != None:
self.todolist.set_priority(todo, None)
self.out("Priority removed.")
self.out(self.printer.print_todo(todo))
except IndexError:
self.error(self.usage())
def _execute_multi_specific(self):
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
for todo in self.todos:
if todo.priority() != None:
self.todolist.set_priority(todo, None)
self.out("Priority removed.")
self.out(self.printer.print_todo(todo))
def usage(self):
return """Synopsis: depri <NUMBER1> [<NUMBER2> ...]"""
......
......@@ -16,7 +16,7 @@
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.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.Recurrence import advance_recurring_todo, strict_advance_recurring_todo, NoRecurrenceException
......@@ -36,9 +36,13 @@ class DoCommand(DCommand):
def get_flags(self):
""" 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):
super(DoCommand, self).process_flag(p_opt, p_value)
if p_opt == "-s" or p_opt == "--strict":
self.strict_recurrence = True
elif p_opt == "-d" or p_opt == "--date":
......
......@@ -18,7 +18,7 @@ import os
from subprocess import call, check_call, CalledProcessError
import tempfile
from six import text_type, u
from six import u
from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.MultiCommand import MultiCommand
......@@ -34,31 +34,30 @@ DEFAULT_EDITOR = 'vi'
# cannot use super() inside the class itself
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):
super(EditCommand, self).__init__(p_args, p_todolist, p_output,
p_error, p_input)
if len(self.args) == 0:
self.multi_mode = False
self.is_expression = False
self.edit_archive = False
self.last_argument = False
def _process_flags(self):
opts, args = self.getopt('xed')
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
def get_flags(self):
return ("d", [])
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):
f = tempfile.NamedTemporaryFile()
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)
return f
......@@ -73,12 +72,20 @@ class EditCommand(MultiCommand, ExpressionCommand):
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:
return check_call([p_editor, p_temp_file.name])
return check_call([editor, p_file])
except CalledProcessError:
self.error('Something went wrong in the editor...')
return 1
except(OSError):
self.error('There is no such editor as: ' + editor + '. '
'Check your $EDITOR and/or $PATH')
def _catch_todo_errors(self):
errors = []
......@@ -94,59 +101,35 @@ class EditCommand(MultiCommand, ExpressionCommand):
else:
return None
def execute(self):
if not super(EditCommand, self).execute():
return False
def _execute_multi_specific(self):
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
try:
editor = os.environ['EDITOR'] or DEFAULT_EDITOR
except(KeyError):
editor = DEFAULT_EDITOR
try:
if len(self.args) < 1:
todo = config().todotxt()
temp_todos = self._todos_to_temp()
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:
self._process_flags()
if self.edit_archive:
archive = config().archive()
return call([editor, archive]) == 0
if self.is_expression:
self.todos = self._view()._viewdata
else:
self.get_todos(self.args)
todo_errors = self._catch_todo_errors()
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')
self.error('Number of edited todos is not equal to '
'number of supplied todo IDs.')
else:
self.error(self.usage())
def _execute_not_multi(self):
if self.edit_archive:
archive = config().archive()
return self._open_in_editor(archive) == 0
else:
todo = config().todotxt()
return self._open_in_editor(todo) == 0
def usage(self):
return """Synopsis:
......
......@@ -16,6 +16,7 @@
from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.Config import config
from topydo.lib.PrettyPrinter import pretty_printer_factory
from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterIndentFilter,
PrettyPrinterHideTagFilter
......@@ -45,7 +46,7 @@ class ListCommand(ExpressionCommand):
"""
try:
import icalendar as _
except ImportError:
except ImportError: # pragma: no cover
self.error("icalendar package is not installed.")
return False
......@@ -81,10 +82,8 @@ class ListCommand(ExpressionCommand):
sent to the output.
"""
def _print_text():
"""
Outputs a pretty-printed text format of the todo list.
"""
if self.printer == None:
# create a standard printer with some filters
indent = config().list_indent()
hidden_tags = config().hidden_tags()
......@@ -92,14 +91,9 @@ class ListCommand(ExpressionCommand):
filters.append(PrettyPrinterIndentFilter(indent))
filters.append(PrettyPrinterHideTagFilter(hidden_tags))
self.out(self._view().pretty_print(filters))
self.printer = pretty_printer_factory(self.todolist, filters)
if self.printer == None:
_print_text()
else:
# we have set a special format, simply use the printer set in
# self.printer
self.out(str(self._view()))
self.out(self.printer.print_list(self._view().todos))
def execute(self):
if not super(ListCommand, self).execute():
......@@ -107,7 +101,7 @@ class ListCommand(ExpressionCommand):
try:
self._process_flags()
except SyntaxError:
except SyntaxError: # pragma: no cover
# importing icalendar failed, most likely due to Python 3.2
self.error("icalendar is not supported in this Python version.")
return False
......
......@@ -17,11 +17,9 @@
from datetime import date, timedelta
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.Command import InvalidCommandArgument
from topydo.lib.Config import config
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import date_string_to_date
class PostponeCommand(MultiCommand):
......@@ -33,19 +31,16 @@ class PostponeCommand(MultiCommand):
p_args, p_todolist, p_out, p_err, p_prompt)
self.move_start_date = False
self._process_flags()
self.get_todos(self.args[:-1])
self.last_argument = True
def _process_flags(self):
opts, args = self.getopt('s')
def get_flags(self):
return("s", [])
for opt, _ in opts:
if opt == '-s':
self.move_start_date = True
def process_flag(self, p_opt, p_value):
if p_opt == '-s':
self.move_start_date = True
self.args = args
def execute_multi_specific(self):
def _execute_multi_specific(self):
def _get_offset(p_todo):
offset = p_todo.tag_value(
config().tag_due(), date.today().isoformat())
......@@ -56,31 +51,28 @@ class PostponeCommand(MultiCommand):
return offset_date
try:
pattern = self.args[-1]
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
for todo in self.todos:
offset = _get_offset(todo)
new_due = relative_date_to_date(pattern, offset)
pattern = self.args[-1]
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
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
todo.set_tag(config().tag_start(), new_start.isoformat())
for todo in self.todos:
offset = _get_offset(todo)
new_due = relative_date_to_date(pattern, offset)
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
todo.set_tag(config().tag_due(), new_due.isoformat())
self.todolist.set_dirty()
self.out(self.printer.print_todo(todo))
else:
self.error("Invalid date pattern given.")
break
except (InvalidCommandArgument, IndexError):
self.error(self.usage())
todo.set_tag(config().tag_start(), new_start.isoformat())
# pylint: disable=E1103
todo.set_tag(config().tag_due(), new_due.isoformat())
self.todolist.set_dirty()
self.out(self.printer.print_todo(todo))
else:
self.error("Invalid date pattern given.")
break
def usage(self):
return "Synopsis: postpone [-s] <NUMBER> [<NUMBER2> ...] <PATTERN>"
......
......@@ -14,9 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import is_valid_priority
class PriorityCommand(MultiCommand):
......@@ -27,31 +28,30 @@ class PriorityCommand(MultiCommand):
super(PriorityCommand, self).__init__(
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):
priority = None
def _execute_multi_specific(self):
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 = self.args[-1]
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
priority = normalize_priority(self.args[-1])
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
if is_valid_priority(priority):
for todo in self.todos:
old_priority = todo.priority()
self.todolist.set_priority(todo, priority)
if is_valid_priority(priority):
for todo in self.todos:
old_priority = todo.priority()
self.todolist.set_priority(todo, priority)
if old_priority and priority and old_priority != priority:
self.out("Priority changed from {} to {}".format(
old_priority, priority))
elif not old_priority:
self.out("Priority set to {}.".format(priority))
if old_priority and priority and old_priority != priority:
self.out("Priority changed from {} to {}".format(
old_priority, priority))
elif not old_priority:
self.out("Priority set to {}.".format(priority))
self.out(self.printer.print_todo(todo))
else:
self.error("Invalid priority given.")
except IndexError:
self.error(self.usage())
self.out(self.printer.print_todo(todo))
else:
self.error("Invalid priority given.")
def usage(self):
return """Synopsis: pri <NUMBER1> [<NUMBER2> ...] <PRIORITY>"""
......
......@@ -39,9 +39,7 @@ class SortCommand(Command):
sorted_todos = sorter.sort(self.todolist.todos())
self.todolist.erase()
for todo in sorted_todos:
self.todolist.add_todo(todo)
self.todolist.add_todos(sorted_todos)
def usage(self):
return """Synopsis: sort [expression]"""
......
......@@ -84,8 +84,10 @@ class Command(object):
return result
def usage(self):
return "No usage text available for this command."
""" Returns a one-line synopsis for this command. """
raise NotImplementedError
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:
(such as todo.txt location passed with -t). The key is a tuple of
(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 = {
# topydo
'default_command': 'ls',
'colors': '1',
'highlight_projects_contexts': '1',
'filename' : 'todo.txt',
'archive_filename' : 'done.txt',
'identifiers': 'linenumber',
# add
'auto_creation_date': '1',
# ls
'hide_tags': 'id,p,ical',
'indent': 0,
......@@ -118,12 +128,6 @@ class _Config:
except ValueError:
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):
return os.path.expanduser(self.cp.get('topydo', 'filename'))
......@@ -240,6 +244,12 @@ class _Config:
except ValueError:
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):
"""
Retrieve the config instance.
......
......@@ -19,7 +19,6 @@ import re
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException
class DCommand(MultiCommand):
"""
......@@ -36,29 +35,14 @@ class DCommand(MultiCommand):
self.force = False
self.process_flags()
self.length = len(self.todolist.todos()) # to determine newly activated todos
self.get_todos(self.args)
def get_flags(self):
""" Default implementation of getting specific flags. """
return ("", [])
return ("f", ["force"])
def process_flag(self, p_option, p_value):
""" Default implementation of processing specific flags. """
pass
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 process_flag(self, p_opt, p_value):
if p_opt == "-f" or p_opt == "--force":
self.force = True
def _uncompleted_children(self, p_todo):
return sorted(
......@@ -72,11 +56,10 @@ class DCommand(MultiCommand):
self.out(printer.print_list(p_todos))
def prompt_text(self):
return "Yes or no? [y/N] "
raise NotImplementedError
def prefix(self):
""" Prefix to use when printing a todo. """
return ""
raise NotImplementedError
def _process_subtasks(self, p_todo):
children = self._uncompleted_children(p_todo)
......@@ -110,26 +93,26 @@ class DCommand(MultiCommand):
return [todo for todo in self.todolist.todos()[:self.length]
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.
"""
return True
def condition_failed_text(self):
return ""
raise NotImplementedError
def execute_specific(self, _):
pass
raise NotImplementedError
def execute_specific_core(self, p_todo):
"""
The core operation on the todo itself. Also used to operate on
child/parent tasks.
"""
pass
raise NotImplementedError
def execute_multi_specific(self):
def _execute_multi_specific(self):
old_active = self._active_todos()
for todo in self.todos:
......
......@@ -35,13 +35,22 @@ class ExpressionCommand(Command):
self.sort_expression = config().sort_string()
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):
filters = []
def arg_filters():
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):
argfilter = Filter.OrdinalTagFilter(arg)
elif len(arg) > 1 and arg[0] == '-':
......@@ -70,4 +79,4 @@ class ExpressionCommand(Command):
sorter = Sorter(self.sort_expression)
filters = self._filters()
return View(sorter, filters, self.todolist, self.printer)
return View(sorter, filters, self.todolist)
......@@ -15,9 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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.Utils import date_string_to_date
......@@ -30,9 +28,8 @@ class Filter(object):
return [t for t in p_todos if self.match(t)]
def match(self, p_todo):
""" Default match value. """
return True
def match(self, _):
raise NotImplementedError
class NegationFilter(Filter):
def __init__(self, p_filter):
......@@ -64,7 +61,7 @@ class GrepFilter(Filter):
super(GrepFilter, self).__init__()
# convert to string in case we receive integers
self.expression = text_type(p_expression)
self.expression = p_expression
if p_case_sensitive != None:
self.case_sensitive = p_case_sensitive
......
......@@ -69,15 +69,12 @@ class IcalPrinter(Printer):
try:
import 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
# this is an optional dependency, dropping Python 3.2 support altogether is
# too much. Therefore just disable the iCalendar functionality
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):
result = ""
......
......@@ -16,10 +16,10 @@
from six import u
from topydo.lib.Command import Command
from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.TodoListBase import InvalidTodoException
class MultiCommand(Command):
class MultiCommand(ExpressionCommand):
"""
A common class for operations that can work with multiple todo IDs.
"""
......@@ -33,14 +33,49 @@ class MultiCommand(Command):
self.todos = []
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 """
for number in p_numbers:
try:
self.todos.append(self.todolist.todo(number))
except InvalidTodoException:
self.invalid_numbers.append(number)
if self.is_expression:
self.get_todos_from_expr()
else:
if self.last_argument:
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):
"""
......@@ -65,23 +100,36 @@ class MultiCommand(Command):
else:
return None
def execute_multi_specific(self):
def _execute_multi_specific(self):
"""
Operations specific for particular command dealing with multiple todo
IDs.
"""
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):
if not super(MultiCommand, self).execute():
return False
todo_errors = self._catch_todo_errors()
self._process_flags()
if not todo_errors:
self.execute_multi_specific()
if not self.multi_mode:
self._execute_not_multi()
else:
for error in todo_errors:
self.error(error)
self.get_todos()
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
......@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterColorFilter,
PrettyPrinterNumbers
)
class Printer(object):
"""
An abstract class that turns todo items into strings.
......@@ -21,8 +26,7 @@ class Printer(object):
Subclasses must at least implement the print_todo method.
"""
def print_todo(self, p_todo):
""" Base implementation. Simply returns the string conversion. """
return str(p_todo)
raise NotImplementedError
def print_list(self, p_todos):
"""
......@@ -57,9 +61,26 @@ class PrettyPrinter(Printer):
def print_todo(self, p_todo):
""" Given a todo item, pretty print it. """
todo_str = str(p_todo)
todo_str = p_todo.source()
for ppf in self.filters:
todo_str = ppf.filter(todo_str, p_todo)
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 @@
""" Provides filters used for pretty printing. """
import re
from six import u
from topydo.lib.Config import config
from topydo.lib.Colors import Colors, NEUTRAL_COLOR
......@@ -29,8 +30,10 @@ class PrettyPrinterFilter(object):
"""
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):
"""
......@@ -44,10 +47,10 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
colorscheme = Colors()
priority_colors = colorscheme.get_priority_colors()
project_color = colorscheme.get_project_color()
context_color = colorscheme.get_context_color()
project_color = colorscheme.get_project_color()
context_color = colorscheme.get_context_color()
metadata_color = colorscheme.get_metadata_color()
link_color = colorscheme.get_link_color()
link_color = colorscheme.get_link_color()
if config().colors():
color = NEUTRAL_COLOR
......@@ -56,14 +59,16 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
except KeyError:
pass
# color by priority
p_todo_str = color + p_todo_str
if config().highlight_projects_contexts():
p_todo_str = re.sub(
r'\B(\+|@)(\S*\w)',
lambda m: (
context_color if m.group(0)[0] == "@"
else project_color) + m.group(0) + color,
p_todo_str)
# color projects / contexts
p_todo_str = re.sub(
r'\B(\+|@)(\S*\w)',
lambda m: (
context_color if m.group(0)[0] == "@"
else project_color) + m.group(0) + color,
p_todo_str)
# tags
p_todo_str = re.sub(r'\b\S+:[^/\s]\S*\b',
......@@ -97,7 +102,7 @@ class PrettyPrinterNumbers(PrettyPrinterFilter):
def filter(self, p_todo_str, p_todo):
""" 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):
""" Removes all occurences of the given tags from the text. """
......
......@@ -37,8 +37,8 @@ def _add_months(p_sourcedate, p_months):
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
with the period of time added to it.
Converts a pattern in the form [0-9][dwmy] and returns a date from the
offset with the period of time added to it.
"""
result = None
......
......@@ -20,12 +20,11 @@ This module contains the class that represents a single todo item.
from datetime import date
import re
from six import python_2_unicode_compatible, u
from six import u
from topydo.lib.TodoParser import parse_line
from topydo.lib.Utils import is_valid_priority
@python_2_unicode_compatible
class TodoBase(object):
"""
This class represents a single todo item in a todo.txt file. It maintains
......@@ -227,6 +226,3 @@ class TodoBase(object):
""" Returns the creation date of a todo. """
return self.fields['creationDate']
def __str__(self):
""" A printer for the todo item. """
return self.source()
......@@ -49,7 +49,7 @@ class TodoFile(object):
to the file.
"""
todofile = open(self.path, 'w')
todofile = codecs.open(self.path, 'w', encoding="utf-8")
if p_todos is list:
for todo in p_todos:
......
......@@ -36,14 +36,11 @@ class TodoList(TodoListBase):
Should be given a list of strings, each element a single todo string.
The string will be parsed.
"""
self._todos = []
# initialize these first because the constructor calls add_list
self._tododict = {} # hash(todo) to todo lookup
self._depgraph = DirectedGraph()
self._todo_id_map = {}
self._id_todo_map = {}
self.add_list(p_todostrings)
self.dirty = False
super(TodoList, self).__init__(p_todostrings)
def todo_by_dep_id(self, p_dep_id):
"""
......@@ -231,4 +228,3 @@ class TodoList(TodoListBase):
for todo in self._todos:
todo.attributes['parents'] = self.parents(todo)
......@@ -20,6 +20,7 @@ A list of todo items.
from datetime import date
import re
from six import text_type
from topydo.lib.Config import config
from topydo.lib import Filter
......@@ -124,7 +125,8 @@ class TodoListBase(object):
result = todo_by_linenumber(p_identifier)
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
......@@ -255,7 +257,10 @@ class TodoListBase(object):
self._todo_id_map[todo] = uid
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()
return printer.print_list(self._todos)
""" Version of Topydo. """
VERSION = '0.4.1'
VERSION = '0.5'
LICENSE = """Copyright (C) 2014 - 2015 Bram Schoenmakers
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
......
......@@ -16,62 +16,23 @@
""" 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):
"""
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
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,
p_printer=PrettyPrinter()):
def __init__(self, p_sorter, p_filters, p_todolist):
self._todolist = p_todolist
self._viewdata = []
self._sorter = p_sorter
self._filters = p_filters
self._printer = p_printer
self.update()
def update(self):
"""
Updates the view data. Should be called when the backing todo list
has changed.
"""
self._viewdata = self._sorter.sort(self._todolist.todos())
@property
def todos(self):
""" Returns a sorted and filtered list of todos in this view. """
result = self._sorter.sort(self._todolist.todos())
for _filter in self._filters:
self._viewdata = _filter.filter(self._viewdata)
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)
result = _filter.filter(result)
def __str__(self):
return self._printer.print_list(self._viewdata)
return result
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