Commit bbace860 authored by Jacek Sowiński's avatar Jacek Sowiński

Merge branch 'master' into column-ui

parents 393d4d03 0c7a3aa3
[run]
source = topydo
branch = True
[report]
exclude_lines =
......@@ -11,5 +12,5 @@ exclude_lines =
if __name__ == .__main__.:
omit =
topydo/lib/ExitCommand.py
topydo/commands/ExitCommand.py
topydo/lib/Version.py
*.pyc
*.py[cod]
*.sw?
install
.coverage
......@@ -11,6 +11,7 @@ build/
develop-eggs/
dist/
eggs/
htmlcov/
lib/
lib64/
parts/
......@@ -19,6 +20,9 @@ var/
*.egg-info/
.installed.cfg
*.egg
.eggs/
# Sublime Text
*.sublime-*
!/topydo/lib/
sudo: false # run on new infrastructure
language: python
python:
- "2.7"
- "3.2"
- "3.3"
- "3.4"
- "3.5"
- "pypy3"
install:
- "python -m pip install pip --upgrade"
- "pip install ."
- "pip install urwid"
- "pip install icalendar"
- "pip install .[ical]"
- "pip install .[prompt-toolkit]"
- "pip install .[test]"
- "pip install pylint"
script: "./run-tests.sh"
- "pip install codecov"
script:
- "green -vvr"
- "python -m pylint --errors-only topydo test"
# Cache Dependencies
after_script:
- codecov
cache:
directories:
- $HOME/travis/.cache/pip
......
Main author / maintainer: Bram Schoenmakers (@bram85)
For a list of contributors, please refer to the Contributors page on
[Github](https://github.com/bram85/topydo/graphs/contributors).
0.9
---
* Dropped support for Python 2.7.
* Add ability to filter on creation/completion dates:
topydo ls created:today
topydo ls completed:today
topydo -t done.txt completed:today # if auto-archiving is set
* `ls -F` supports `%P` that expands to a single space when no priority is set,
in contrast to `%p` which expands to an empty string (thanks to @MinchinWeb).
* `ls -N` prints enough todo items such that it fits on one screen (thanks to
@MinchinWeb).
* Aliases can have a `{}` placeholder which is substituted with the alias'
arguments (thanks to @mruwek).
* `pri` accepts priorities in lowercase (thanks to @MinchinWeb).
* Several bugfixes for `dep gc`.
* Various test/CI improvements.
0.8
---
* `do -d` understands relative dates.
* Introduced `yesterday` as a relative date (abbrev. `yes`).
* `tag` command understands relative dates when setting due or t tags.
* Fix install of wheels (unnecessarily installed dependencies). Issue #79.
* Bugfix: the negation of ordinal tag filters did not work.
* Some improvements in test coverage (a.o. thanks to @mruwek).
0.7
---
A big release with many new features. Many thanks to Jacek Sowiński (@mruwek)
for the majority of these new features.
* `ls` output can be customized with a -F flag or a configuration option:
[ls]
list_format = |%I| %x %{(}p{)} %c %s %k %{due:}d %{t:}t
or `ls -F "%{(}p{)} %s %{due:}d"`.
See `help ls` for all placeholders. Each placeholder can optionally be
surrounded by optional texts that are only printed when the placeholder is
expanded to a value.
The format string may contain a tab character: all text that follows is
aligned to the right.
(thanks to @mruwek)
* New subcommand: `revert`. Revert the last executed command(s). The number of
revisions can be tuned in the configuration file:
[topydo]
backup_count = 25
Set to 0 to disable this feature. (thanks to @mruwek)
* New feature: aliases. Aliases can be defined in the configuration file:
[aliases]
showall = ls -x
(thanks to @mruwek)
* Filter based on priorities (thanks to @mruwek)
ls (A)
ls (<A)
* `ls` has a `-n` flag to limit the number of todo items (similar to the
list_limit option in the configuration file:
ls -n 5
* `ls` has a `-i` flag to select displaying todo items based on their ID. This
can be useful to have a 'clean' default view, and to gather more details for
a certain todo item using aliases and formatting.
* Prompt mode no longer warns about background modifications to todo.txt when a
read-only command is entered (e.g. `ls`).
* Removed restriction in `edit` mode that requires keeping the same amount of
lines in the todo.txt file.
* `edit` only processes the todo items when edits were actually made in the
editor (thanks to @mruwek)
* When entering today's day of the week as a relative date, it will use next
week's date instead of today.
* Bugfix: not all tags were properly hidden with the `hide_tags` configuration
option.
* Better PEP8 compliance (thanks to @MinchinWeb)
* Various test/CI improvements (thanks to @MinchinWeb)
* Support for Python 3.2 removed.
* Many other minor improvements (a.o. thanks to @MinchinWeb)
0.6
---
......@@ -16,7 +113,9 @@
---
* 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:
* 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 =
......
......@@ -5,11 +5,9 @@ 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.
* Use descriptive commit messages. The post
[How to write a commit message](http://chris.beams.io/posts/git-commit/) by
Chris Beams has some good guidelines.
### Coding style
......@@ -17,37 +15,63 @@ smoothly into topydo.
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
ask you to refactor functions with massive indentation or don't fit
otherwise on a screen.
### Testing
* Run tests with:
* First make sure to have the prerequisites installed to perform the tests:
pip install .[test]
./run-tests.sh [python2|python3]
* Then, run the tests with:
green -r
Obviously, I won't accept anything that makes the tests fail. When you submit
a Pull Request, Travis CI will automatically run all tests for various Python
versions, but it's better if you run the tests locally first.
* Travis CI will also run `pylint` and fail when new errors are introduced. You
may want to add a `pre-push` script to your topydo clone before pushing to
Github (.git/hooks/pre-push):
#!/bin/sh
remote="$1"
if [ $remote = "origin" ]; then
if ! green; then
exit 1
fi
if ! python2 -m pylint --errors-only topydo test; then
exit 1
fi
if ! python3 -m pylint --errors-only topydo test; then
exit 1
fi
fi
exit 0
Make sure to run `chmod +x .git/hooks/pre-push` to activate the hook.
Make sure you have the `mock` package installed if you test on a Python
version older than 3.3.
* Add tests for your change(s):
* Bugfixes: add a testcase that covers your bugfix, so the bug won't happen
* Bugfixes: add a test case that covers your bugfix, so the bug won't happen
ever again.
* Features: add testcases that checks various inputs and outputs of your
* Features: add test cases that checks various inputs and outputs of your
feature. Be creative in trying to break the feature you've just implemented.
* Check the test coverage of your contributed code, in particular if you
touched code in the topydo.lib or topydo.command packages:
* Check the test coverage of your contributed code, in particular if you touched
code in the topydo.lib or topydo.command packages:
pip install coverage
coverage run setup.py test
coverage report
coverage report -m
Or alternatively, for a more friendly output, run:
coverage html
Which will generate annotated files in the *htmlcov* folder. The new code
which will generate annotated files in the *htmlcov* folder. The new code
should be marked green (i.e. covered).
When you create a Pull Request, code coverage will be automatically checked
and reported by [Codecov.io](https://codecov.io/github/bram85/topydo).
topydo
======
[![Build Status](https://travis-ci.org/bram85/topydo.svg?branch=master)](https://travis-ci.org/bram85/topydo) [![Join the chat at https://gitter.im/bram85/topydo](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bram85/topydo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=bram85&url=https://github.com/bram85/topydo&title=topydo&language=&tags=github&category=software)
[![Build Status](https://travis-ci.org/bram85/topydo.svg?branch=master)](https://travis-ci.org/bram85/topydo) [![codecov.io](https://codecov.io/github/bram85/topydo/coverage.svg?branch=master)](https://codecov.io/github/bram85/topydo?branch=master) [![Join the chat at https://gitter.im/bram85/topydo](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bram85/topydo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=bram85&url=https://github.com/bram85/topydo&title=topydo&language=&tags=github&category=software)
topydo is a todo list application using the [todo.txt format][1]. It is heavily
inspired by the [todo.txt CLI][2] by Gina Trapani. This tool is actually a
......@@ -28,10 +28,20 @@ Simply install with:
### 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.
* [icalendar][7] : To print your todo.txt file as an iCalendar file
(not supported for Python 3.2).
* [prompt-toolkit][6] : For topydo's _prompt_ mode, which offers a shell-like
interface with auto-completion.
* [arrow][8] : Used to turn dates into a human readable version.
* [backports.shutil_get_terminal_size][9] : Used to determine your terminal
window size. This function was
added to the standard library in
Python 3.3 and so is only
required in older versions of
Python.
* [python-dateutil][10]: A dependency of *arrow*.
* [mock][11] : Used for testing. This was added to the standard
library in Python 3.3.
Demo
----
......@@ -43,4 +53,10 @@ Demo
[2]: https://github.com/ginatrapani/todo.txt-cli
[3]: https://github.com/bram85/todo.txt-tools
[4]: https://github.com/bram85/topydo/wiki
[5]: https://raw.githubusercontent.com/bram85/topydo/stable/doc/topydo.gif
[5]: https://raw.githubusercontent.com/bram85/topydo/master/doc/topydo.gif
[6]: https://github.com/jonathanslenders/python-prompt-toolkit
[7]: https://github.com/collective/icalendar
[8]: https://github.com/crsmithdev/arrow
[9]: https://github.com/chrippa/backports.shutil_get_terminal_size
[10]: https://dateutil.readthedocs.org/
[11]: https://github.com/testing-cabal/mock
#!/bin/bash
if [ "$1" = "python2" ] || [ "$1" = "python3" ]; then
PYTHON=$1
else
# run whatever is active
PYTHON=python
fi
# Run normal tests
if ! $PYTHON setup.py test; then
exit 1
fi
# pylint is not supported on 3.2, so skip the test there
if $PYTHON --version 2>&1 | grep 'Python 3\.2' > /dev/null; then
exit 0
fi
if ! $PYTHON -m pylint --errors-only topydo test; then
exit 1
fi
exit 0
[bdist_wheel]
universal = 1
......@@ -20,12 +20,6 @@ def find_version(*file_paths):
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
conditional_dependencies = {
"colorama>=0.2.5": "win32" in sys.platform,
}
setup(
name = "topydo",
packages = find_packages(exclude=["test"]),
......@@ -35,13 +29,16 @@ setup(
author_email = "me@bramschoenmakers.nl",
url = "https://github.com/bram85/topydo",
install_requires = [
'six >= 1.9.0',
] + [p for p, cond in conditional_dependencies.items() if cond],
'arrow >= 0.7.0',
],
extras_require = {
':sys_platform=="win32"': ['colorama>=0.2.5'],
':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'],
'ical': ['icalendar'],
'prompt-toolkit': ['prompt-toolkit >= 0.47'],
'urwid': ['urwid >= 1.3.0'],
'edit-cmd-tests': ['mock'],
'prompt-toolkit': ['prompt-toolkit >= 0.53'],
'test': ['coverage', 'freezegun', 'green', ],
'test:python_version=="3.2"': ['mock'],
},
entry_points= {
'console_scripts': ['topydo = topydo.cli.UILoader:main'],
......@@ -52,10 +49,12 @@ setup(
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Natural Language :: English",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Utilities",
],
long_description = """\
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,10 +14,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Utils import escape_ansi
from test.TopydoTest import TopydoTest
class CommandTest(TopydoTest):
def __init__(self, *args, **kwargs):
......@@ -32,6 +31,3 @@ class CommandTest(TopydoTest):
def error(self, p_error):
if p_error:
self.errors += escape_ansi(p_error + "\n")
if __name__ == '__main__':
unittest.main()
[topydo]
colors = junk
[add]
auto_creation_date = junk
[ls]
indent = junk
list_limit = junk
human_readable_dates = junk
[sort]
keep_sorted = junk
ignore_weekends = junk
[dep]
append_parent_projects = junk
append_parent_contexts = junk
[colorscheme]
priority_colors = junk
project_color = junk
context_color = junk
metadata_color = junk
link_color = junk
[colorscheme]
; priority_colors = junk
; project_color = junk
; context_color = junk
; metadata_color = junk
; link_color = junk
[{"completed": false, "completion_date": null, "contexts": ["Context2"], "creation_date": null, "priority": "C", "projects": ["Project1"], "source": "(C) Foo @Context2 Not@Context +Project1 Not+Project", "tags": [], "text": "Foo @Context2 Not@Context +Project1 Not+Project"}, {"completed": false, "completion_date": null, "contexts": [], "creation_date": null, "priority": "C", "projects": [], "source": "(C) Drink beer @ home", "tags": [], "text": "Drink beer @ home"}, {"completed": false, "completion_date": null, "contexts": [], "creation_date": null, "priority": "C", "projects": [], "source": "(C) 13 + 29 = 42", "tags": [], "text": "13 + 29 = 42"}, {"completed": false, "completion_date": null, "contexts": ["Context1"], "creation_date": null, "priority": "D", "projects": ["Project2"], "source": "(D) Bar @Context1 +Project2 p:1", "tags": [["p", "1"]], "text": "Bar @Context1 +Project2"}]
[{"completed": false, "completion_date": null, "contexts": ["Context2"], "creation_date": "2015-11-05", "priority": "C", "projects": ["Project1"], "source": "(C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project", "tags": [], "text": "Foo @Context2 Not@Context +Project1 Not+Project"}, {"completed": false, "completion_date": null, "contexts": [], "creation_date": null, "priority": "C", "projects": [], "source": "(C) Drink beer @ home", "tags": [], "text": "Drink beer @ home"}, {"completed": false, "completion_date": null, "contexts": [], "creation_date": null, "priority": "C", "projects": [], "source": "(C) 13 + 29 = 42", "tags": [], "text": "13 + 29 = 42"}, {"completed": false, "completion_date": null, "contexts": ["Context1"], "creation_date": null, "priority": "D", "projects": ["Project2"], "source": "(D) Bar @Context1 +Project2 p:1", "tags": [["p", "1"]], "text": "Bar @Context1 +Project2"}]
(C) Foo @Context2 Not@Context +Project1 Not+Project
(C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project
(D) Bar @Context1 +Project2 p:1
(C) Baz @Context1 +Project1 key:value id:1
(C) Drink beer @ home
......
(A) item 1
(B) item 2
(C) item 3
(D) item 4
(E) item 5
(F) item 6
(G) item 7
(H) item 8
(I) item 9
(J) item 10
(K) item 11
(L) item 12
(M) item 13
(N) item 14
(O) item 15
(P) item 16
(Q) item 17
(R) item 18
(S) item 19
(T) item 20
(U) item 21
(V) item 22
(W) item 23
(X) item 24
(Y) item 25
(Z) item 26
(A) item 27
(B) item 28
(C) item 29
(D) item 30
(E) item 31
(F) item 32
(G) item 33
(H) item 34
(I) item 35
(J) item 36
(K) item 37
(L) item 38
(M) item 39
(N) item 40
(O) item 41
(P) item 42
(Q) item 43
(R) item 44
(S) item 45
(T) item 46
(U) item 47
(V) item 48
(W) item 49
(X) item 50
(D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
(Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox +jumped over the lazy:bar and jar due:2015-11-08 t:2015-11-07
(C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
(C) Baz @Context1 +Project1 key:value
Drink beer @ home id:1 p:2 ical:foobar
x 2014-12-12 Completed but with date:2014-12-12
Todo item with +project3 @context1
Todo item with +Project2 @Context2
Todo item with +project1 @context3
Todo item with +project1 @context3
Todo item with +Project2 @Context2
Todo item with +project3 @context1
Todo item with +Project2 @Context2
Todo item with +project1 @context3
Todo item with +project3 @context1
[aliases]
foo = rm -f test
baz = FooBar
format = ls -F "|I| x c d {(}p{)} s k" -n 25
smile = ls
star = tag {} star 1
[ls]
list_format = |%I| %x %{(}p{)} %c %S\t%K
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -19,6 +19,7 @@ from topydo.lib.Todo import Todo
from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList
def load_file(p_filename):
"""
Loads a todo file from the given filename and returns a list of todos.
......@@ -26,6 +27,7 @@ def load_file(p_filename):
todolist = load_file_to_raw_list(p_filename)
return [Todo(src) for src in todolist]
def load_file_to_raw_list(p_filename):
"""
Loads a todo file from the given filename and returns a list of todo
......@@ -34,6 +36,7 @@ def load_file_to_raw_list(p_filename):
todofile = TodoFile(p_filename)
return todofile.read()
def load_file_to_todolist(p_filename):
"""
Loads a todo file to a TodoList instance.
......@@ -41,10 +44,12 @@ def load_file_to_todolist(p_filename):
todolist = load_file_to_raw_list(p_filename)
return TodoList(todolist)
def todolist_to_string(p_list):
""" Converts a todo list to a single string. """
return '\n'.join([t.source() for t in p_list])
def print_view(p_view):
printer = PrettyPrinter()
return printer.print_list(p_view.todos)
......@@ -14,8 +14,14 @@
# 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 datetime import date
import unittest
from datetime import date
from io import StringIO
from test.command_testcase import CommandTest
from topydo.commands import AddCommand, ListCommand
from topydo.lib import TodoList
from topydo.lib.Config import config
# We're searching for 'mock'
# pylint: disable=no-name-in-module
......@@ -24,14 +30,6 @@ try:
except ImportError:
import mock
from six import u
from io import StringIO
from topydo.commands import AddCommand
from topydo.commands import ListCommand
from test.CommandTest import CommandTest
from topydo.lib.Config import config
from topydo.lib import TodoList
class AddCommandTest(CommandTest):
def setUp(self):
......@@ -41,247 +39,318 @@ class AddCommandTest(CommandTest):
def test_add_task(self):
args = ["New todo"]
command = AddCommand.AddCommand(args, self.todolist, self.out, self.error)
command = AddCommand.AddCommand(args, self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " New todo")
self.assertEqual(self.todolist.todo(1).source(),
self.today + " New todo")
self.assertEqual(self.errors, "")
def test_add_multiple_args(self):
args = ["New", "todo"]
command = AddCommand.AddCommand(args, self.todolist, self.out, self.error)
command = AddCommand.AddCommand(args, self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " New todo")
self.assertEqual(self.todolist.todo(1).source(),
self.today + " New todo")
self.assertEqual(self.errors, "")
def test_add_priority1(self):
command = AddCommand.AddCommand(["Foo (C)"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Foo (C)"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).priority(), 'C')
self.assertEqual(self.todolist.todo(1).source(), "(C) " + self.today + " Foo")
self.assertEqual(self.todolist.todo(1).source(),
"(C) " + self.today + " Foo")
self.assertEqual(self.errors, "")
def test_add_priority2(self):
command = AddCommand.AddCommand(["Foo (CC)"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Foo (CC)"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).priority(), None)
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo (CC)")
self.assertEqual(self.todolist.todo(1).source(),
self.today + " Foo (CC)")
self.assertEqual(self.errors, "")
def test_add_priority3(self):
command = AddCommand.AddCommand(["Fo(C)o"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Fo(C)o"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).priority(), None)
self.assertEqual(self.todolist.todo(1).source(), self.today + " Fo(C)o" )
self.assertEqual(self.todolist.todo(1).source(),
self.today + " Fo(C)o")
self.assertEqual(self.errors, "")
def test_add_priority4(self):
command = AddCommand.AddCommand(["(C) Foo"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["(C) Foo"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).priority(), 'C')
self.assertEqual(self.todolist.todo(1).source(), "(C) " + self.today + " Foo")
self.assertEqual(self.todolist.todo(1).source(),
"(C) " + self.today + " Foo")
self.assertEqual(self.errors, "")
def test_add_dep1(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error)
def test_add_dep01(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Bar before:1"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar before:1"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar p:1")
self.assertEqual(self.todolist.todo(1).source(),
self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(2).source(),
self.today + " Bar p:1")
self.assertEqual(self.errors, "")
def test_add_dep2(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error)
def test_add_dep02(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Bar partof:1"], self.todolist)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar p:1")
self.assertEqual(self.todolist.todo(1).source(),
self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(2).source(),
self.today + " Bar p:1")
self.assertEqual(self.errors, "")
def test_add_dep3(self):
def test_add_dep03(self):
command = AddCommand.AddCommand(["Foo"], self.todolist)
command.execute()
command = AddCommand.AddCommand(["Bar after:1"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar after:1"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo p:1")
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar id:1")
self.assertEqual(self.todolist.todo(1).source(),
self.today + " Foo p:1")
self.assertEqual(self.todolist.todo(2).source(),
self.today + " Bar id:1")
self.assertEqual(self.errors, "")
def test_add_dep4(self):
def test_add_de04(self):
""" Test for using an after: tag with non-existing value. """
command = AddCommand.AddCommand(["Foo after:1"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Foo after:1"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.todo(1).has_tag("after"))
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo")
self.assertEqual(self.output, "| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.output,
"| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.errors, "")
def test_add_dep5(self):
def test_add_dep05(self):
""" Test for using an after: tag with non-existing value. """
command = AddCommand.AddCommand(["Foo after:2"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Foo after:2"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.todo(1).has_tag("after"))
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo")
self.assertEqual(self.output, "| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.output,
"| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.errors, "")
def test_add_dep6(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error)
def test_add_dep06(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Bar"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Baz before:1 before:2"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Baz before:1 before:2"],
self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar id:2")
self.assertEqual(self.todolist.todo(3).source(), self.today + " Baz p:1 p:2")
self.assertEqual(self.todolist.todo(1).source(),
self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(2).source(),
self.today + " Bar id:2")
self.assertEqual(self.todolist.todo(3).source(),
self.today + " Baz p:1 p:2")
self.assertEqual(self.errors, "")
def test_add_dep7(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error)
def test_add_dep07(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Bar"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Baz after:1 after:2"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Baz after:1 after:2"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo p:1")
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar p:1")
self.assertEqual(self.todolist.todo(3).source(), self.today + " Baz id:1")
self.assertEqual(self.todolist.todo(1).source(),
self.today + " Foo p:1")
self.assertEqual(self.todolist.todo(2).source(),
self.today + " Bar p:1")
self.assertEqual(self.todolist.todo(3).source(),
self.today + " Baz id:1")
self.assertEqual(self.errors, "")
def test_add_dep8(self):
def test_add_dep08(self):
config("test/data/todolist-uid.conf")
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute()
command = AddCommand.AddCommand(["Bar after:7ui"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar after:7ui"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo('7ui').source(), "{} Foo p:1".format(self.today))
self.assertEqual(self.todolist.todo('8to').source(), "{} Bar id:1".format(self.today))
self.assertEqual(self.todolist.todo('7ui').source(),
"{} Foo p:1".format(self.today))
self.assertEqual(self.todolist.todo('8to').source(),
"{} Bar id:1".format(self.today))
def test_add_dep9(self):
def test_add_dep09(self):
"""
The text ID shown after adding and after an 'ls' must be equal."
The text ID shown after adding and after an 'ls' must be equal.
By appending the parent's projects, the textual ID may change.
"""
config("test/data/todolist-uid-projects.conf")
# pass identitiy function to for writing output, we're not interested
# in this output
command = AddCommand.AddCommand(["Foo +Project"], self.todolist, lambda t: t, self.error)
command = AddCommand.AddCommand(["Foo +Project"], self.todolist,
lambda t: t, self.error)
command.execute()
command = AddCommand.AddCommand(["Bar before:kh0"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar before:kh0"], self.todolist,
self.out, self.error)
command.execute()
command = ListCommand.ListCommand(["Bar"], self.todolist, self.out, self.error)
command = ListCommand.ListCommand(["Bar"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "|kbn| {today} Bar p:1 +Project\n|kbn| {today} Bar +Project\n".format(today=self.today))
def test_add_dep10(self):
"""
The text ID shown after adding and after an 'ls' must be equal."
The text ID shown after adding and after an 'ls' must be equal.
By appending the parent's contexts, the textual ID may change.
"""
config("test/data/todolist-uid-contexts.conf")
# pass identitiy function to for writing output, we're not interested
# in this output
command = AddCommand.AddCommand(["Foo @Context"], self.todolist, lambda t: t, self.error)
command = AddCommand.AddCommand(["Foo @Context"], self.todolist,
lambda t: t, self.error)
command.execute()
command = AddCommand.AddCommand(["Bar before:2a2"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar before:2a2"], self.todolist,
self.out, self.error)
command.execute()
command = ListCommand.ListCommand(["Bar"], self.todolist, self.out, self.error)
command = ListCommand.ListCommand(["Bar"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "|wb3| {today} Bar p:1 @Context\n|wb3| {today} Bar @Context\n".format(today=self.today))
def test_add_reldate1(self):
command = AddCommand.AddCommand(["Foo due:today"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Foo due:today"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo due:" + self.today)
self.assertEqual(self.todolist.todo(1).source(),
self.today + " Foo due:" + self.today)
self.assertEqual(self.errors, "")
def test_add_reldate2(self):
command = AddCommand.AddCommand(["Foo t:today due:today"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Foo t:today due:today"],
self.todolist, self.out, self.error)
command.execute()
result = "| 1| {} Foo t:{} due:{}\n".format(self.today, self.today, self.today)
result = "| 1| {} Foo t:{} due:{}\n".format(self.today, self.today,
self.today)
self.assertEqual(self.output, result)
self.assertEqual(self.errors, "")
def test_add_empty(self):
command = AddCommand.AddCommand([], self.todolist, self.out, self.error)
command = AddCommand.AddCommand([], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
def test_add_unicode(self):
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()
self.assertEqual(self.output, 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")))
@mock.patch("topydo.commands.AddCommand.stdin",
StringIO(u"Fo\u00f3 due:tod id:1\nB\u0105r before:1"))
def test_add_from_stdin(self):
command = AddCommand.AddCommand(["-f", "-"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["-f", "-"], self.todolist, self.out,
self.error)
command.execute()
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.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, 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 = 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_add_completed(self):
""" Add a command that is completed automatically. """
command = AddCommand.AddCommand(["x 2015-01-01 Already completed"],
self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.todo(1).is_completed())
self.assertEqual(self.output,
"| 1| x 2015-01-01 {} Already completed\n".format(self.today))
self.assertEqual(self.errors, "")
def test_help(self):
command = AddCommand.AddCommand(["help"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["help"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,10 +16,11 @@
import unittest
from test.command_testcase import CommandTest
from topydo.commands.AppendCommand import AppendCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
class AppendCommandTest(CommandTest):
def setUp(self):
super(AppendCommandTest, self).setUp()
......@@ -27,14 +28,16 @@ class AppendCommandTest(CommandTest):
self.todolist.add("Foo")
def test_append1(self):
command = AppendCommand([1, "Bar"], self.todolist, self.out, self.error)
command = AppendCommand([1, "Bar"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "| 1| Foo Bar\n")
self.assertEqual(self.errors, "")
def test_append2(self):
command = AppendCommand([2, "Bar"], self.todolist, self.out, self.error)
command = AppendCommand([2, "Bar"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "")
......@@ -55,7 +58,8 @@ class AppendCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n")
def test_append5(self):
command = AppendCommand([1, "Bar", "Baz"], self.todolist, self.out, self.error)
command = AppendCommand([1, "Bar", "Baz"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "| 1| Foo Bar Baz\n")
......@@ -80,7 +84,8 @@ class AppendCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,11 +16,12 @@
import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ArchiveCommand import ArchiveCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
from topydo.lib.TodoList import TodoList
class ArchiveCommandTest(CommandTest):
def test_archive(self):
todolist = load_file_to_todolist("test/data/ArchiveCommandTest.txt")
......@@ -36,4 +37,3 @@ class ArchiveCommandTest(CommandTest):
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -17,9 +17,11 @@
""" Tests for the colorscheme functionality. """
import unittest
from topydo.lib.Colors import Colors, NEUTRAL_COLOR
from test.topydo_testcase import TopydoTest
from topydo.lib.Colors import NEUTRAL_COLOR, Colors
from topydo.lib.Config import config
from test.TopydoTest import TopydoTest
class ColorsTest(TopydoTest):
def test_project_color1(self):
......@@ -69,7 +71,7 @@ class ColorsTest(TopydoTest):
color = Colors().get_context_color()
self.assertEqual(color, NEUTRAL_COLOR)
def test_metadata_color1(self):
config(p_overrides={('colorscheme', 'metadata_color'): '128'})
color = Colors().get_metadata_color()
......@@ -150,7 +152,7 @@ class ColorsTest(TopydoTest):
self.assertEqual(color['A'], NEUTRAL_COLOR)
self.assertEqual(color['B'], NEUTRAL_COLOR)
self.assertEqual(color['C'], NEUTRAL_COLOR)
self.assertEqual(color['C'], NEUTRAL_COLOR)
def test_empty_color_values(self):
config("test/data/ColorsTest5.conf")
......@@ -183,3 +185,6 @@ class ColorsTest(TopydoTest):
self.assertEqual(context_color, '\033[1;35m')
self.assertEqual(link_color, '\033[4;36m')
self.assertEqual(metadata_color, '\033[1;32m')
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from unittest import skip
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
class ConfigTest(TopydoTest):
def test_config01(self):
self.assertEqual(config("test/data/ConfigTest1.conf").default_command(), 'do')
def test_config02(self):
self.assertNotEqual(config("").default_command(), 'do')
def test_config03(self):
self.assertTrue(config("test/data/ConfigTest2.conf").ignore_weekends())
def test_config04(self):
""" Test that value in file is overridden by parameter. """
overrides = {
('topydo', 'default_command'): 'edit'
}
self.assertEqual(config("test/data/ConfigTest1.conf",
p_overrides=overrides).default_command(),
'edit')
def test_config05(self):
""" Bad colour switch value. """
# boolean settings must first be typecast to integers, because all
# strings evaulate to 'True'
self.assertEqual(config("test/data/ConfigTest4.conf").colors(),
bool(int(config().defaults["topydo"]["colors"])))
def test_config06(self):
""" Bad auto creation date switch value. """
self.assertEqual(config("test/data/ConfigTest4.conf").auto_creation_date(),
bool(int(config().defaults["add"]["auto_creation_date"])))
def test_config07(self):
""" Bad indent value. """
self.assertEqual(config("test/data/ConfigTest4.conf").list_indent(),
int(config().defaults["ls"]["indent"]))
def test_config08(self):
""" Bad list limit value. """
self.assertEqual(config("test/data/ConfigTest4.conf").list_limit(),
int(config().defaults["ls"]["list_limit"]))
def test_config10(self):
""" Bad keep sorted switch value. """
self.assertEqual(config("test/data/ConfigTest4.conf").keep_sorted(),
bool(int(config().defaults["sort"]["keep_sorted"])))
def test_config11(self):
""" Bad ignore weekends switch value. """
self.assertEqual(config("test/data/ConfigTest4.conf").ignore_weekends(),
bool(int(config().defaults["sort"]["ignore_weekends"])))
def test_config12(self):
""" Bad append parent projects switch value. """
self.assertEqual(config("test/data/ConfigTest4.conf").append_parent_projects(),
bool(int(config().defaults["dep"]["append_parent_projects"])))
def test_config13(self):
""" Bad append parent project contexts switch value. """
self.assertEqual(config("test/data/ConfigTest4.conf").append_parent_contexts(),
bool(int(config().defaults["dep"]["append_parent_contexts"])))
@skip("Error checking not yet implemented")
def test_config14(self):
""" Bad priority color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").priority_colors(),
config().defaults["colorscheme"]["priority_colors"])
@skip("Error checking not yet implemented")
def test_config15(self):
""" Bad project color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").project_color(),
config().defaults["colorscheme"]["project_color"])
@skip("Error checking not yet implemented")
def test_config16(self):
""" Bad context color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").context_color(),
config().defaults["colorscheme"]["context_color"])
@skip("Error checking not yet implemented")
def test_config17(self):
""" Bad metadata color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").metadata_color(),
config().defaults["colorscheme"]["metadata_color"])
@skip("Error checking not yet implemented")
def test_config18(self):
""" Bad link color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").link_color(),
config().defaults["colorscheme"]["link_color"])
@skip("Test not yet implemented")
# the test needs to be of the internal function _str_to_dict
def test_config19(self):
""" No priority color value. """
self.assertEqual(config("test/data/ConfigTest4.conf").priority_colors(),
config().defaults["colorscheme"]["priority_colors"])
def test_config20(self):
""" No project color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").project_color(),
config().defaults["colorscheme"]["project_color"])
def test_config21(self):
""" No context color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").context_color(),
config().defaults["colorscheme"]["context_color"])
def test_config22(self):
""" No metadata color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").metadata_color(),
config().defaults["colorscheme"]["metadata_color"])
def test_config23(self):
""" No link color value. """
self.assertEqual(config("test/data/ConfigTest5.conf").link_color(),
config().defaults["colorscheme"]["link_color"])
if __name__ == '__main__':
unittest.main()
......@@ -15,20 +15,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from six import u
from test.CommandTest import CommandTest
from topydo.lib.Config import config
from test.command_testcase import CommandTest
from topydo.commands.DeleteCommand import DeleteCommand
from topydo.lib.Config import config
from topydo.lib.TodoList import TodoList
from topydo.lib.TodoListBase import InvalidTodoException
def _yes_prompt(self):
return "y"
def _no_prompt(self):
return "n"
class DeleteCommandTest(CommandTest):
def setUp(self):
super(DeleteCommandTest, self).setUp()
......@@ -42,7 +44,8 @@ class DeleteCommandTest(CommandTest):
self.todolist = TodoList(todos)
def test_del1(self):
command = DeleteCommand(["1"], self.todolist, self.out, self.error, _no_prompt)
command = DeleteCommand(["1"], self.todolist, self.out, self.error,
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -51,7 +54,8 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_del1_regex(self):
command = DeleteCommand(["Foo"], self.todolist, self.out, self.error, _no_prompt)
command = DeleteCommand(["Foo"], self.todolist, self.out, self.error,
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -60,29 +64,33 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_del2(self):
command = DeleteCommand(["1"], self.todolist, self.out, self.error, _yes_prompt)
command = DeleteCommand(["1"], self.todolist, self.out, self.error,
_yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
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, "")
def test_del3(self):
command = DeleteCommand(["-f", "1"], self.todolist, self.out, self.error, _yes_prompt)
command = DeleteCommand(["-f", "1"], self.todolist, self.out,
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
def test_del4(self):
command = DeleteCommand(["--force", "1"], self.todolist, self.out, self.error, _yes_prompt)
command = DeleteCommand(["--force", "1"], self.todolist, self.out,
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.todolist.count(), 3) # force won't delete subtasks
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "")
......@@ -125,7 +133,8 @@ class DeleteCommandTest(CommandTest):
def test_multi_del1(self):
""" Test deletion of multiple items. """
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()
result = "a @test with due:2015-06-03\na @test with +project"
......@@ -135,7 +144,8 @@ class DeleteCommandTest(CommandTest):
def test_multi_del2(self):
""" 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()
result = "a @test with due:2015-06-03\na @test with +project"
......@@ -145,7 +155,8 @@ class DeleteCommandTest(CommandTest):
def test_multi_del3(self):
""" Fail if any of supplied todo numbers is invalid. """
command = DeleteCommand(["99", "2"], self.todolist, self.out, self.error, _yes_prompt)
command = DeleteCommand(["99", "2"], self.todolist, self.out,
self.error, _yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -154,7 +165,8 @@ class DeleteCommandTest(CommandTest):
def test_multi_del4(self):
""" Check output when all supplied todo numbers are invalid. """
command = DeleteCommand(["99", "A"], self.todolist, self.out, self.error, _yes_prompt)
command = DeleteCommand(["99", "A"], self.todolist, self.out,
self.error, _yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -162,16 +174,21 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: A.\n")
def test_multi_del5(self):
""" Throw an error with invalid argument containing special characters. """
command = DeleteCommand([u("Fo\u00d3B\u0105r"), "Bar"], self.todolist, self.out, self.error, None)
"""
Throw an error with invalid argument containing special characters.
"""
command = DeleteCommand([u"Fo\u00d3B\u0105r", "Bar"], self.todolist,
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
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 = 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"
......@@ -182,7 +199,8 @@ class DeleteCommandTest(CommandTest):
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 = DeleteCommand(["-e", "@test", "due:2015-06-03"],
self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -190,14 +208,16 @@ class DeleteCommandTest(CommandTest):
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 = 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 = DeleteCommand(["-e", ""], self.todolist, self.out,
self.error, None)
command.execute()
result = "Foo"
......@@ -208,7 +228,8 @@ class DeleteCommandTest(CommandTest):
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 = DeleteCommand(["-xe", ""], self.todolist, self.out,
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -227,7 +248,8 @@ class DeleteCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,10 +16,11 @@
import unittest
from test.command_testcase import CommandTest
from topydo.commands.DepCommand import DepCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
class DepCommandTest(CommandTest):
def setUp(self):
super(DepCommandTest, self).setUp()
......@@ -35,7 +36,8 @@ class DepCommandTest(CommandTest):
self.todolist = TodoList(todos)
def test_add1(self):
command = DepCommand(["add", "1", "to", "4"], self.todolist, self.out, self.error)
command = DepCommand(["add", "1", "to", "4"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -44,7 +46,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_add2(self):
command = DepCommand(["add", "1", "4"], self.todolist, self.out, self.error)
command = DepCommand(["add", "1", "4"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -53,7 +56,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_add3(self):
command = DepCommand(["add", "99", "3"], self.todolist, self.out, self.error)
command = DepCommand(["add", "99", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -61,7 +65,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_add4(self):
command = DepCommand(["add", "A", "3"], self.todolist, self.out, self.error)
command = DepCommand(["add", "A", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -77,7 +82,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n")
def test_add6(self):
command = DepCommand(["add", "1", "after", "4"], self.todolist, self.out, self.error)
command = DepCommand(["add", "1", "after", "4"], self.todolist,
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -86,7 +92,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_add7(self):
command = DepCommand(["add", "1", "before", "4"], self.todolist, self.out, self.error)
command = DepCommand(["add", "1", "before", "4"], self.todolist,
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -95,7 +102,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_add8(self):
command = DepCommand(["add", "1", "partof", "4"], self.todolist, self.out, self.error)
command = DepCommand(["add", "1", "partof", "4"], self.todolist,
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -104,7 +112,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_add9(self):
command = DepCommand(["add", "Foo", "to", "4"], self.todolist, self.out, self.error)
command = DepCommand(["add", "Foo", "to", "4"], self.todolist,
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -117,7 +126,6 @@ class DepCommandTest(CommandTest):
Helper function that checks the removal of the dependency from todo 1
to todo 3.
"""
command = DepCommand(p_args, self.todolist, self.out, self.error)
command.execute()
......@@ -131,16 +139,17 @@ class DepCommandTest(CommandTest):
self.rm_helper(["rm", "1", "to", "3"])
def test_rm2(self):
self.rm_helper(["rm", "1", "3"])
self.rm_helper(["rm", "1", "3"])
def test_del1(self):
self.rm_helper(["del", "1", "to", "3"])
def test_del2(self):
self.rm_helper(["del", "1", "3"])
self.rm_helper(["del", "1", "3"])
def test_rm3(self):
command = DepCommand(["rm", "99", "3"], self.todolist, self.out, self.error)
command = DepCommand(["rm", "99", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -148,7 +157,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_rm4(self):
command = DepCommand(["rm", "A", "3"], self.todolist, self.out, self.error)
command = DepCommand(["rm", "A", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -164,7 +174,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n")
def test_ls1(self):
command = DepCommand(["ls", "1", "to"], self.todolist, self.out, self.error)
command = DepCommand(["ls", "1", "to"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -172,7 +183,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_ls2(self):
command = DepCommand(["ls", "99", "to"], self.todolist, self.out, self.error)
command = DepCommand(["ls", "99", "to"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -180,7 +192,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_ls3(self):
command = DepCommand(["ls", "to", "3"], self.todolist, self.out, self.error)
command = DepCommand(["ls", "to", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -188,7 +201,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_ls4(self):
command = DepCommand(["ls", "to", "99"], self.todolist, self.out, self.error)
command = DepCommand(["ls", "to", "99"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -212,7 +226,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n")
def test_ls7(self):
command = DepCommand(["ls", "top", "99"], self.todolist, self.out, self.error)
command = DepCommand(["ls", "top", "99"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -220,7 +235,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n")
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()
self.assertTrue(self.todolist.is_dirty())
......@@ -255,7 +271,8 @@ class DepCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -15,12 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from six import u
from test.command_testcase import CommandTest
from topydo.commands.DepriCommand import DepriCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
class DepriCommandTest(CommandTest):
def setUp(self):
super(DepriCommandTest, self).setUp()
......@@ -63,7 +63,8 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_depri4(self):
command = DepriCommand(["1","Baz"], self.todolist, self.out, self.error)
command = DepriCommand(["1", "Baz"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -73,10 +74,10 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_expr_depri1(self):
command = DepriCommand(["-e", "@test"], self.todolist, self.out, self.error, None)
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())
......@@ -84,7 +85,8 @@ class DepriCommandTest(CommandTest):
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 = 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"
......@@ -94,29 +96,30 @@ class DepriCommandTest(CommandTest):
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 = 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 = 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 = 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)
command.execute()
......@@ -126,7 +129,8 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_invalid2(self):
command = DepriCommand(["99", "1"], self.todolist, self.out, self.error)
command = DepriCommand(["99", "1"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -134,7 +138,8 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
def test_invalid3(self):
command = DepriCommand(["99", "FooBar"], self.todolist, self.out, self.error)
command = DepriCommand(["99", "FooBar"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -142,13 +147,17 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: FooBar.\n")
def test_invalid4(self):
""" Throw an error with invalid argument containing special characters. """
command = DepriCommand([u("Fo\u00d3B\u0105r"), "Bar"], self.todolist, self.out, self.error, None)
"""
Throw an error with invalid argument containing special characters.
"""
command = DepriCommand([u"Fo\u00d3B\u0105r", "Bar"], self.todolist,
self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(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_empty(self):
command = DepriCommand([], self.todolist, self.out, self.error)
......@@ -163,7 +172,8 @@ class DepriCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
......@@ -14,20 +14,22 @@
# 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 datetime import date, timedelta
import unittest
from six import u
from datetime import date, timedelta
from test.command_testcase import CommandTest
from topydo.commands.DoCommand import DoCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
def _yes_prompt(self):
return "y"
def _no_prompt(self):
return "n"
class DoCommandTest(CommandTest):
def setUp(self):
super(DoCommandTest, self).setUp()
......@@ -55,17 +57,19 @@ class DoCommandTest(CommandTest):
self.tomorrow = self.tomorrow.isoformat()
def test_do1(self):
command = DoCommand(["3"], self.todolist, self.out, self.error, _no_prompt)
command = DoCommand(["3"], self.todolist, self.out, self.error,
_no_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output, "Completed: x {} Baz p:1\n".format(
self.today))
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.errors, "")
def test_do_subtasks1(self):
command = DoCommand(["1"], self.todolist, self.out, self.error, _yes_prompt)
command = DoCommand(["1"], self.todolist, self.out, self.error,
_yes_prompt)
command.execute()
result = "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {today} Bar p:1\nCompleted: x {today} Baz p:1\nCompleted: x {today} Foo id:1\n".format(today=self.today)
......@@ -79,7 +83,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_do_subtasks2(self):
command = DoCommand(["1"], self.todolist, self.out, self.error, _no_prompt)
command = DoCommand(["1"], self.todolist, self.out, self.error,
_no_prompt)
command.execute()
result = "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {} Foo id:1\n".format(self.today)
......@@ -98,7 +103,8 @@ class DoCommandTest(CommandTest):
global prompt_shown
prompt_shown = True
command = DoCommand(["-f", "1"], self.todolist, self.out, self.error, prompt)
command = DoCommand(["-f", "1"], self.todolist, self.out, self.error,
prompt)
command.execute()
self.assertFalse(prompt_shown)
......@@ -112,7 +118,8 @@ class DoCommandTest(CommandTest):
global prompt_shown
prompt_shown = True
command = DoCommand(["--force", "1"], self.todolist, self.out, self.error, prompt)
command = DoCommand(["--force", "1"], self.todolist, self.out,
self.error, prompt)
command.execute()
self.assertFalse(prompt_shown)
......@@ -168,7 +175,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_invalid3(self):
command = DoCommand(["01"], self.todolist, self.out, self.error, _yes_prompt)
command = DoCommand(["01"], self.todolist, self.out, self.error,
_yes_prompt)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -202,7 +210,8 @@ class DoCommandTest(CommandTest):
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.todolist.todo(5).completion_date(), date(2014, 10, 18))
self.assertEqual(self.todolist.todo(5).completion_date(),
date(2014, 10, 18))
self.assertFalse(self.output)
self.assertEqual(self.errors, "Todo has already been completed.\n")
......@@ -212,11 +221,13 @@ class DoCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output, "Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.errors, "")
def test_do_custom_date1(self):
command = DoCommand(["-d", "2014-11-18", "3"], self.todolist, self.out, self.error)
command = DoCommand(["-d", "2014-11-18", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -224,7 +235,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_do_custom_date2(self):
command = DoCommand(["-d", "2014-11-18", "1"], self.todolist, self.out, self.error, _yes_prompt)
command = DoCommand(["-d", "2014-11-18", "1"], self.todolist, self.out,
self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -232,7 +244,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_do_custom_date3(self):
command = DoCommand(["--date=2014-11-18", "3"], self.todolist, self.out, self.error)
command = DoCommand(["--date=2014-11-18", "3"], self.todolist,
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -240,11 +253,13 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_do_custom_date4(self):
command = DoCommand(["-d", "foo", "3"], self.todolist, self.out, self.error)
command = DoCommand(["-d", "foo", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.errors, "")
def test_do_custom_date5(self):
......@@ -252,7 +267,8 @@ class DoCommandTest(CommandTest):
Make sure that the new recurrence date is correct when a custom
date is given.
"""
command = DoCommand(["-d", self.yesterday, "4"], self.todolist, self.out, self.error)
command = DoCommand(["-d", self.yesterday, "4"], self.todolist,
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -265,7 +281,8 @@ class DoCommandTest(CommandTest):
due date as the offset. This todo item however, has no due date, then
the completion date must be used as an offset.
"""
command = DoCommand(["-s", "-d", self.yesterday, "4"], self.todolist, self.out, self.error)
command = DoCommand(["-s", "-d", self.yesterday, "4"], self.todolist,
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -277,15 +294,43 @@ class DoCommandTest(CommandTest):
When a custom date is set, strict recurrence must still hold on to the
due date as the offset.
"""
command = DoCommand(["-s", "-d", self.yesterday, "8"], self.todolist, self.out, self.error)
command = DoCommand(["-s", "-d", self.yesterday, "8"], self.todolist,
self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 12| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {yesterday} Strict due:2014-01-01 rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "")
def test_do_custom_date8(self):
"""
Convert relative completion dates to an absolute date (yesterday).
"""
command = DoCommand(["-d", "yesterday", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.yesterday))
self.assertEqual(self.errors, "")
def test_do_custom_date9(self):
"""
Convert relative completion dates to an absolute date (-1d)
"""
command = DoCommand(["-d", "-1d", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.yesterday))
self.assertEqual(self.errors, "")
def test_multi_do1(self):
command = DoCommand(["1", "3"], self.todolist, self.out, self.error, _yes_prompt)
command = DoCommand(["1", "3"], self.todolist, self.out, self.error,
_yes_prompt)
command.execute()
self.assertTrue(self.todolist.todo(1).is_completed())
......@@ -295,7 +340,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.output, "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {today} Bar p:1\nCompleted: x {today} Baz p:1\nCompleted: x {today} Foo id:1\n".format(today=self.today))
def test_multi_do2(self):
command = DoCommand(["1", "3"], self.todolist, self.out, self.error, _no_prompt)
command = DoCommand(["1", "3"], self.todolist, self.out, self.error,
_no_prompt)
command.execute()
self.assertTrue(self.todolist.todo(1).is_completed())
......@@ -305,14 +351,17 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.output, "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {today} Foo id:1\nCompleted: x {today} Baz p:1\n".format(today=self.today))
def test_multi_do3(self):
command = DoCommand(["3", "3"], self.todolist, self.out, self.error, _no_prompt)
command = DoCommand(["3", "3"], self.todolist, self.out, self.error,
_no_prompt)
command.execute()
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output, "Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.output,
"Completed: x {} Baz p:1\n".format(self.today))
def test_multi_do4(self):
command = DoCommand(["99", "3"], self.todolist, self.out, self.error, _no_prompt)
command = DoCommand(["99", "3"], self.todolist, self.out, self.error,
_no_prompt)
command.execute()
self.assertFalse(self.todolist.todo(3).is_completed())
......@@ -322,21 +371,27 @@ class DoCommandTest(CommandTest):
"""
Check output when all supplied todo numbers are invalid.
"""
command = DoCommand(["99", "15"], 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: 15.\n")
def test_multi_do6(self):
""" Throw an error with invalid argument containing special characters. """
command = DoCommand([u("Fo\u00d3B\u0105r"), "Bar"], self.todolist, self.out, self.error, None)
"""
Throw an error with invalid argument containing special characters.
"""
command = DoCommand([u"Fo\u00d3B\u0105r", "Bar"], self.todolist,
self.out, self.error, None)
command.execute()
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 = DoCommand(["-e", "@test"], self.todolist, self.out,
self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -344,7 +399,8 @@ class DoCommandTest(CommandTest):
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 = DoCommand(["-e", "@test", "due:2015-06-03"], self.todolist,
self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -352,21 +408,24 @@ class DoCommandTest(CommandTest):
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 = 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 = 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 = 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)
......@@ -376,11 +435,15 @@ class DoCommandTest(CommandTest):
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)
"""
Show error message when an item has an invalid recurrence pattern.
"""
command = DoCommand(["9"], self.todolist, self.out, self.error,
_no_prompt)
command.execute()
self.assertEqual(self.output, "Completed: x {} Invalid rec:1\n".format(self.today))
self.assertEqual(self.output,
"Completed: x {} Invalid rec:1\n".format(self.today))
self.assertEqual(self.errors, "Warning: todo item has an invalid recurrence pattern.\n")
def test_empty(self):
......@@ -396,7 +459,8 @@ class DoCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
......@@ -14,8 +14,15 @@
# 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 os
import unittest
from test.command_testcase import CommandTest
from topydo.commands.EditCommand import EditCommand
from topydo.lib.Config import config
from topydo.lib.Todo import Todo
from topydo.lib.TodoList import TodoList
# We're searching for 'mock'
# pylint: disable=no-name-in-module
try:
......@@ -23,14 +30,6 @@ try:
except ImportError:
import mock
from six import u
import os
from topydo.commands.EditCommand import EditCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
from topydo.lib.Todo import Todo
from topydo.lib.Config import config
class EditCommandTest(CommandTest):
def setUp(self):
......@@ -39,105 +38,129 @@ class EditCommandTest(CommandTest):
"Foo id:1",
"Bar p:1 @test",
"Baz @test",
u("Fo\u00f3B\u0105\u017a"),
u"Fo\u00f3B\u0105\u017a",
]
self.todolist = TodoList(todos)
@mock.patch('topydo.commands.EditCommand._is_edited')
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit1(self, mock_open_in_editor):
def test_edit01(self, mock_open_in_editor, mock_todos_from_temp, mock_is_edited):
""" Preserve dependencies after editing. """
mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Foo id:1')]
mock_is_edited.return_value = True
command = EditCommand(["1"], self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), u("Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1"))
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.print_todos(), u"Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1")
@mock.patch('topydo.commands.EditCommand._is_edited')
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit2(self, mock_open_in_editor, mock_todos_from_temp):
def test_edit02(self, mock_open_in_editor, mock_todos_from_temp, mock_is_edited):
""" Edit some todo. """
mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Lazy Cat')]
mock_is_edited.return_value = True
command = EditCommand(["Bar"], self.todolist, self.out, self.error, None)
command = EditCommand(["Bar"], self.todolist, self.out, self.error,
None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat"))
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat")
def test_edit3(self):
def test_edit03(self):
""" Throw an error after invalid todo number given as argument. """
command = EditCommand(["FooBar"], self.todolist, self.out, self.error, None)
command = EditCommand(["FooBar"], self.todolist, self.out, self.error,
None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_edit4(self):
def test_edit04(self):
""" Throw an error with pointing invalid argument. """
command = EditCommand(["Bar", "5"], self.todolist, self.out, self.error, None)
command = EditCommand(["Bar", "5"], self.todolist, self.out,
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Invalid todo number given: 5.\n")
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit5(self, mock_open_in_editor, mock_todos_from_temp):
""" Don't let to delete todos acidentally while editing. """
mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Only one line')]
command = EditCommand(["1", "Bar"], self.todolist, self.out, self.error, None)
command.execute()
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.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. """
command = EditCommand([u("Fo\u00d3B\u0105r"), "Bar"], self.todolist, self.out, self.error, None)
def test_edit05(self):
"""
Throw an error with invalid argument containing special characters.
"""
command = EditCommand([u"Fo\u00d3B\u0105r", "Bar"], self.todolist,
self.out, self.error, None)
command.execute()
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")
@mock.patch('topydo.commands.EditCommand._is_edited')
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit7(self, mock_open_in_editor, mock_todos_from_temp):
def test_edit06(self, mock_open_in_editor, mock_todos_from_temp, mock_is_edited):
""" Edit todo with special characters. """
mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Lazy Cat')]
mock_is_edited.return_value = True
command = EditCommand([u("Fo\u00f3B\u0105\u017a")], self.todolist, self.out, self.error, None)
command = EditCommand([u"Fo\u00f3B\u0105\u017a"], self.todolist,
self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), 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._is_edited')
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit_expr(self, mock_open_in_editor, mock_todos_from_temp):
def test_edit07(self, mock_open_in_editor, mock_todos_from_temp, mock_is_edited):
""" Don't perform write if tempfile is unchanged """
mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Only one line')]
mock_is_edited.return_value = False
command = EditCommand(["1", "Bar"], self.todolist, self.out,
self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Editing aborted. Nothing to do.\n")
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nBar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a")
@mock.patch('topydo.commands.EditCommand._is_edited')
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit_expr(self, mock_open_in_editor, mock_todos_from_temp, mock_is_edited):
""" Edit todos matching expression. """
mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Lazy Cat'), Todo('Lazy Dog')]
mock_todos_from_temp.return_value = [Todo('Lazy Cat'),
Todo('Lazy Dog')]
mock_is_edited.return_value = True
command = EditCommand(["-e", "@test"], self.todolist, self.out, self.error, None)
command = EditCommand(["-e", "@test"], self.todolist, self.out,
self.error, None)
command.execute()
expected = 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.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, expected)
self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog"))
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog")
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_archive(self, mock_call):
......@@ -148,7 +171,8 @@ class EditCommandTest(CommandTest):
os.environ['EDITOR'] = editor
archive = config().archive()
command = EditCommand(["-d"], self.todolist, self.out, self.error, None)
command = EditCommand(["-d"], self.todolist, self.out, self.error,
None)
command.execute()
self.assertEqual(self.errors, "")
......@@ -163,7 +187,7 @@ class EditCommandTest(CommandTest):
os.environ['EDITOR'] = editor
todotxt = config().todotxt()
result = self.todolist.print_todos() # copy TodoList content *before* executing command
result = self.todolist.print_todos() # copy TodoList content *before* executing command
command = EditCommand([], self.todolist, self.out, self.error, None)
command.execute()
......@@ -172,5 +196,14 @@ class EditCommandTest(CommandTest):
self.assertEqual(self.todolist.print_todos(), result)
mock_call.assert_called_once_with([editor, todotxt])
def test_help(self):
command = EditCommand(["help"], self.todolist, self.out, self.error,
None)
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,23 +16,25 @@
""" Tests for the filter functionality. """
from datetime import date, timedelta
import unittest
from datetime import date, timedelta
from test.facilities import (load_file, load_file_to_todolist,
todolist_to_string)
from test.topydo_testcase import TopydoTest
from topydo.lib import Filter
from test.TestFacilities import load_file, todolist_to_string, load_file_to_todolist
from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
class FilterTest(TopydoTest):
def test_filter3(self):
def test_filter03(self):
todo = Todo("(C) Relevant")
relevance = Filter.RelevanceFilter()
result = relevance.filter([todo])
self.assertEqual(result, [todo])
def test_filter4(self):
def test_filter04(self):
""" Test case insensitive match. """
todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter('+project')
......@@ -40,10 +42,10 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest1a-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter5(self):
def test_filter05(self):
""" Test case sensitive match. """
todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter('+Project')
......@@ -51,10 +53,10 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest1b-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter6(self):
def test_filter06(self):
""" Test case sensitive match (forced, with lowercase). """
todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter('+project', True)
......@@ -62,10 +64,10 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest1c-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter7(self):
def test_filter07(self):
""" Tests the dependency filter. """
todolist = load_file_to_todolist('test/data/FilterTest2.txt')
depfilter = Filter.DependencyFilter(todolist)
......@@ -73,10 +75,10 @@ class FilterTest(TopydoTest):
filtered_todos = depfilter.filter(todolist.todos())
reference = load_file('test/data/FilterTest2-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter8(self):
def test_filter08(self):
""" Test case sensitive match (forced, with lowercase). """
todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter('+Project', False)
......@@ -84,11 +86,11 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest1a-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter9(self):
""" Test instance filter """
def test_filter09(self):
""" Test instance filter. """
todos = load_file('test/data/FilterTest1.txt')
instance_filter = Filter.InstanceFilter(todos[2:])
......@@ -97,7 +99,7 @@ class FilterTest(TopydoTest):
self.assertEqual(todos[2:], filtered_todos)
def test_filter10(self):
""" Test instance filter """
""" Test instance filter. """
todos = load_file('test/data/FilterTest1.txt')
instance_filter = Filter.InstanceFilter([])
......@@ -106,7 +108,7 @@ class FilterTest(TopydoTest):
self.assertEqual([], filtered_todos)
def test_filter11(self):
""" Test instance filter """
""" Test instance filter. """
todos = load_file('test/data/FilterTest1.txt')
instance_filter = Filter.InstanceFilter(todos[2:])
......@@ -131,7 +133,8 @@ class FilterTest(TopydoTest):
filtered_todos = limit_filter.filter(todos)
self.assertEqual(len(filtered_todos), 1)
self.assertEqual(filtered_todos[0].source(), '(C) This is part of some +Project')
self.assertEqual(filtered_todos[0].source(),
'(C) This is part of some +Project')
def test_filter14(self):
""" Test limit filter. """
......@@ -158,8 +161,8 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest3-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter17(self):
todos = load_file('test/data/FilterTest1.txt')
......@@ -170,8 +173,8 @@ class FilterTest(TopydoTest):
filtered_todos = andfilter.filter(todos)
reference = load_file('test/data/FilterTest4-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter18(self):
todos = load_file('test/data/FilterTest1.txt')
......@@ -182,8 +185,8 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest5-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter20(self):
todos = load_file('test/data/FilterTest3.txt')
......@@ -192,8 +195,8 @@ class FilterTest(TopydoTest):
filtered_todos = otf.filter(todos)
reference = load_file('test/data/FilterTest6-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \
todolist_to_string(reference))
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
def test_filter21(self):
todos = load_file('test/data/FilterTest3.txt')
......@@ -235,7 +238,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest8-result.txt')
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
todolist_to_string(reference))
def test_filter26(self):
todos = load_file('test/data/FilterTest3.txt')
......@@ -245,7 +248,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest9-result.txt')
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
todolist_to_string(reference))
def test_filter27(self):
todos = load_file('test/data/FilterTest3.txt')
......@@ -255,7 +258,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest10-result.txt')
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
todolist_to_string(reference))
def test_filter28(self):
todos = load_file('test/data/FilterTest3.txt')
......@@ -273,7 +276,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest11-result.txt')
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
todolist_to_string(reference))
def test_filter30(self):
todos = load_file('test/data/FilterTest3.txt')
......@@ -283,7 +286,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest12-result.txt')
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
todolist_to_string(reference))
def test_filter31(self):
todos = load_file('test/data/FilterTest3.txt')
......@@ -293,7 +296,8 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest13-result.txt')
self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference))
todolist_to_string(reference))
class OrdinalTagFilterTest(TopydoTest):
def setUp(self):
......@@ -374,6 +378,183 @@ class OrdinalTagFilterTest(TopydoTest):
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo3)
class CreationFilterTest(TopydoTest):
def setUp(self):
super(CreationFilterTest, self).setUp()
self.todo1 = "2015-12-19 With creation date."
self.todo2 = "Without creation date."
self.todos = [Todo(self.todo1), Todo(self.todo2)]
def test_filter1(self):
cf = Filter.CreationFilter('create:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter2(self):
cf = Filter.CreationFilter('creation:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter3(self):
cf = Filter.CreationFilter('created:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
class CompletionFilterTest(TopydoTest):
def setUp(self):
super(CompletionFilterTest, self).setUp()
self.todo1 = "2015-12-19 With creation date."
self.todo2 = "x 2015-12-19 2015-12-18 Without creation date."
self.todo3 = "x 2015-12-18 Without creation date."
self.todos = [Todo(self.todo1), Todo(self.todo2), Todo(self.todo3)]
def test_filter1(self):
cf = Filter.CompletionFilter('complete:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter2(self):
cf = Filter.CompletionFilter('completed:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter3(self):
cf = Filter.CompletionFilter('completion:2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo2)
def test_filter4(self):
cf = Filter.CompletionFilter('completion:<=2015-12-19')
result = cf.filter(self.todos)
self.assertEqual(len(result), 2)
self.assertEqual(result[0].source(), self.todo2)
self.assertEqual(result[1].source(), self.todo3)
class PriorityFilterTest(TopydoTest):
def setUp(self):
super(PriorityFilterTest, self).setUp()
self.todo1 = "(A) Foo"
self.todo2 = "(B) Bar"
self.todo3 = "(C) Baz"
self.todo4 = "(Z) FooBar"
self.todo5 = "FooBaz"
self.todos = [
Todo(self.todo1),
Todo(self.todo2),
Todo(self.todo3),
Todo(self.todo4),
Todo(self.todo5),
]
def test_filter1(self):
pf = Filter.PriorityFilter('(A)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter1a(self):
pf = Filter.PriorityFilter('(=A)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter2(self):
pf = Filter.PriorityFilter('(>B)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo1)
def test_filter3(self):
pf = Filter.PriorityFilter('(>=C)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 3)
self.assertEqual(result[0].source(), self.todo1)
self.assertEqual(result[1].source(), self.todo2)
self.assertEqual(result[2].source(), self.todo3)
def test_filter4(self):
pf = Filter.PriorityFilter('(<A)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 4)
self.assertEqual(result[0].source(), self.todo2)
self.assertEqual(result[1].source(), self.todo3)
self.assertEqual(result[2].source(), self.todo4)
self.assertEqual(result[3].source(), self.todo5)
def test_filter5(self):
pf = Filter.PriorityFilter('(<=C)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 3)
self.assertEqual(result[0].source(), self.todo3)
self.assertEqual(result[1].source(), self.todo4)
self.assertEqual(result[2].source(), self.todo5)
def test_filter6(self):
pf = Filter.PriorityFilter('(!B)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 4)
self.assertEqual(result[0].source(), self.todo1)
self.assertEqual(result[1].source(), self.todo3)
self.assertEqual(result[2].source(), self.todo4)
self.assertEqual(result[3].source(), self.todo5)
def test_filter7(self):
pf = Filter.PriorityFilter('(>A)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 0)
def test_filter8(self):
pf = Filter.PriorityFilter('(<Z)')
result = pf.filter(self.todos)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo5)
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from test.topydo_testcase import TopydoTest
from topydo.Commands import get_subcommand
from topydo.commands.AddCommand import AddCommand
from topydo.commands.DeleteCommand import DeleteCommand
from topydo.commands.ListCommand import ListCommand
from topydo.commands.ListProjectCommand import ListProjectCommand
from topydo.commands.TagCommand import TagCommand
from topydo.lib.Config import config
class GetSubcommandTest(TopydoTest):
def test_normal_cmd(self):
args = ["add"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, AddCommand))
def test_cmd_help(self):
args = ["help", "add"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, AddCommand))
self.assertEqual(final_args, ["help"])
def test_alias01(self):
config("test/data/aliases.conf")
args = ["foo"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, DeleteCommand))
self.assertEqual(final_args, ["-f", "test"])
def test_alias02(self):
config("test/data/aliases.conf")
args = ["format"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, ListCommand))
self.assertEqual(final_args, ["-F", "|I| x c d {(}p{)} s k", "-n", "25"])
def test_alias03(self):
config("test/data/aliases.conf")
args = ["smile"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, ListCommand))
self.assertEqual(final_args, [u"\u263b"])
def test_alias04(self):
config("test/data/aliases.conf")
args = ["star", "foo"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, TagCommand))
self.assertEqual(final_args, ["foo", "star", "1"])
def test_default_cmd01(self):
args = ["bar"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, ListCommand))
self.assertEqual(final_args, ["bar"])
def test_default_cmd02(self):
args = []
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, ListCommand))
self.assertEqual(final_args, [])
def test_alias_default_cmd01(self):
config("test/data/aliases.conf", {('topydo', 'default_command'): 'foo'})
args = ["bar"]
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, DeleteCommand))
self.assertEqual(final_args, ["-f", "test", "bar"])
def test_alias_default_cmd02(self):
config("test/data/aliases.conf", {('topydo', 'default_command'): 'foo'})
args = []
real_cmd, final_args = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, DeleteCommand))
self.assertEqual(final_args, ["-f", "test"])
def test_alias_default_cmd03(self):
config("test/data/aliases.conf", {('topydo', 'default_command'): 'nonexisting_default'})
args = ['nonexisting']
real_cmd, final_args = get_subcommand(args)
self.assertFalse(real_cmd)
self.assertEqual(final_args, ['nonexisting'])
def test_alias_default_cmd04(self):
config("test/data/aliases.conf", {('topydo', 'default_command'): 'nonexisting_default'})
args = []
real_cmd, final_args = get_subcommand(args)
self.assertFalse(real_cmd)
self.assertEqual(final_args, [])
def test_wrong_alias(self):
config("test/data/aliases.conf")
args = ["baz"]
real_cmd, final_args = get_subcommand(args)
self.assertEqual(real_cmd, None)
def test_help(self):
real_cmd, final_args = get_subcommand(['help', 'nonexisting'])
self.assertFalse(real_cmd)
self.assertEqual(final_args, ['help', 'nonexisting'])
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,8 +16,9 @@
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Graph import DirectedGraph
from test.TopydoTest import TopydoTest
class GraphTest(TopydoTest):
def setUp(self):
......@@ -63,7 +64,8 @@ class GraphTest(TopydoTest):
self.assertEqual(self.graph.incoming_neighbors(1, True), set())
def test_incoming_neighbors4(self):
self.assertEqual(self.graph.incoming_neighbors(5, True), set([1, 2, 3, 4, 6]))
self.assertEqual(self.graph.incoming_neighbors(5, True),
set([1, 2, 3, 4, 6]))
def test_outgoing_neighbors1(self):
self.assertEqual(self.graph.outgoing_neighbors(1), set([2, 3]))
......@@ -72,7 +74,8 @@ class GraphTest(TopydoTest):
self.assertEqual(self.graph.outgoing_neighbors(2), set([4]))
def test_outgoing_neighbors3(self):
self.assertEqual(self.graph.outgoing_neighbors(1, True), set([2, 3, 4, 5, 6]))
self.assertEqual(self.graph.outgoing_neighbors(1, True),
set([2, 3, 4, 5, 6]))
def test_outgoing_neighbors4(self):
self.assertEqual(self.graph.outgoing_neighbors(3), set([5]))
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,31 +14,47 @@
# 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 datetime import date
import unittest
from datetime import date, timedelta
from freezegun import freeze_time
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
from topydo.lib.Importance import importance
from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
@freeze_time("2015, 11, 06")
class ImportanceTest(TopydoTest):
def test_importance1(self):
def test_importance01(self):
todo = Todo("Foo")
self.assertEqual(importance(todo), 2)
def test_importance2(self):
def test_importance02(self):
todo = Todo("(A) Foo")
self.assertEqual(importance(todo), 5)
def test_importance3(self):
def test_importance03(self):
todo = Todo("(A) Foo " + config().tag_star() + ":1")
self.assertEqual(importance(todo), 6)
def test_importance4(self):
def test_importance04(self):
today_str = date.today().isoformat()
todo = Todo("(C) Foo " + config().tag_due() + ":" + today_str)
self.assertEqual(importance(todo), 8)
def test_importance05(self):
todo = Todo("(C) Foo " + config().tag_due() + ":" + "2015-11-14")
self.assertEqual(importance(todo), 4)
def test_importance06(self):
todo = Todo("(C) Foo " + config().tag_due() + ":" + "2015-11-10")
self.assertEqual(importance(todo), 5)
def test_importance07(self):
config(p_overrides={('sort', 'ignore_weekends'): '1'})
todo = Todo("(C) Foo " + config().tag_due() + ":" + "2015-11-09")
self.assertEqual(importance(todo), 6)
if __name__ == '__main__':
unittest.main()
......@@ -14,15 +14,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.JsonPrinter import JsonPrinter
from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
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()
......@@ -31,3 +35,6 @@ class JsonPrinterTest(TopydoTest):
result = printer.print_todo(todo)
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"}')
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,80 +14,95 @@
# 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 six import u
import codecs
import re
import sys
import unittest
from collections import namedtuple
from topydo.lib.Config import config
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListCommand import ListCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
from topydo.lib.Config import config
# We're searching for 'mock'
# 'mock' was added as 'unittest.mock' in Python 3.3, but PyPy 3 is based on Python 3.2
# pylint: disable=no-name-in-module
try:
from unittest import mock
except ImportError:
import mock
class ListCommandTest(CommandTest):
def setUp(self):
super(ListCommandTest, self).setUp()
self.todolist = load_file_to_todolist("test/data/ListCommandTest.txt")
self.terminal_size = namedtuple('terminal_size', ['columns', 'lines'])
def test_list1(self):
def test_list01(self):
command = ListCommand([""], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list3(self):
command = ListCommand(["Context1"], self.todolist, self.out, self.error)
def test_list03(self):
command = ListCommand(["Context1"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list4(self):
command = ListCommand(["-x", "Context1"], self.todolist, self.out, self.error)
def test_list04(self):
command = ListCommand(["-x", "Context1"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list5(self):
def test_list05(self):
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.errors, "")
def test_list6(self):
command = ListCommand(["Project3"], self.todolist, self.out, self.error)
def test_list06(self):
command = ListCommand(["Project3"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
def test_list7(self):
command = ListCommand(["-s", "text", "-x", "Project1"], self.todolist, self.out, self.error)
def test_list07(self):
command = ListCommand(["-s", "text", "-x", "Project1"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_list8(self):
command = ListCommand(["--", "-project1"], self.todolist, self.out, self.error)
def test_list08(self):
command = ListCommand(["--", "-project1"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list9(self):
command = ListCommand(["--", "-project1", "-Drink"], self.todolist, self.out, self.error)
def test_list09(self):
command = ListCommand(["--", "-project1", "-Drink"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -95,7 +110,8 @@ class ListCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_list10(self):
command = ListCommand(["text1", "2"], self.todolist, self.out, self.error)
command = ListCommand(["text1", "2"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -109,25 +125,27 @@ class ListCommandTest(CommandTest):
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_list12(self):
config("test/data/listcommand.conf")
command = ListCommand(["-x", "project"], self.todolist, self.out, self.error)
command = ListCommand(["-x", "project"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list13(self):
command = ListCommand(["-x", "--", "-@Context1 +Project2"], self.todolist, self.out, self.error)
command = ListCommand(["-x", "--", "-@Context1 +Project2"],
self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.errors, "")
def test_list14(self):
......@@ -137,7 +155,7 @@ class ListCommandTest(CommandTest):
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, " | 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n | 4| (C) Drink beer @ home\n | 5| (C) 13 + 29 = 42\n | 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.output, " | 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n | 4| (C) Drink beer @ home\n | 5| (C) 13 + 29 = 42\n | 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list15(self):
......@@ -155,19 +173,22 @@ class ListCommandTest(CommandTest):
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "|t5c| (C) Foo @Context2 Not@Context +Project1 Not+Project\n|wa5| (C) Drink beer @ home\n|z63| (C) 13 + 29 = 42\n|mfg| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.output, "|t5c| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n|wa5| (C) Drink beer @ home\n|z63| (C) 13 + 29 = 42\n|mfg| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list17(self):
command = ListCommand(["-x", "id:"], self.todolist, self.out, self.error)
command = ListCommand(["-x", "id:"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value\n")
self.assertEqual(self.output,
"| 3| (C) Baz @Context1 +Project1 key:value\n")
self.assertEqual(self.errors, "")
def test_list18(self):
command = ListCommand(["-x", "date:2014-12-12"], self.todolist, self.out, self.error)
command = ListCommand(["-x", "date:2014-12-12"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -177,11 +198,12 @@ class ListCommandTest(CommandTest):
""" Force showing all tags. """
config('test/data/listcommand-tags.conf')
command = ListCommand(["-s", "text", "-x", "Project1"], self.todolist, self.out, self.error)
command = ListCommand(["-s", "text", "-x", "Project1"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 key:value id:1\n| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.output, "| 3| (C) Baz @Context1 +Project1 id:1 key:value\n| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_list20(self):
......@@ -189,15 +211,179 @@ class ListCommandTest(CommandTest):
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list21(self):
command = ListCommand(["-f invalid"], self.todolist, self.out, self.error)
command = ListCommand(["-f invalid"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| (C) Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list22(self):
""" Handle tag lists with spaces and punctuation."""
config(p_overrides={('ls', 'hide_tags'): 'p, id'})
self.todolist = load_file_to_todolist('test/data/ListCommandTagTest.txt')
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, '| 1| Foo.\n')
def test_list31(self):
""" Don't show any todos with -n 0 """
command = ListCommand(["-n", "0"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "")
def test_list32(self):
""" Only show the top todo. """
command = ListCommand(["-n", "1"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_list33(self):
""" Negative values result in showing all relevent todos. """
command = ListCommand(["-n", "-1"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list34(self):
""" Test non-integer value for -n """
config(p_overrides={('ls', 'list_limit'): '2'})
command = ListCommand(["-n", "foo"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n")
self.assertEqual(self.errors, "")
def test_list35(self):
""" -x flag takes precedence over -n """
command = ListCommand(["-x", "-n", "foo"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n| 6| x 2014-12-12 Completed but with date:2014-12-12\n")
self.assertEqual(self.errors, "")
def test_list36(self):
command = ListCommand(["-i", "1"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_list37(self):
command = ListCommand(["-i", "1,foo,3"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 3| (C) Baz @Context1 +Project1 key:value\n")
self.assertEqual(self.errors, "")
def test_list38(self):
config("test/data/todolist-uid.conf")
command = ListCommand(["-i", "1,foo,z63"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "|t5c| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n|z63| (C) 13 + 29 = 42\n")
self.assertEqual(self.errors, "")
def test_list39(self):
config("test/data/todolist-uid.conf")
command = ListCommand(["-i", "t5c,foo"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "|t5c| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_list40(self):
command = ListCommand(["(<C)"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
def test_list41(self):
command = ListCommand(["-z", "Zzz"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "option -z not recognized\n")
def test_list42(self):
command = ListCommand(["-x", "+Project1", "-id:1"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_list43(self):
"""Test basic 'N' parameter."""
command = ListCommand(["-N"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "")
@mock.patch('topydo.commands.ListCommand.get_terminal_size')
def test_list44(self, mock_terminal_size):
"""
Test 'N' parameter with output longer than available terminal lines.
"""
self.todolist = load_file_to_todolist("test/data/ListCommand_50_items.txt")
mock_terminal_size.return_value = self.terminal_size(80, 23)
command = ListCommand(["-N"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (A) item 1\n| 27| (A) item 27\n| 2| (B) item 2\n| 28| (B) item 28\n| 3| (C) item 3\n| 29| (C) item 29\n| 4| (D) item 4\n| 30| (D) item 30\n| 5| (E) item 5\n| 31| (E) item 31\n| 6| (F) item 6\n| 32| (F) item 32\n| 7| (G) item 7\n| 33| (G) item 33\n| 8| (H) item 8\n| 34| (H) item 34\n| 9| (I) item 9\n| 35| (I) item 35\n| 10| (J) item 10\n| 36| (J) item 36\n| 11| (K) item 11\n")
self.assertEqual(self.errors, "")
@mock.patch('topydo.commands.ListCommand.get_terminal_size')
def test_list45(self, mock_terminal_size):
"""Test basic 'N' parameter with nine line terminal."""
# have 9 lines on the terminal will print 7 items and leave 2 lines
# for the next prompt
mock_terminal_size.return_value = self.terminal_size(100, 9)
self.todolist = load_file_to_todolist("test/data/ListCommand_50_items.txt")
command = ListCommand(["-N"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (A) item 1\n| 27| (A) item 27\n| 2| (B) item 2\n| 28| (B) item 28\n| 3| (C) item 3\n| 29| (C) item 29\n| 4| (D) item 4\n")
self.assertEqual(self.errors, "")
@mock.patch('topydo.commands.ListCommand.get_terminal_size')
def test_list46(self, mock_terminal_size):
"""Test basic 'N' parameter with zero height terminal."""
# we still print at least 1 item
mock_terminal_size.return_value = self.terminal_size(100, 0)
self.todolist = load_file_to_todolist("test/data/ListCommand_50_items.txt")
command = ListCommand(["-N"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (A) item 1\n")
self.assertEqual(self.errors, "")
def test_list47(self):
command = ListCommand(["created:2015-11-05"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "")
def test_help(self):
......@@ -205,7 +391,9 @@ class ListCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
class ListCommandUnicodeTest(CommandTest):
def setUp(self):
......@@ -213,16 +401,18 @@ class ListCommandUnicodeTest(CommandTest):
self.todolist = load_file_to_todolist("test/data/ListCommandUnicodeTest.txt")
def test_list_unicode1(self):
""" Unicode filters """
command = ListCommand([u("\u25c4")], self.todolist, self.out, self.error)
""" Unicode filters."""
command = ListCommand([u"\u25c4"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
expected = 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")
......@@ -233,7 +423,8 @@ class ListCommandJsonTest(CommandTest):
self.assertFalse(todolist.is_dirty())
jsontext = ""
with codecs.open('test/data/ListCommandTest.json', 'r', encoding='utf-8') as json:
with codecs.open('test/data/ListCommandTest.json', 'r',
encoding='utf-8') as json:
jsontext = json.read()
self.assertEqual(self.output, jsontext)
......@@ -248,12 +439,14 @@ class ListCommandJsonTest(CommandTest):
self.assertFalse(todolist.is_dirty())
jsontext = ""
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()
self.assertEqual(self.output, jsontext)
self.assertEqual(self.errors, "")
def replace_ical_tags(p_text):
# replace identifiers with dots, since they're random.
result = re.sub(r'\bical:....\b', 'ical:....', p_text)
......@@ -261,43 +454,29 @@ def replace_ical_tags(p_text):
return result
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/ListCommandIcalTest.txt")
command = ListCommand(["-x", "-f", "ical"], todolist, self.out, self.error)
command = ListCommand(["-x", "-f", "ical"], todolist, self.out,
self.error)
command.execute()
self.assertTrue(todolist.is_dirty())
icaltext = ""
with codecs.open('test/data/ListCommandTest.ics', 'r', encoding='utf-8') as ical:
with codecs.open('test/data/ListCommandTest.ics', 'r',
encoding='utf-8') as ical:
icaltext = ical.read()
self.assertEqual(replace_ical_tags(self.output), replace_ical_tags(icaltext))
self.assertEqual(replace_ical_tags(self.output),
replace_ical_tags(icaltext))
self.assertEqual(self.errors, "")
@unittest.skipUnless(IS_PYTHON_32, "icalendar is not supported for Python 3.2")
def test_ical_python32(self):
"""
Test case for Python 3.2 where icalendar is not supported.
"""
todolist = load_file_to_todolist("test/data/ListCommandTest.txt")
command = ListCommand(["-f", "ical"], todolist, self.out, self.error)
command.execute()
self.assertFalse(todolist.is_dirty())
self.assertEqual(self.output, '')
self.assertEqual(self.errors, "icalendar is not supported in this Python version.\n")
@unittest.skipIf(IS_PYTHON_32, "icalendar is not supported for Python 3.2")
def test_ical_unicode(self):
todolist = load_file_to_todolist("test/data/ListCommandUnicodeTest.txt")
......@@ -307,10 +486,12 @@ class ListCommandIcalTest(CommandTest):
self.assertTrue(todolist.is_dirty())
icaltext = ""
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()
self.assertEqual(replace_ical_tags(self.output), replace_ical_tags(icaltext))
self.assertEqual(replace_ical_tags(self.output),
replace_ical_tags(icaltext))
self.assertEqual(self.errors, "")
if __name__ == '__main__':
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,9 +16,10 @@
import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListContextCommand import ListContextCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
class ListContextCommandTest(CommandTest):
def test_contexts1(self):
......@@ -26,7 +27,7 @@ class ListContextCommandTest(CommandTest):
command = ListContextCommand([""], todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output,"Context1\nContext2\n")
self.assertEqual(self.output, "Context1\nContext2\n")
self.assertFalse(self.errors)
def test_contexts2(self):
......@@ -34,7 +35,7 @@ class ListContextCommandTest(CommandTest):
command = ListContextCommand(["aaa"], todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output,"Context1\nContext2\n")
self.assertEqual(self.output, "Context1\nContext2\n")
self.assertFalse(self.errors)
def test_help(self):
......@@ -42,7 +43,8 @@ class ListContextCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from collections import namedtuple
from freezegun import freeze_time
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListCommand import ListCommand
from topydo.lib.Config import config
# We're searching for 'mock'
# 'mock' was added as 'unittest.mock' in Python 3.3, but PyPy 3 is based on Python 3.2
# pylint: disable=no-name-in-module
try:
from unittest import mock
except ImportError:
import mock
@freeze_time("2015, 11, 06")
class ListFormatTest(CommandTest):
def setUp(self):
super(ListFormatTest, self).setUp()
self.todolist = load_file_to_todolist("test/data/ListFormat.txt")
self.terminal_size = namedtuple('terminal_size', ['columns', 'lines'])
def test_list_format01(self):
config(p_overrides={('ls', 'list_format'): '|%I| %x %{(}p{)} %c %s %K'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox +jumped over the and jar due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format02(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(80, 25)
config(p_overrides={('ls', 'list_format'): '|%I| %x %{(}p{)} %c %S %K'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolore... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format03(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
config(p_overrides={('ls', 'list_format'): '|%I| %x %{(}p{)} %c %S %K'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format04(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
config(p_overrides={('ls', 'list_format'): '|%I| %x %{(}p{)} %c %S %K'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format05(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(80, 25)
config(p_overrides={('ls', 'list_format'): '|%I| %x %{(}p{)} %c %S %K'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolore... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format06(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
config(p_overrides={('ls', 'list_format'): '|%I| %x %p %S %k %{(}H{)}'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| D Bar @Context1 +Project2 (3 months ago, due a month ago, started a month ago)
| 2| Z Lorem ipsum dolorem sit amet. Red @f... lazy:bar (just now, due in 2 days, starts in a day)
| 3| C Foo @Context2 Not@Context +Project1 Not+Project (4 months ago)
| 4| C Baz @Context1 +Project1 key:value
| 5| Drink beer @ home
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format07(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
config(p_overrides={('ls', 'list_format'): '|%I| %x %p %S %k %{(}h{)}'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| D Bar @Context1 +Project2 (due a month ago, started a month ago)
| 2| Z Lorem ipsum dolorem sit amet. Red @fox +jumped... lazy:bar (due in 2 days, starts in a day)
| 3| C Foo @Context2 Not@Context +Project1 Not+Project
| 4| C Baz @Context1 +Project1 key:value
| 5| Drink beer @ home
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format08(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
config(p_overrides={('ls', 'list_format'): '%c %d %t %x'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """2015-08-31 2015-09-30 2015-09-29
2015-11-06 2015-11-08 2015-11-07
2015-07-12
x 2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format09(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
config(p_overrides={('ls', 'list_format'): '%C | %D | %T | %X'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """3 months ago | a month ago | a month ago |
just now | in 2 days | in a day |
4 months ago | | |
| | |
| | |
| | | x 11 months ago
"""
self.assertEqual(self.output, result)
def test_list_format10(self):
config(p_overrides={('ls', 'list_format'): '|%i| %k'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """|1|
|2| lazy:bar
|3|
|4| key:value
|5|
|6| date:2014-12-12
"""
self.assertEqual(self.output, result)
def test_list_format11(self):
config(p_overrides={('ls', 'list_format'): '|%I| %K'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| due:2015-09-30 t:2015-09-29
| 2| due:2015-11-08 lazy:bar t:2015-11-07
| 3|
| 4| key:value
| 5| ical:foobar id:1 p:2
| 6| date:2014-12-12
"""
self.assertEqual(self.output, result)
def test_list_format12(self):
config(p_overrides={('ls', 'list_format'): '|%I| \%'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| %
| 2| %
| 3| %
| 4| %
| 5| %
| 6| %
"""
self.assertEqual(self.output, result)
def test_list_format13(self):
command = ListCommand(["-x", "-F", "|%I| %x %{(}p{)} %c %s %K"],
self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox +jumped over the and jar due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format14(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(40, 25)
command = ListCommand(["-x", "-F", "|%I| %x %{(}p{)} %c %s %K", "@Context1"],
self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 4| (C) Baz @Context1 +Project1 key:value
"""
self.assertEqual(self.output, result)
def test_list_format15(self):
command = ListCommand(["-x", "-F", "%c"], self.todolist, self.out, self.error)
command.execute()
result = """2015-08-31
2015-11-06
2015-07-12
"""
self.assertEqual(self.output, result)
def test_list_format16(self):
command = ListCommand(["-x", "-F", "%C"], self.todolist, self.out, self.error)
command.execute()
result = """3 months ago
just now
4 months ago
"""
self.assertEqual(self.output, result)
def test_list_format17(self):
command = ListCommand(["-x", "-F", "%d"], self.todolist, self.out, self.error)
command.execute()
result = """2015-09-30
2015-11-08
"""
self.assertEqual(self.output, result)
def test_list_format18(self):
command = ListCommand(["-x", "-F", "%D"], self.todolist, self.out, self.error)
command.execute()
result = """a month ago
in 2 days
"""
self.assertEqual(self.output, result)
def test_list_format19(self):
command = ListCommand(["-x", "-F", "%h"], self.todolist, self.out, self.error)
command.execute()
result = """due a month ago, started a month ago
due in 2 days, starts in a day
"""
self.assertEqual(self.output, result)
def test_list_format20(self):
command = ListCommand(["-x", "-F", "%H"], self.todolist, self.out, self.error)
command.execute()
result = """3 months ago, due a month ago, started a month ago
just now, due in 2 days, starts in a day
4 months ago
"""
self.assertEqual(self.output, result)
def test_list_format21(self):
command = ListCommand(["-x", "-F", "%i"], self.todolist, self.out, self.error)
command.execute()
result = """1
2
3
4
5
6
"""
self.assertEqual(self.output, result)
def test_list_format22(self):
command = ListCommand(["-x", "-F", "%I"], self.todolist, self.out, self.error)
command.execute()
result = """ 1
2
3
4
5
6
"""
self.assertEqual(self.output, result)
def test_list_format23(self):
command = ListCommand(["-x", "-F", "%k"], self.todolist, self.out, self.error)
command.execute()
result = """
lazy:bar
key:value
date:2014-12-12
"""
self.assertEqual(self.output, result)
def test_list_format24(self):
command = ListCommand(["-x", "-F", "%K"], self.todolist, self.out, self.error)
command.execute()
result = """due:2015-09-30 t:2015-09-29
due:2015-11-08 lazy:bar t:2015-11-07
key:value
ical:foobar id:1 p:2
date:2014-12-12
"""
self.assertEqual(self.output, result)
def test_list_format25(self):
command = ListCommand(["-x", "-F", "%p"], self.todolist, self.out, self.error)
command.execute()
result = """D
Z
C
C
"""
self.assertEqual(self.output, result)
def test_list_format26(self):
command = ListCommand(["-x", "-F", "%s"], self.todolist, self.out, self.error)
command.execute()
result = u"""Bar @Context1 +Project2
Lorem ipsum dolorem sit amet. Red @fox +jumped over the and jar
Foo @Context2 Not@Context +Project1 Not+Project
Baz @Context1 +Project1
Drink beer @ home
Completed but with
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format27(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(50, 25)
command = ListCommand(["-x", "-F", "%S"], self.todolist, self.out, self.error)
command.execute()
result = """Bar @Context1 +Project2
Lorem ipsum dolorem sit amet. Red @fox +jumped...
Foo @Context2 Not@Context +Project1 Not+Project
Baz @Context1 +Project1
Drink beer @ home
Completed but with
"""
self.assertEqual(self.output, result)
def test_list_format28(self):
command = ListCommand(["-x", "-F", "%t"], self.todolist, self.out, self.error)
command.execute()
result = """2015-09-29
2015-11-07
"""
self.assertEqual(self.output, result)
def test_list_format29(self):
command = ListCommand(["-x", "-F", "%T"], self.todolist, self.out, self.error)
command.execute()
result = """a month ago
in a day
"""
self.assertEqual(self.output, result)
def test_list_format30(self):
command = ListCommand(["-x", "-F", "%x"], self.todolist, self.out, self.error)
command.execute()
result = """
x 2014-12-12
"""
self.assertEqual(self.output, result)
def test_list_format31(self):
command = ListCommand(["-x", "-F", "%X"], self.todolist, self.out, self.error)
command.execute()
result = """
x 11 months ago
"""
self.assertEqual(self.output, result)
def test_list_format32(self):
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%{{}p{}}"], self.todolist, self.out, self.error)
command.execute()
result = """{C}
{C}
{D}
{Z}
"""
self.assertEqual(self.output, result)
def test_list_format33(self):
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%{\%p}p{\%p}"], self.todolist, self.out, self.error)
command.execute()
result = """%pC%p
%pC%p
%pD%p
%pZ%p
"""
self.assertEqual(self.output, result)
def test_list_format34(self):
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%p%p"], self.todolist, self.out, self.error)
command.execute()
result = """CC
CC
DD
ZZ
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format35(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(5, 25)
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%p{ } %{ }p"], self.todolist, self.out, self.error)
command.execute()
result = """C C
C C
D D
Z Z
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format36(self, mock_terminal_size):
"""Tab expands to 1 character."""
mock_terminal_size.return_value = self.terminal_size(6, 25)
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%p{ } %{ }p"], self.todolist, self.out, self.error)
command.execute()
result = """C C
C C
D D
Z Z
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format37(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(5, 25)
command = ListCommand(["-x", "-s", "desc:priority", "-F", " %{ }p"], self.todolist, self.out, self.error)
command.execute()
result = """ C
C
D
Z
"""
self.assertEqual(self.output, result)
def test_list_format38(self):
"""
Invalid placeholders should expand to an empty string.
"""
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%&"], self.todolist, self.out, self.error)
command.execute()
result = """
"""
self.assertEqual(self.output, result)
def test_list_format39(self):
"""
Invalid placeholders without a character should expand to an empty
string.
"""
command = ListCommand(["-x", "-s", "desc:priority", "-F", "%"], self.todolist, self.out, self.error)
command.execute()
result = """
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format40(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
config('test/data/listformat.conf')
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format41(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
command = ListCommand(["-x", "-F", "|%I| %x %{(}p{)} %c %S\\t%K"], self.todolist, self.out, self.error)
command.execute()
result = """| 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @fox... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
@mock.patch('topydo.lib.ListFormat.get_terminal_size')
def test_list_format42(self, mock_terminal_size):
mock_terminal_size.return_value = self.terminal_size(100, 25)
config('test/data/listformat.conf', p_overrides={('ls', 'indent'): '3'})
command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute()
result = """ | 1| (D) 2015-08-31 Bar @Context1 +Project2 due:2015-09-30 t:2015-09-29
| 2| (Z) 2015-11-06 Lorem ipsum dolorem sit amet. Red @... due:2015-11-08 lazy:bar t:2015-11-07
| 3| (C) 2015-07-12 Foo @Context2 Not@Context +Project1 Not+Project
| 4| (C) Baz @Context1 +Project1 key:value
| 5| Drink beer @ home ical:foobar id:1 p:2
| 6| x 2014-12-12 Completed but with date:2014-12-12
"""
self.assertEqual(self.output, result)
def test_list_format43(self):
command = ListCommand(["-x", "-F", "%P -"], self.todolist, self.out, self.error)
command.execute()
result = """D -
Z -
C -
C -
-
-
"""
self.assertEqual(self.output, result)
def test_list_format44(self):
command = ListCommand(["-x", "-F", "%i %{(}P{)}"], self.todolist, self.out, self.error)
command.execute()
result = """1 (D)
2 (Z)
3 (C)
4 (C)
5 ( )
6 ( )
"""
self.assertEqual(self.output, result)
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,9 +16,10 @@
import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListProjectCommand import ListProjectCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
class ListProjectCommandTest(CommandTest):
def test_projects1(self):
......@@ -42,7 +43,8 @@ class ListProjectCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,14 +14,14 @@
# 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 datetime import date, timedelta
import unittest
from six import u
from datetime import date, timedelta
from test.command_testcase import CommandTest
from topydo.commands.PostponeCommand import PostponeCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
class PostponeCommandTest(CommandTest):
def setUp(self):
super(PostponeCommandTest, self).setUp()
......@@ -42,38 +42,45 @@ class PostponeCommandTest(CommandTest):
self.todolist = TodoList(todos)
def test_postpone1(self):
command = PostponeCommand(["1", "1w"], self.todolist, self.out, self.error)
def test_postpone01(self):
command = PostponeCommand(["1", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.output,
"| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone2(self):
command = PostponeCommand(["2", "1w"], self.todolist, self.out, self.error)
def test_postpone02(self):
command = PostponeCommand(["2", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.output,
"| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone3(self):
command = PostponeCommand(["-s", "2", "1w"], self.todolist, self.out, self.error)
def test_postpone03(self):
command = PostponeCommand(["-s", "2", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.output,
"| 2| Bar due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone4(self):
command = PostponeCommand(["3", "1w"], self.todolist, self.out, self.error)
def test_postpone04(self):
command = PostponeCommand(["3", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
......@@ -82,8 +89,9 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), self.start.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone5(self):
command = PostponeCommand(["-s", "3", "1w"], self.todolist, self.out, self.error)
def test_postpone05(self):
command = PostponeCommand(["-s", "3", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
......@@ -94,18 +102,21 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone6(self):
command = PostponeCommand(["4", "1w"], self.todolist, self.out, self.error)
def test_postpone06(self):
command = PostponeCommand(["4", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 4| Past due:{}\n".format(due.isoformat()))
self.assertEqual(self.output,
"| 4| Past due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone7(self):
command = PostponeCommand(["5", "1w"], self.todolist, self.out, self.error)
def test_postpone07(self):
command = PostponeCommand(["5", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.future + timedelta(7)
......@@ -115,8 +126,9 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), self.future_start.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone8(self):
command = PostponeCommand(["-s", "5", "1w"], self.todolist, self.out, self.error)
def test_postpone08(self):
command = PostponeCommand(["-s", "5", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.future + timedelta(7)
......@@ -127,8 +139,9 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone9(self):
command = PostponeCommand(["1", "foo"], self.todolist, self.out, self.error)
def test_postpone09(self):
command = PostponeCommand(["1", "foo"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -136,7 +149,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid date pattern given.\n")
def test_postpone10(self):
command = PostponeCommand(["99", "foo"], self.todolist, self.out, self.error)
command = PostponeCommand(["99", "foo"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -144,7 +158,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_postpone11(self):
command = PostponeCommand(["A", "foo"], self.todolist, self.out, self.error)
command = PostponeCommand(["A", "foo"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -160,17 +175,20 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n")
def test_postpone13(self):
command = PostponeCommand(["Foo", "1w"], self.todolist, self.out, self.error)
command = PostponeCommand(["Foo", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.output,
"| 1| Foo due:{}\n".format(due.isoformat()))
self.assertEqual(self.errors, "")
def test_postpone14(self):
command = PostponeCommand(["1", "2", "1w"], self.todolist, self.out, self.error)
command = PostponeCommand(["1", "2", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
......@@ -180,7 +198,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_postpone15(self):
command = PostponeCommand(["Foo", "2", "1w"], self.todolist, self.out, self.error)
command = PostponeCommand(["Foo", "2", "1w"], self.todolist, self.out,
self.error)
command.execute()
due = self.today + timedelta(7)
......@@ -190,7 +209,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_postpone16(self):
command = PostponeCommand(["-s", "2", "3", "1w"], self.todolist, self.out, self.error)
command = PostponeCommand(["-s", "2", "3", "1w"], self.todolist,
self.out, self.error)
command.execute()
due = self.today + timedelta(7)
......@@ -202,7 +222,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_postpone17(self):
command = PostponeCommand(["1", "2", "3"], self.todolist, self.out, self.error)
command = PostponeCommand(["1", "2", "3"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -210,7 +231,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid date pattern given.\n")
def test_postpone18(self):
command = PostponeCommand(["1", "99", "123", "1w"], self.todolist, self.out, self.error)
command = PostponeCommand(["1", "99", "123", "1w"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -218,7 +240,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 123.\n")
def test_postpone19(self):
command = PostponeCommand(["Zoo", "99", "123", "1w"], self.todolist, self.out, self.error)
command = PostponeCommand(["Zoo", "99", "123", "1w"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -227,15 +250,18 @@ class PostponeCommandTest(CommandTest):
def test_postpone20(self):
""" Throw an error with invalid argument containing special characters. """
command = PostponeCommand([u("Fo\u00d3B\u0105r"), "Bar", "1d"], self.todolist, self.out, self.error, None)
command = PostponeCommand([u"Fo\u00d3B\u0105r", "Bar", "1d"],
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
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 = PostponeCommand(["-e", "due:tod", "2w"], self.todolist,
self.out, self.error, None)
command.execute()
due = self.today + timedelta(14)
......@@ -247,33 +273,38 @@ class PostponeCommandTest(CommandTest):
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 = 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())
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 = 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 = 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 = PostponeCommand(["-xe", "FutureStart", "1w"], self.todolist,
self.out, self.error, None)
command.execute()
due = self.today + timedelta(7)
......@@ -284,12 +315,13 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_help(self):
command = PostponeCommand(["help"], self.todolist, self.out, self.error)
command = PostponeCommand(["help"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -15,12 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from six import u
from test.command_testcase import CommandTest
from topydo.commands.PriorityCommand import PriorityCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
class PriorityCommandTest(CommandTest):
def setUp(self):
super(PriorityCommandTest, self).setUp()
......@@ -35,15 +35,18 @@ class PriorityCommandTest(CommandTest):
self.todolist = TodoList(todos)
def test_set_prio1(self):
command = PriorityCommand(["1", "B"], self.todolist, self.out, self.error)
command = PriorityCommand(["1", "B"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.output,
"Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.errors, "")
def test_set_prio2(self):
command = PriorityCommand(["2", "Z"], self.todolist, self.out, self.error)
command = PriorityCommand(["2", "Z"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -51,15 +54,18 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_set_prio3(self):
command = PriorityCommand(["Foo", "B"], self.todolist, self.out, self.error)
command = PriorityCommand(["Foo", "B"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.output,
"Priority changed from A to B\n| 1| (B) Foo\n")
self.assertEqual(self.errors, "")
def test_set_prio4(self):
command = PriorityCommand(["1", "A"], self.todolist, self.out, self.error)
command = PriorityCommand(["1", "A"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -67,7 +73,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_set_prio5(self):
command = PriorityCommand(["Foo", "2", "C"], self.todolist, self.out, self.error)
command = PriorityCommand(["Foo", "2", "C"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -76,17 +83,28 @@ class PriorityCommandTest(CommandTest):
def test_set_prio6(self):
""" Allow priority to be set including parentheses. """
command = PriorityCommand(["Foo", "2", "(C)"], self.todolist, self.out, self.error)
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)
def test_set_prio7(self):
""" Allow lowercase priority to be set. """
command = PriorityCommand(["Foo", "2", "c"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.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"
......@@ -95,7 +113,8 @@ class PriorityCommandTest(CommandTest):
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 = 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"
......@@ -105,29 +124,35 @@ class PriorityCommandTest(CommandTest):
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 = 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 = 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 = 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.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 = PriorityCommand(["99", "A"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -135,7 +160,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_invalid2(self):
command = PriorityCommand(["1", "99", "A"], self.todolist, self.out, self.error)
command = PriorityCommand(["1", "99", "A"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -143,7 +169,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
def test_invalid3(self):
command = PriorityCommand(["98", "99", "A"], self.todolist, self.out, self.error)
command = PriorityCommand(["98", "99", "A"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -151,7 +178,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 98.\nInvalid todo number given: 99.\n")
def test_invalid4(self):
command = PriorityCommand(["1", "ZZ"], self.todolist, self.out, self.error)
command = PriorityCommand(["1", "ZZ"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -175,20 +203,25 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n")
def test_invalid7(self):
""" Throw an error with invalid argument containing special characters. """
command = PriorityCommand([u("Fo\u00d3B\u0105r"), "Bar", "C"], self.todolist, self.out, self.error, None)
"""
Throw an error with invalid argument containing special characters.
"""
command = PriorityCommand([u"Fo\u00d3B\u0105r", "Bar", "C"],
self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
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 = PriorityCommand(["2", "(Aa)"], self.todolist, self.out,
self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -200,12 +233,14 @@ class PriorityCommandTest(CommandTest):
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 = 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()
......@@ -215,11 +250,13 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n")
def test_help(self):
command = PriorityCommand(["help"], self.todolist, self.out, self.error)
command = PriorityCommand(["help"], self.todolist, self.out,
self.error)
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,13 +14,14 @@
# 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 datetime import date, timedelta
import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
from topydo.lib.Recurrence import advance_recurring_todo, NoRecurrenceException
from topydo.lib.Recurrence import NoRecurrenceException, advance_recurring_todo
from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
class RecurrenceTest(TopydoTest):
def setUp(self):
......@@ -164,12 +165,14 @@ class RecurrenceTest(TopydoTest):
def test_no_recurrence(self):
self.todo.remove_tag('rec')
self.assertRaises(NoRecurrenceException, advance_recurring_todo, self.todo)
self.assertRaises(NoRecurrenceException, advance_recurring_todo,
self.todo)
def test_invalid_recurrence(self):
""" Throw exception when 'rec' tag has an invalid value. """
self.todo.set_tag('rec', '1')
self.assertRaises(NoRecurrenceException, advance_recurring_todo, self.todo)
self.assertRaises(NoRecurrenceException, advance_recurring_todo,
self.todo)
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,21 +14,23 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date, timedelta
import unittest
from datetime import date, timedelta
from freezegun import freeze_time
from test.topydo_testcase import TopydoTest
from topydo.lib.RelativeDate import relative_date_to_date
from test.TopydoTest import TopydoTest
@freeze_time('2015, 11, 06')
class RelativeDateTester(TopydoTest):
def setUp(self):
super(RelativeDateTester, self).setUp()
self.today = date.today()
self.tomorrow = self.today + timedelta(1)
self.monday = self.today
if self.monday.weekday() != 0:
self.monday += timedelta(7 - self.today.weekday() % 7)
self.yesterday = date(2015, 11, 5)
self.today = date(2015, 11, 6)
self.tomorrow = date(2015, 11, 7)
self.monday = date(2015, 11, 9)
self.friday = date(2015, 11, 13)
def test_zero_days(self):
result = relative_date_to_date('0d')
......@@ -40,7 +42,7 @@ class RelativeDateTester(TopydoTest):
def test_one_week(self):
result = relative_date_to_date('1w')
self.assertEqual(result, date.today() + timedelta(weeks=1))
self.assertEqual(result, date(2015, 11, 13))
def test_one_month(self):
test_date = date(2015, 1, 10)
......@@ -103,8 +105,7 @@ class RelativeDateTester(TopydoTest):
self.assertEqual(result, self.today)
def test_today3(self):
result = relative_date_to_date('today', \
date.today() + timedelta(1))
result = relative_date_to_date('today', self.tomorrow)
self.assertEqual(result, self.today)
def test_tomorrow1(self):
......@@ -115,6 +116,14 @@ class RelativeDateTester(TopydoTest):
result = relative_date_to_date('tom')
self.assertEqual(result, self.tomorrow)
def test_yesterday1(self):
result = relative_date_to_date('yesterday')
self.assertEqual(result, self.yesterday)
def test_yesterday2(self):
result = relative_date_to_date('yes')
self.assertEqual(result, self.yesterday)
def test_monday1(self):
result = relative_date_to_date('monday')
self.assertEqual(result, self.monday)
......@@ -133,15 +142,23 @@ class RelativeDateTester(TopydoTest):
def test_offset1(self):
result = relative_date_to_date('1d', self.tomorrow)
self.assertEqual(result, date.today() + timedelta(2))
self.assertEqual(result, date(2015, 11, 8))
def test_negative_period1(self):
result = relative_date_to_date('-1d')
self.assertEqual(result, date.today() - timedelta(1))
self.assertEqual(result, date(2015, 11, 5))
def test_negative_period2(self):
result = relative_date_to_date('-0d')
self.assertTrue(result, self.today)
def test_weekday_next_week(self):
"""
When entering "Friday" on a Friday, return next week Friday instead of
today.
"""
result = relative_date_to_date("fri")
self.assertTrue(result, self.friday)
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import tempfile
import unittest
from datetime import date
from glob import glob
from uuid import uuid4
from test.command_testcase import CommandTest
from topydo.commands.AddCommand import AddCommand
from topydo.commands.ArchiveCommand import ArchiveCommand
from topydo.commands.DeleteCommand import DeleteCommand
from topydo.commands.DoCommand import DoCommand
from topydo.commands.RevertCommand import RevertCommand
from topydo.lib.ChangeSet import ChangeSet
from topydo.lib.Config import config
from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList
class RevertCommandTest(CommandTest):
def setUp(self):
super(RevertCommandTest, self).setUp()
todos = [
"Foo",
"Bar",
"Baz",
]
self.todolist = TodoList(todos)
self.today = date.today()
self.tmp_name = str(uuid4().hex.upper()[0:6])
archive_filename = tempfile.gettempdir() + os.sep + self.tmp_name + '_archive'
todo_filename = tempfile.gettempdir() + os.sep + self.tmp_name + '_todo'
config(p_overrides={('topydo', 'archive_filename'): archive_filename,
('topydo', 'filename'): todo_filename, ('topydo', 'backup_count'): '5'})
self.archive_file = TodoFile(archive_filename)
self.archive = TodoList([])
def test_revert01(self):
backup = ChangeSet(p_call=['do 1'])
backup.add_todolist(self.todolist)
backup.add_archive(self.archive)
backup.timestamp = '1'
command = DoCommand(["1"], self.todolist, self.out, self.error, None)
command.execute()
archive_command = ArchiveCommand(self.todolist, self.archive)
archive_command.execute()
self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist)
self.assertEqual(self.archive.print_todos(), "x {} Foo".format(self.today))
self.assertEqual(self.todolist.print_todos(), "Bar\nBaz")
revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute()
result = TodoList(self.archive_file.read()).print_todos()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: do 1\n"))
self.assertEqual(result, "")
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz")
def test_revert02(self):
backup = ChangeSet(self.todolist, self.archive, ['do 1'])
backup.timestamp = '1'
command1 = DoCommand(["1"], self.todolist, self.out, self.error, None)
command1.execute()
archive_command1 = ArchiveCommand(self.todolist, self.archive)
archive_command1.execute()
self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['do Bar'])
backup.timestamp = '2'
command2 = DoCommand(["Bar"], self.todolist, self.out, self.error, None)
command2.execute()
archive_command2 = ArchiveCommand(self.todolist, self.archive)
archive_command2.execute()
self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist)
self.assertEqual(self.archive.print_todos(), "x {t} Foo\nx {t} Bar".format(t=self.today))
self.assertEqual(self.todolist.print_todos(), "Baz")
revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute()
result = TodoList(self.archive_file.read()).print_todos()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: do Bar\n"))
self.assertEqual(result, "x {} Foo".format(self.today))
self.assertEqual(self.todolist.print_todos(), "Bar\nBaz")
def test_revert03(self):
""" Test behavior when no backup is found """
command = RevertCommand([], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.errors, "No backup was found for the current state of {}\n".format(config().todotxt()))
def test_revert04(self):
""" Test trimming of the backup_file """
backup = ChangeSet(self.todolist, self.archive, ['add One'])
backup.timestamp = '1'
command1 = AddCommand(["One"], self.todolist, self.out, self.error, None)
command1.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Two'])
backup.timestamp = '2'
command2 = AddCommand(["Two"], self.todolist, self.out, self.error, None)
command2.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Three'])
backup.timestamp = '3'
command3 = AddCommand(["Three"], self.todolist, self.out, self.error, None)
command3.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Four'])
backup.timestamp = '4'
command4 = AddCommand(["Four"], self.todolist, self.out, self.error, None)
command4.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Five'])
backup.timestamp = '5'
command5 = AddCommand(["Five"], self.todolist, self.out, self.error, None)
command5.execute()
backup.save(self.todolist)
result = len(ChangeSet().backup_dict.keys())
self.assertEqual(result, 6)
backup = ChangeSet(self.todolist, self.archive, ['add Six'])
backup.timestamp = '6'
command6 = AddCommand(["Six"], self.todolist, self.out, self.error, None)
command6.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Seven'])
backup.timestamp = '7'
command7 = AddCommand(["Seven"], self.todolist, self.out, self.error, None)
command7.execute()
backup.save(self.todolist)
result = len(ChangeSet().backup_dict.keys())
self.assertEqual(result, 6)
revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute()
backup = ChangeSet()
changesets = list(backup.backup_dict.keys())
changesets.remove('index')
index_timestamps = [change[0] for change in backup._get_index()]
result = list(set(index_timestamps) - set(changesets))
self.assertEqual(len(changesets), 4)
self.assertEqual(result, [])
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Seven\n"))
def test_revert05(self):
""" Test for possible backup collisions """
backup = ChangeSet(self.todolist, self.archive, ['add One'])
backup.timestamp = '1'
command1 = AddCommand(["One"], self.todolist, self.out, self.error, None)
command1.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Two'])
backup.timestamp = '2'
command2 = AddCommand(["Two"], self.todolist, self.out, self.error, None)
command2.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Three'])
backup.timestamp = '3'
command3 = AddCommand(["Three"], self.todolist, self.out, self.error, None)
command3.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['delete Three'])
backup.timestamp = '4'
command4 = DeleteCommand(["Three"], self.todolist, self.out, self.error, None)
command4.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Four'])
backup.timestamp = '5'
command4 = AddCommand(["Four"], self.todolist, self.out, self.error, None)
command4.execute()
backup.save(self.todolist)
revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Four\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two".format(t=self.today))
revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: delete Three\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two\n{t} Three".format(t=self.today))
revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Three\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two".format(t=self.today))
revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Two\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One".format(t=self.today))
revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add One\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz")
def test_revert06(self):
""" Test attempt of deletion with non-existing backup key"""
backup = ChangeSet(self.todolist, self.archive, ['add One'])
backup.timestamp = '1'
command1 = AddCommand(["One"], self.todolist, self.out, self.error, None)
command1.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Two'])
backup.timestamp = '2'
command2 = AddCommand(["Two"], self.todolist, self.out, self.error, None)
command2.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Three'])
backup.timestamp = '3'
command3 = AddCommand(["Three"], self.todolist, self.out, self.error, None)
command3.execute()
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['delete Three'])
backup.timestamp = '4'
command4 = DeleteCommand(["Three"], self.todolist, self.out, self.error, None)
command4.execute()
backup.save(self.todolist)
backup = ChangeSet()
backup.delete('Foo')
changesets = list(backup.backup_dict.keys())
changesets.remove('index')
index_timestamps = [change[0] for change in backup._get_index()]
result = list(set(index_timestamps) - set(changesets))
self.assertEqual(len(changesets), 4)
self.assertEqual(result, [])
self.assertEqual(self.errors, "")
def test_backup_config01(self):
config(p_overrides={('topydo', 'backup_count'): '1'})
self.assertEqual(config().backup_count(), 1)
def test_backup_config02(self):
config(p_overrides={('topydo', 'backup_count'): '0'})
self.assertEqual(config().backup_count(), 0)
def test_backup_config03(self):
config(p_overrides={('topydo', 'backup_count'): '-88'})
self.assertEqual(config().backup_count(), 0)
def test_backup_config04(self):
config(p_overrides={('topydo', 'backup_count'): 'foo'})
self.assertEqual(config().backup_count(), 5)
def test_help(self):
command = RevertCommand(["help"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
def tearDown(self):
for filename in glob('/tmp/' + self.tmp_name + '*'):
os.remove(filename)
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,10 +16,11 @@
import unittest
from topydo.lib.Config import config
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.SortCommand import SortCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
from topydo.lib.Config import config
class SortCommandTest(CommandTest):
def setUp(self):
......@@ -27,17 +28,19 @@ class SortCommandTest(CommandTest):
self.todolist = load_file_to_todolist("test/data/SorterTest1.txt")
def test_sort1(self):
""" Alphabetically sorted """
""" Alphabetically sorted. """
command = SortCommand(["text"], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.todolist.print_todos(), "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(self.todolist.print_todos(), "(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. """
......@@ -55,7 +58,8 @@ class SortCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,11 +16,12 @@
import unittest
from test.facilities import (load_file, load_file_to_todolist, print_view,
todolist_to_string)
from test.topydo_testcase import TopydoTest
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, print_view
from test.TopydoTest import TopydoTest
class SorterTest(TopydoTest):
def sort_file(self, p_filename, p_filename_ref, p_sorter):
......@@ -36,80 +37,93 @@ class SorterTest(TopydoTest):
self.assertEqual(todos_sorted, todos_ref)
self.assertEqual(todolist_to_string(todos), text_before)
def test_sort1(self):
""" Alphabetically sorted """
def test_sort01(self):
""" Alphabetically sorted. """
sorter = Sorter('text')
self.sort_file('test/data/SorterTest1.txt', 'test/data/SorterTest1-result.txt', sorter)
self.sort_file('test/data/SorterTest1.txt',
'test/data/SorterTest1-result.txt', sorter)
def test_sort2a(self):
def test_sort02a(self):
"""
Ascendingly sorted by priority. Also checks stableness of the sort.
"""
sorter = Sorter('prio')
self.sort_file('test/data/SorterTest2.txt', 'test/data/SorterTest2-result.txt', sorter)
self.sort_file('test/data/SorterTest2.txt',
'test/data/SorterTest2-result.txt', sorter)
def test_sort2b(self):
def test_sort02b(self):
"""
Ascendingly sorted by priority. Also checks stableness of the sort.
"""
sorter = Sorter('asc:prio')
self.sort_file('test/data/SorterTest2.txt', 'test/data/SorterTest2-result.txt', sorter)
self.sort_file('test/data/SorterTest2.txt',
'test/data/SorterTest2-result.txt', sorter)
def test_sort3(self):
def test_sort03(self):
"""
Descendingly sorted by priority. Also checks stableness of the
sort.
"""
sorter = Sorter('desc:prio')
self.sort_file('test/data/SorterTest3.txt', 'test/data/SorterTest3-result.txt', sorter)
self.sort_file('test/data/SorterTest3.txt',
'test/data/SorterTest3-result.txt', sorter)
def test_sort4(self):
""" Ascendingly sorted by due date """
def test_sort04(self):
""" Ascendingly sorted by due date. """
sorter = Sorter(config().tag_due())
self.sort_file('test/data/SorterTest4.txt', 'test/data/SorterTest4-result.txt', sorter)
self.sort_file('test/data/SorterTest4.txt',
'test/data/SorterTest4-result.txt', sorter)
def test_sort5(self):
""" Descendingly sorted by due date """
def test_sort05(self):
""" Descendingly sorted by due date. """
sorter = Sorter('desc:due')
self.sort_file('test/data/SorterTest5.txt', 'test/data/SorterTest5-result.txt', sorter)
self.sort_file('test/data/SorterTest5.txt',
'test/data/SorterTest5-result.txt', sorter)
def test_sort6(self):
""" Ascendingly sorted by creation date """
def test_sort06(self):
""" Ascendingly sorted by creation date. """
sorter = Sorter('creation')
self.sort_file('test/data/SorterTest6.txt', 'test/data/SorterTest6-result.txt', sorter)
self.sort_file('test/data/SorterTest6.txt',
'test/data/SorterTest6-result.txt', sorter)
def test_sort7(self):
def test_sort07(self):
""" Ascendingly sorted by completion date. """
sorter = Sorter('completion')
self.sort_file('test/data/SorterTest7.txt', 'test/data/SorterTest7-result.txt', sorter)
self.sort_file('test/data/SorterTest7.txt',
'test/data/SorterTest7-result.txt', sorter)
def test_sort8(self):
""" Descendingly sorted by importance """
def test_sort08(self):
""" Descendingly sorted by importance. """
sorter = Sorter('desc:importance')
self.sort_file('test/data/SorterTest8.txt', 'test/data/SorterTest8-result.txt', sorter)
self.sort_file('test/data/SorterTest8.txt',
'test/data/SorterTest8-result.txt', sorter)
def test_sort9(self):
def test_sort09(self):
"""
Sort on multiple levels: first descending importance, then
ascending priority.
"""
sorter = Sorter('desc:importance,priority')
self.sort_file('test/data/SorterTest9.txt', 'test/data/SorterTest9-result.txt', sorter)
self.sort_file('test/data/SorterTest9.txt',
'test/data/SorterTest9-result.txt', sorter)
def test_sort10(self):
""" Deal with garbage input. """
sorter = Sorter('')
self.sort_file('test/data/SorterTest9.txt', 'test/data/SorterTest9.txt', sorter)
self.sort_file('test/data/SorterTest9.txt',
'test/data/SorterTest9.txt', sorter)
def test_sort11(self):
""" Deal with garbage input. """
sorter = Sorter('fnord')
self.sort_file('test/data/SorterTest9.txt', 'test/data/SorterTest9.txt', sorter)
self.sort_file('test/data/SorterTest9.txt',
'test/data/SorterTest9.txt', sorter)
def test_sort12(self):
""" Deal with garbage input. """
sorter = Sorter('desc:importance,,priority')
self.sort_file('test/data/SorterTest9.txt', 'test/data/SorterTest9-result.txt', sorter)
self.sort_file('test/data/SorterTest9.txt',
'test/data/SorterTest9-result.txt', sorter)
def test_sort13(self):
"""
......@@ -119,7 +133,8 @@ class SorterTest(TopydoTest):
dependencies the average importance should be equal.
"""
sorter = Sorter('desc:importance-avg')
self.sort_file('test/data/SorterTest9.txt', 'test/data/SorterTest9-result.txt', sorter)
self.sort_file('test/data/SorterTest9.txt',
'test/data/SorterTest9-result.txt', sorter)
def test_sort14(self):
sorter = Sorter('desc:importance-average')
......@@ -155,5 +170,24 @@ class SorterTest(TopydoTest):
self.assertEqual(print_view(view), todolist_to_string(result))
def test_sort17(self):
"""
Check sorting by project.
"""
sorter = Sorter('project')
self.sort_file('test/data/SorterTest13.txt',
'test/data/SorterTest13-result-project.txt', sorter)
def test_sort18(self):
"""
Check sorting by context.
"""
sorter = Sorter('context')
self.sort_file('test/data/SorterTest13.txt',
'test/data/SorterTest13-result-context.txt', sorter)
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,12 +14,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from freezegun import freeze_time
import unittest
from test.command_testcase import CommandTest
from topydo.commands.TagCommand import TagCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
class TagCommandTest(CommandTest):
def setUp(self):
super(TagCommandTest, self).setUp()
......@@ -33,7 +35,8 @@ class TagCommandTest(CommandTest):
self.todolist = TodoList(todos)
def test_add_tag1(self):
command = TagCommand(["1", "due", "2014-10-22"], self.todolist, self.out, self.error)
command = TagCommand(["1", "due", "2014-10-22"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22")
......@@ -42,7 +45,8 @@ class TagCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
def test_add_tag2(self):
command = TagCommand(["Foo", "due", "2014-10-22"], self.todolist, self.out, self.error)
command = TagCommand(["Foo", "due", "2014-10-22"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22")
......@@ -51,64 +55,74 @@ class TagCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
def test_add_tag3(self):
command = TagCommand(["-a", "2", "due", "2014-10-19"], self.todolist, self.out, self.error)
command = TagCommand(["-a", "2", "due", "2014-10-19"], self.todolist,
self.out, self.error)
command.execute()
self.assertEqual(self.todolist.todo(2).source(), "Bar due:2014-10-22 due:2014-10-19")
self.assertEqual(self.output, "| 2| Bar due:2014-10-22 due:2014-10-19\n")
self.assertEqual(self.todolist.todo(2).source(),
"Bar due:2014-10-22 due:2014-10-19")
self.assertEqual(self.output,
"| 2| Bar due:2014-10-22 due:2014-10-19\n")
self.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
def test_add_tag4(self):
command = TagCommand(["Foox", "due", "2014-10-22"], self.todolist, self.out, self.error)
command = TagCommand(["Foox", "due", "2014-10-22"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number.\n")
def test_set_tag4(self):
command = TagCommand(["3", "due", "2014-10-20"], self.todolist, self.out, self.error)
def test_set_tag04(self):
command = TagCommand(["3", "due", "2014-10-20"], self.todolist,
self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 3| Baz due:2014-10-20\n")
self.assertEqual(self.errors, "")
def test_set_tag5(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "all")
def test_set_tag05(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "all")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
def test_set_tag6(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "1")
def test_set_tag06(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "1")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
def test_set_tag7(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "2")
def test_set_tag07(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "2")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
def test_set_tag8(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "")
def test_set_tag08(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
def test_set_tag9(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "99")
def test_set_tag09(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "99")
command.execute()
self.assertFalse(self.todolist.is_dirty())
......@@ -116,14 +130,39 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_set_tag10(self):
command = TagCommand(["-f", "4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "99")
command = TagCommand(["-f", "4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "99")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output,
"| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "")
@freeze_time('2015, 11, 19')
def test_set_tag11(self):
command = TagCommand(["3", "due", "today"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 3| Baz due:2015-11-19\n")
self.assertEqual(self.errors, "")
def test_set_tag12(self):
"""
Do not convert relative dates for tags that were not configured as
start/due date.
"""
command = TagCommand(["3", "foo", "today"], self.todolist, self.out,
self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.output, "| 3| Baz due:2014-10-20 foo:today\n")
self.assertEqual(self.errors, "")
def test_rm_tag1(self):
def test_rm_tag01(self):
command = TagCommand(["1", "due"], self.todolist, self.out, self.error)
command.execute()
......@@ -131,7 +170,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output, "| 1| Foo\n")
self.assertEqual(self.errors, "")
def test_rm_tag2(self):
def test_rm_tag02(self):
command = TagCommand(["2", "due"], self.todolist, self.out, self.error)
command.execute()
......@@ -139,39 +178,44 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output, "| 2| Bar\n")
self.assertEqual(self.errors, "")
def test_rm_tag3(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error, lambda t: "all")
def test_rm_tag03(self):
command = TagCommand(["4", "due"], self.todolist, self.out,
self.error, lambda t: "all")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord\n")
self.assertEqual(self.output,
" 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord\n")
self.assertEqual(self.errors, "")
def test_rm_tag4(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error, lambda t: "1")
def test_rm_tag04(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error,
lambda t: "1")
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-22\n")
self.assertEqual(self.errors, "")
def test_rm_tag6(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error, lambda t: "99")
def test_rm_tag06(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error,
lambda t: "99")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
def test_rm_tag7(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error, lambda t: "A")
def test_rm_tag07(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error,
lambda t: "A")
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "")
def test_rm_tag8(self):
def test_rm_tag08(self):
command = TagCommand(["5", "due"], self.todolist, self.out, self.error)
command.execute()
......@@ -179,7 +223,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number.\n")
def test_rm_tag9(self):
def test_rm_tag09(self):
command = TagCommand(["A", "due"], self.todolist, self.out, self.error)
command.execute()
......@@ -188,7 +232,8 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number.\n")
def test_rm_tag10(self):
command = TagCommand(["-f", "4", "due"], self.todolist, self.out, self.error, lambda t: "A")
command = TagCommand(["-f", "4", "due"], self.todolist, self.out,
self.error, lambda t: "A")
command.execute()
self.assertTrue(self.todolist.is_dirty())
......@@ -208,7 +253,8 @@ class TagCommandTest(CommandTest):
command.execute()
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
self.assertEqual(self.errors,
command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,20 +14,23 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date, timedelta
import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
def today_date():
today = date.today()
return today.isoformat()
def tomorrow_date():
tomorrow = date.today() + timedelta(days=1)
return tomorrow.isoformat()
class TodoTest(TopydoTest):
def test_due_date1(self):
todo = Todo("(C) Foo due:2014-06-09")
......@@ -37,7 +40,7 @@ class TodoTest(TopydoTest):
def test_false_date(self):
todo = Todo("(C) Foo due:2014-04-31")
self.assertEqual( todo.due_date(), None )
self.assertEqual(todo.due_date(), None)
def test_active1(self):
todo = Todo("(C) Foo due:2014-01-01")
......@@ -87,5 +90,29 @@ class TodoTest(TopydoTest):
todo = Todo("(C) Foo t:2014-01-01 due:2014-01-02")
self.assertEqual(todo.length(), 1)
def test_length4(self):
todo = Todo("(C) Foo)")
self.assertEqual(todo.length(), 0)
def test_length5(self):
todo = Todo("(C) 2015-11-18 Foo)")
self.assertEqual(todo.length(), 0)
def test_length6(self):
todo = Todo("(C) 2015-11-18 Foo due:2015-11-19)")
self.assertEqual(todo.length(), 1)
def test_length7(self):
todo = Todo("(C) 2015-11-18 Foo due:2015-11-18)")
self.assertEqual(todo.length(), 0)
def test_length8(self):
todo = Todo("(C) 2015-11-18 Foo t:2015-11-19 due:2015-11-20)")
self.assertEqual(todo.length(), 1)
def test_length9(self):
todo = Todo("(C) 2015-11-18 Foo due:2015-11-16)")
self.assertEqual(todo.length(), 0)
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,12 +16,13 @@
""" Tests for the TodoBase class. """
from datetime import date, timedelta
import re
import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.TodoBase import TodoBase
from test.TopydoTest import TopydoTest
class TodoBaseTester(TopydoTest):
def test_parse_tag(self):
......@@ -85,7 +86,7 @@ class TodoBaseTester(TopydoTest):
def test_set_tag_double_value(self):
todo = TodoBase("(C) Foo foo:bar baz:bar")
todo.set_tag('foo', 'blah');
todo.set_tag('foo', 'blah')
self.assertTrue(todo.has_tag('foo'))
self.assertTrue(todo.tag_value('foo'), 'blah')
......@@ -97,7 +98,8 @@ class TodoBaseTester(TopydoTest):
todo.set_tag('foo', 'blah')
self.assertTrue(todo.has_tag('foo', 'blah'))
self.assertTrue(todo.has_tag('foo', 'bar') or todo.has_tag('foo', 'baz'))
self.assertTrue(todo.has_tag('foo', 'bar') or
todo.has_tag('foo', 'baz'))
def test_set_tag_empty_value(self):
todo = TodoBase("(C) Foo foo:bar foo:baz")
......@@ -108,14 +110,12 @@ class TodoBaseTester(TopydoTest):
def test_tag_empty_value(self):
""" Tag should not be recorded when there is no value. """
todo = TodoBase("(C) Foo foo:")
self.assertFalse(todo.has_tag('foo'))
def test_tag_empty_key(self):
""" Tag should not be recorded when there is no key. """
todo = TodoBase("(C) Foo :bar")
self.assertFalse(todo.has_tag(''))
......@@ -259,8 +259,8 @@ class TodoBaseTester(TopydoTest):
today_str = today.isoformat()
self.assertEqual(todo.fields['completionDate'], today)
self.assertTrue(re.match('^x ' + today_str + ' 2014-06-12 Foo', \
todo.src))
self.assertTrue(re.match('^x ' + today_str + ' 2014-06-12 Foo',
todo.src))
def test_set_complete3(self):
todo = TodoBase("Foo")
......@@ -280,7 +280,8 @@ class TodoBaseTester(TopydoTest):
today_str = today.isoformat()
self.assertEqual(todo.fields['completionDate'], today)
self.assertTrue(re.match('^x ' + today_str + ' 2014-06-12 Foo', todo.src))
self.assertTrue(re.match('^x ' + today_str + ' 2014-06-12 Foo',
todo.src))
def test_set_complete5(self):
todo = TodoBase("x 2014-06-13 Foo")
......@@ -302,7 +303,7 @@ class TodoBaseTester(TopydoTest):
todo.set_source_text(new_text)
self.assertEqual(todo.src, new_text)
self.assertEqual(todo.priority(),'C')
self.assertEqual(todo.priority(), 'C')
def test_set_creation_date1(self):
todo = TodoBase("Foo")
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,11 +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 six import u
import unittest
from test.TestFacilities import load_file
from test.TopydoTest import TopydoTest
from test.facilities import load_file
from test.topydo_testcase import TopydoTest
class TodoFileTest(TopydoTest):
def test_empty_file(self):
......@@ -29,7 +29,8 @@ class TodoFileTest(TopydoTest):
def test_utf_8(self):
todofile = load_file('test/data/utf-8.txt')
self.assertEqual(todofile[0].source(), u('(C) \u25ba UTF-8 test \u25c4'))
self.assertEqual(todofile[0].source(),
u'(C) \u25ba UTF-8 test \u25c4')
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -19,32 +19,32 @@
import re
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
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
from topydo.lib.TodoListBase import InvalidTodoException, TodoListBase
class TodoListTester(TopydoTest):
def setUp(self):
super(TodoListTester, self).setUp()
self.todofile = TodoFile('test/data/TodoListTest.txt')
lines = [line for line in self.todofile.read() \
if re.search(r'\S', line)]
lines = [line for line in self.todofile.read()
if re.search(r'\S', line)]
self.text = ''.join(lines)
self.todolist = TodoListBase(lines)
def test_contexts(self):
self.assertEqual(set(['Context1', 'Context2']), \
self.todolist.contexts())
self.assertEqual(set(['Context1', 'Context2']),
self.todolist.contexts())
self.assertFalse(self.todolist.is_dirty())
def test_projects(self):
self.assertEqual(set(['Project1', 'Project2']), \
self.todolist.projects())
self.assertEqual(set(['Project1', 'Project2']),
self.todolist.projects())
self.assertFalse(self.todolist.is_dirty())
def test_add1(self):
......@@ -53,10 +53,10 @@ class TodoListTester(TopydoTest):
todo = self.todolist.add(text)
self.assertEqual(self.todolist.todo(count+1).source(), text)
self.assertEqual(set(['Project1', 'Project2', 'Project3']), \
self.todolist.projects())
self.assertEqual(set(['Context1', 'Context2', 'Context3']), \
self.todolist.contexts())
self.assertEqual(set(['Project1', 'Project2', 'Project3']),
self.todolist.projects())
self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts())
self.assertEqual(self.todolist.number(todo), 6)
self.assertTrue(self.todolist.is_dirty())
......@@ -70,7 +70,8 @@ class TodoListTester(TopydoTest):
self.todolist.add('\n(C) New task')
self.assertEqual(self.todolist.count(), count + 1)
self.assertEqual(self.todolist.todo(count + 1).source(), '(C) New task')
self.assertEqual(self.todolist.todo(count + 1).source(),
'(C) New task')
self.assertEqual(self.todolist.todo(count + 1).priority(), 'C')
def test_add3b(self):
......@@ -78,7 +79,8 @@ class TodoListTester(TopydoTest):
self.todolist.add('(C) New task\n')
self.assertEqual(self.todolist.count(), count + 1)
self.assertEqual(self.todolist.todo(count + 1).source(), '(C) New task')
self.assertEqual(self.todolist.todo(count + 1).source(),
'(C) New task')
self.assertEqual(self.todolist.todo(count + 1).priority(), 'C')
def test_add4(self):
......@@ -96,8 +98,8 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo(2)
self.todolist.delete(todo)
self.assertEqual(self.todolist.todo(2).source(), \
"(C) Baz @Context1 +Project1 key:value")
self.assertEqual(self.todolist.todo(2).source(),
"(C) Baz @Context1 +Project1 key:value")
self.assertEqual(self.todolist.count(), count - 1)
self.assertTrue(self.todolist.is_dirty())
self.assertRaises(InvalidTodoException, self.todolist.number, todo)
......@@ -116,10 +118,10 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo(3)
self.todolist.append(todo, "@Context3")
self.assertEqual(todo.source(), \
"(C) Baz @Context1 +Project1 key:value @Context3")
self.assertEqual(set(['Context1', 'Context2', 'Context3']), \
self.todolist.contexts())
self.assertEqual(todo.source(),
"(C) Baz @Context1 +Project1 key:value @Context3")
self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts())
self.assertTrue(self.todolist.is_dirty())
def test_append2(self):
......@@ -128,8 +130,8 @@ class TodoListTester(TopydoTest):
self.todolist.append(todo, "foo:bar")
self.assertEqual(todo.text(), text)
self.assertEqual(todo.source(), \
"(C) Baz @Context1 +Project1 key:value foo:bar")
self.assertEqual(todo.source(),
"(C) Baz @Context1 +Project1 key:value foo:bar")
def test_append3(self):
todo = self.todolist.todo(3)
......@@ -141,7 +143,8 @@ class TodoListTester(TopydoTest):
def test_todo(self):
count = self.todolist.count()
self.assertRaises(InvalidTodoException, self.todolist.todo, count + 100)
self.assertRaises(InvalidTodoException, self.todolist.todo,
count + 100)
self.assertFalse(self.todolist.is_dirty())
def test_count(self):
......@@ -197,7 +200,8 @@ class TodoListTester(TopydoTest):
def test_uid1(self):
config("test/data/todolist-uid.conf")
self.assertEqual(self.todolist.todo('t5c').source(), "(C) Foo @Context2 Not@Context +Project1 Not+Project")
self.assertEqual(self.todolist.todo('t5c').source(),
"(C) Foo @Context2 Not@Context +Project1 Not+Project")
def test_uid2(self):
""" Changing the priority should not change the identifier. """
......@@ -205,25 +209,25 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo('t5c')
self.todolist.set_priority(todo, 'B')
self.assertEqual(self.todolist.todo('t5c').source(), "(B) Foo @Context2 Not@Context +Project1 Not+Project")
self.assertEqual(self.todolist.todo('t5c').source(),
"(B) Foo @Context2 Not@Context +Project1 Not+Project")
def test_uid3(self):
"""
Must be able to handle integers when text identifiers are enabled.
"""
config("test/data/todolist-uid.conf")
self.assertRaises(InvalidTodoException, self.todolist.todo, 1)
def test_new_uid(self):
""" Make sure that item has new text ID after append. """
config("test/data/todolist-uid.conf")
todo = self.todolist.todo('t5c')
self.todolist.append(todo, "A")
self.assertNotEqual(self.todolist.number(todo), 't5c')
class TodoListDependencyTester(TopydoTest):
def setUp(self):
super(TodoListDependencyTester, self).setUp()
......@@ -241,24 +245,24 @@ class TodoListDependencyTester(TopydoTest):
def test_check_dep(self):
children = self.todolist.children(self.todolist.todo(1))
self.assertEqual(sorted([todo.source() for todo in children]), \
sorted(['Bar p:1', 'Baz p:1 id:2', 'Buzz p:2']))
self.assertEqual(sorted([todo.source() for todo in children]),
sorted(['Bar p:1', 'Baz p:1 id:2', 'Buzz p:2']))
children = self.todolist.children(self.todolist.todo(1), True)
self.assertEqual(sorted([todo.source() for todo in children]), \
sorted(['Bar p:1', 'Baz p:1 id:2']))
self.assertEqual(sorted([todo.source() for todo in children]),
sorted(['Bar p:1', 'Baz p:1 id:2']))
children = self.todolist.children(self.todolist.todo(3))
self.assertEqual(sorted([todo.source() for todo in children]), \
['Buzz p:2'])
self.assertEqual(sorted([todo.source() for todo in children]),
['Buzz p:2'])
parents = self.todolist.parents(self.todolist.todo(4))
self.assertEqual(sorted([todo.source() for todo in parents]), \
sorted(['Foo id:1', 'Baz p:1 id:2']))
self.assertEqual(sorted([todo.source() for todo in parents]),
sorted(['Foo id:1', 'Baz p:1 id:2']))
parents = self.todolist.parents(self.todolist.todo(4), True)
self.assertEqual(sorted([todo.source() for todo in parents]), \
['Baz p:1 id:2'])
self.assertEqual(sorted([todo.source() for todo in parents]),
['Baz p:1 id:2'])
self.assertEqual(self.todolist.children(self.todolist.todo(2)), [])
self.assertEqual(self.todolist.parents(self.todolist.todo(1)), [])
......@@ -276,7 +280,6 @@ class TodoListDependencyTester(TopydoTest):
Make sure that previous add_dependency invocation stored the
edge_id properly.
"""
todo1 = self.todolist.todo(1)
todo4 = self.todolist.todo(4)
todo5 = self.todolist.todo(5)
......@@ -291,7 +294,6 @@ class TodoListDependencyTester(TopydoTest):
"""
Test that projects are not added double.
"""
todo6 = self.todolist.todo(6)
todo7 = self.todolist.todo(7)
projects = todo7.projects().copy()
......@@ -329,6 +331,15 @@ class TodoListDependencyTester(TopydoTest):
self.assertEqual(str(self.todolist), old)
def test_remove_dep3(self):
""" Try to remove non-existing dependency. """
old = str(self.todolist)
from_todo = self.todolist.todo(4)
to_todo = self.todolist.todo(1)
self.todolist.remove_dependency(from_todo, to_todo)
self.assertEqual(str(self.todolist), old)
def test_remove_todo_check_children(self):
todo = self.todolist.todo(2)
self.todolist.delete(todo)
......@@ -361,16 +372,26 @@ class TodoListDependencyTester(TopydoTest):
self.assertTrue(todolist.todo_by_dep_id('1'))
self.assertFalse(todolist.todo_by_dep_id('2'))
class TodoListCleanDependencyTester(TopydoTest):
"""
Tests for cleaning up the graph:
* Transitive reduction
* Remove obsolete id: tags
* Remove obsolete p: tags
"""
def setUp(self):
super(TodoListCleanDependencyTester, self).setUp()
self.todolist = TodoList([])
def test_clean_dependencies1(self):
""" Clean p: tags from non-existing parent items. """
self.todolist.add("Bar p:1")
self.todolist.add("Baz p:1 id:2")
self.todolist.add("Buzz p:2")
def test_clean_dependencies(self):
self.todolist.clean_dependencies()
self.assertFalse(self.todolist.todo(1).has_tag('p'))
......@@ -378,5 +399,40 @@ class TodoListCleanDependencyTester(TopydoTest):
self.assertTrue(self.todolist.todo(2).has_tag('id', '2'))
self.assertTrue(self.todolist.todo(3).has_tag('p', '2'))
def test_clean_dependencies2(self):
""" Clean p: items when siblings are still connected to parent. """
self.todolist.add("Foo id:1")
self.todolist.add("Bar p:1")
self.todolist.add("Baz p:1 id:2")
self.todolist.add("Buzz p:1 p:2")
self.todolist.clean_dependencies()
self.assertFalse(self.todolist.todo(4).has_tag('p', '1'))
self.assertTrue(self.todolist.todo(1).has_tag('id', '1'))
self.assertTrue(self.todolist.todo(2).has_tag('p', '1'))
def test_clean_dependencies3(self):
""" Clean id: tags from todo items without child todos. """
self.todolist.add("Foo id:1")
self.todolist.clean_dependencies()
self.assertFalse(self.todolist.todo(1).has_tag('id'))
def test_clean_dependencies4(self):
""" Clean p: items when siblings are still connected to parent. """
self.todolist.add("Foo id:1")
self.todolist.add("Bar p:1")
self.todolist.add("Baz p:1 id:2")
self.todolist.add("Buzz p:2 p:1")
self.todolist.clean_dependencies()
self.assertFalse(self.todolist.todo(4).has_tag('p', '1'))
self.assertTrue(self.todolist.todo(1).has_tag('id', '1'))
self.assertTrue(self.todolist.todo(2).has_tag('p', '1'))
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,12 +16,13 @@
import unittest
from test.facilities import load_file, print_view, todolist_to_string
from test.topydo_testcase import TopydoTest
from topydo.lib import Filter
from topydo.lib.Sorter import Sorter
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
class ViewTest(TopydoTest):
def test_view(self):
......@@ -38,4 +39,3 @@ class ViewTest(TopydoTest):
if __name__ == '__main__':
unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -18,10 +18,10 @@ import unittest
from topydo.lib.Config import config
class TopydoTest(unittest.TestCase):
def tearDown(self):
"""
Make sure that every test case leaves a clean configuration.
"""
config("")
......@@ -5,7 +5,9 @@ default_command = ls
; filename = todo.txt
; archive_filename = done.txt
colors = 1
identifiers = linenumber ; or: text
; identifiers can be 'linenumber' or 'text'
identifiers = linenumber
backup_count = 5
[add]
auto_creation_date = 1
......@@ -14,6 +16,7 @@ auto_creation_date = 1
hide_tags = id,p,ical
indent = 0
list_limit = -1
list_format = |%I| %x %{(}p{)} %c %s %k %{due:}d %{t:}t
[tags]
tag_start = t
......@@ -45,3 +48,18 @@ append_parent_contexts = 0
; context_color = magenta
; metadata_color = green
; link_color = light-cyan
[aliases]
;showall = ls -x
;next = ls -n 1
;top = ls -F '%I %x %P %S %k %{(}H{)}' -N
;star = tag {} star 1
;unstar = tag {} star
;lsproj = lsprj
;listprj = lsprj
;listproj = lsprj
;listproject = lsprj
;listprojects = lsprj
;listcon = lscon
;listcontext = lscon
;listcontexts = lscon
......@@ -32,21 +32,14 @@ _SUBCOMMAND_MAP = {
'depri': 'DepriCommand',
'do': 'DoCommand',
'edit': 'EditCommand',
'exit': 'ExitCommand', # used for the prompt
'exit': 'ExitCommand', # used for the prompt
'ls': 'ListCommand',
'lscon': 'ListContextCommand',
'listcon': 'ListContextCommand',
'listcontext': 'ListContextCommand',
'listcontexts': 'ListContextCommand',
'lsprj': 'ListProjectCommand',
'lsproj': 'ListProjectCommand',
'listprj': 'ListProjectCommand',
'listproj': 'ListProjectCommand',
'listproject': 'ListProjectCommand',
'listprojects': 'ListProjectCommand',
'postpone': 'PostponeCommand',
'pri': 'PriorityCommand',
'quit': 'ExitCommand',
'revert': 'RevertCommand',
'rm': 'DeleteCommand',
'sort': 'SortCommand',
'tag': 'TagCommand',
......@@ -54,8 +47,7 @@ _SUBCOMMAND_MAP = {
def get_subcommand(p_args):
"""
Retrieves the to-be executed Command and returns a tuple
(Command, args).
Retrieves the to-be executed Command and returns a tuple (Command, args).
If args is an empty list, then the Command that corresponds with the
default command specified in the configuration will be returned.
......@@ -79,13 +71,43 @@ def get_subcommand(p_args):
__import__(modulename, globals(), locals(), [classname], 0)
return getattr(sys.modules[modulename], classname)
def join_args(p_cli_args, p_alias_args):
"""
Returns properly joined args from alias definition and from user input.
"""
if '{}' in p_alias_args:
pos = p_alias_args.index('{}')
args = p_alias_args[:pos] + p_cli_args + p_alias_args[pos+1:]
else:
args = p_alias_args + p_cli_args
return args
def resolve_alias(p_alias, p_args):
"""
Resolves a subcommand alias and returns a tuple (Command, args).
If alias resolves to non-existent command, main help message is
returned.
"""
real_subcommand, alias_args = alias_map[p_alias]
try:
result = import_subcommand(real_subcommand)
args = join_args(p_args, alias_args)
return (result, args)
except KeyError:
return get_subcommand(['help'])
result = None
args = p_args
alias_map = config().aliases()
try:
subcommand = p_args[0]
if subcommand in _SUBCOMMAND_MAP:
if subcommand in alias_map:
result, args = resolve_alias(subcommand, args[1:])
elif subcommand in _SUBCOMMAND_MAP:
result = import_subcommand(subcommand)
args = args[1:]
elif subcommand == 'help':
......@@ -100,13 +122,16 @@ def get_subcommand(p_args):
pass
else:
p_command = config().default_command()
if p_command in _SUBCOMMAND_MAP:
if p_command in alias_map:
result, args = resolve_alias(p_command, args)
elif p_command in _SUBCOMMAND_MAP:
result = import_subcommand(p_command)
# leave args unchanged
except IndexError:
p_command = config().default_command()
if p_command in _SUBCOMMAND_MAP:
if p_command in alias_map:
result, args = resolve_alias(p_command, args)
elif p_command in _SUBCOMMAND_MAP:
result = import_subcommand(p_command)
return (result, args)
......@@ -34,10 +34,12 @@ except ConfigError as config_error:
from topydo.Commands import get_subcommand
from topydo.lib import TodoList
class CLIApplication(CLIApplicationBase):
"""
Class that represents the (original) Command Line Interface of Topydo.
"""
def __init__(self):
super(CLIApplication, self).__init__()
......@@ -50,7 +52,7 @@ class CLIApplication(CLIApplicationBase):
(subcommand, args) = get_subcommand(args)
if subcommand == None:
if subcommand is None:
self._usage()
if self._execute(subcommand, args) == False:
......@@ -58,6 +60,7 @@ class CLIApplication(CLIApplicationBase):
else:
self._post_execute()
def main():
""" Main entry point of the CLI. """
CLIApplication().run()
......
......@@ -21,10 +21,10 @@ I/O on the command-line.
import getopt
import sys
from six import PY2
from six.moves import input
MAIN_OPTS = "ac:d:ht:v"
READ_ONLY_COMMANDS = ('List', 'ListContext', 'ListProject')
def usage():
""" Prints the command-line usage of topydo. """
......@@ -55,12 +55,14 @@ Available commands:
* listprojects (lsprj)
* postpone
* pri
* revert
* sort
* tag
Run `topydo help <subcommand>` for command-specific help.
""")
def write(p_file, p_string):
"""
Write p_string to file p_file, trailed by a newline character.
......@@ -73,11 +75,12 @@ def write(p_file, p_string):
if p_string:
p_file.write(p_string + "\n")
def error(p_string):
""" Writes an error on the standard error. """
write(sys.stderr, p_string)
def version():
""" Print the current version and exit. """
from topydo.lib.Version import VERSION, LICENSE
......@@ -96,13 +99,12 @@ except ConfigError as config_error:
error(str(config_error))
sys.exit(1)
from topydo.commands.ArchiveCommand import ArchiveCommand
from topydo.commands.SortCommand import SortCommand
from topydo.lib import TodoFile
from topydo.lib import TodoList
from topydo.lib import TodoListBase
from topydo.lib.Utils import escape_ansi
class CLIApplicationBase(object):
"""
Base class for a Command Line Interfaces (CLI) for topydo. Examples are the
......@@ -110,10 +112,12 @@ class CLIApplicationBase(object):
Handles input/output of the various subcommands.
"""
def __init__(self):
self.todolist = TodoList.TodoList([])
self.todofile = None
self.do_archive = True
self.backup = None
def _usage(self):
usage()
......@@ -122,9 +126,6 @@ class CLIApplicationBase(object):
def _process_flags(self):
args = sys.argv[1:]
if PY2:
args = [arg.decode('utf-8') for arg in args]
try:
opts, args = getopt.getopt(args, MAIN_OPTS)
except getopt.GetoptError as e:
......@@ -165,7 +166,11 @@ class CLIApplicationBase(object):
archive_file = TodoFile.TodoFile(config().archive())
archive = TodoListBase.TodoListBase(archive_file.read())
if self.backup:
self.backup.add_archive(archive)
if archive:
from topydo.commands.ArchiveCommand import ArchiveCommand
command = ArchiveCommand(self.todolist, archive)
command.execute()
......@@ -173,28 +178,34 @@ class CLIApplicationBase(object):
archive_file.write(archive.print_todos())
def _help(self, args):
if args == None:
pass # TODO
if args is None:
pass # TODO
else:
pass # TODO
pass # TODO
def _input(self):
"""
Returns a function that retrieves user input.
"""
return input
def is_read_only(self, p_command):
""" Returns True when the given command class is read-only. """
read_only_commands = tuple(cmd + 'Command' for cmd in ('Revert', ) +
READ_ONLY_COMMANDS)
return p_command.__module__.endswith(read_only_commands)
def _execute(self, p_command, p_args):
"""
Execute a subcommand with arguments. p_command is a class (not an
object).
"""
if config().backup_count() > 0 and p_command and not self.is_read_only(p_command):
call = [p_command.__module__.lower()[16:-7]] + p_args # strip "topydo.commands" and "Command"
from topydo.lib.ChangeSet import ChangeSet
self.backup = ChangeSet(self.todolist, p_call=call)
command = p_command(
p_args,
self.todolist,
lambda o: write(sys.stdout, o),
error,
self._input())
input)
if command.execute() != False:
return True
......@@ -215,10 +226,15 @@ class CLIApplicationBase(object):
self._archive()
if config().keep_sorted():
from topydo.commands.SortCommand import SortCommand
self._execute(SortCommand, [])
if self.backup:
self.backup.save(self.todolist)
self.todofile.write(self.todolist.print_todos())
self.backup = None
def run(self):
raise NotImplementedError
......@@ -17,11 +17,12 @@
""" Entry file for the topydo Prompt interface (CLI). """
import os.path
import shlex
import sys
from topydo.cli.CLIApplicationBase import CLIApplicationBase, error, usage
from topydo.cli.TopydoCompleter import TopydoCompleter
from prompt_toolkit.shortcuts import get_input
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.history import InMemoryHistory
from topydo.lib.Config import config, ConfigError
......@@ -39,6 +40,7 @@ from topydo.Commands import get_subcommand
from topydo.lib import TodoFile
from topydo.lib import TodoList
def _todotxt_mtime():
"""
Returns the mtime for the configured todo.txt file.
......@@ -49,11 +51,13 @@ def _todotxt_mtime():
# file not found
return None
class PromptApplication(CLIApplicationBase):
"""
This class implements a variant of topydo's CLI showing a shell and
offering auto-completion thanks to the prompt toolkit.
"""
def __init__(self):
super(PromptApplication, self).__init__()
......@@ -69,7 +73,6 @@ class PromptApplication(CLIApplicationBase):
If the modification time of the todo.txt file is equal to the last time
it was checked, nothing will be done.
"""
current_mtime = _todotxt_mtime()
if not self.todofile or self.mtime != current_mtime:
......@@ -77,8 +80,6 @@ class PromptApplication(CLIApplicationBase):
self.todolist = TodoList.TodoList(self.todofile.read())
self.mtime = current_mtime
# suppress upstream issue with Python 2.7
# pylint: disable=no-value-for-parameter
self.completer = TopydoCompleter(self.todolist)
def run(self):
......@@ -90,32 +91,32 @@ class PromptApplication(CLIApplicationBase):
self._load_file()
try:
user_input = get_input(u'topydo> ', history=history,
completer=self.completer,
complete_while_typing=False).split()
user_input = prompt(u'topydo> ', history=history,
completer=self.completer,
complete_while_typing=False)
user_input = shlex.split(user_input)
except (EOFError, KeyboardInterrupt):
sys.exit(0)
mtime_after = _todotxt_mtime()
(subcommand, args) = get_subcommand(user_input)
if self.mtime != mtime_after:
# refuse to perform operations such as 'del' and 'do' if the
# todo.txt file has been changed in the background.
# refuse to perform operations such as 'del' and 'do' if the
# todo.txt file has been changed in the background.
if subcommand and not self.is_read_only(subcommand) and self.mtime != mtime_after:
error("WARNING: todo.txt file was modified by another application.\nTo prevent unintended changes, this operation was not executed.")
continue
(subcommand, args) = get_subcommand(user_input)
try:
if self._execute(subcommand, args) != False:
self._post_execute()
except TypeError:
usage()
def main():
""" Main entry point of the prompt interface. """
PromptApplication().run()
if __name__ == '__main__':
main()
......@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module provides a completer class that can be used by get_input provided
This module provides a completer class that can be used by the prompt provided
by the prompt toolkit.
"""
......@@ -23,18 +23,21 @@ import datetime
import re
from prompt_toolkit.completion import Completer, Completion
from topydo.lib.Config import config
from topydo.Commands import _SUBCOMMAND_MAP
from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date
def _subcommands(p_word_before_cursor):
""" Generator for subcommand name completion. """
subcommands = [sc for sc in sorted(_SUBCOMMAND_MAP.keys()) if
sc_map = config().aliases()
sc_map.update(_SUBCOMMAND_MAP)
subcommands = [sc for sc in sorted(sc_map.keys()) if
sc.startswith(p_word_before_cursor)]
for command in subcommands:
yield Completion(command, -len(p_word_before_cursor))
def _dates(p_word_before_cursor):
""" Generator for date completion. """
def _date_suggestions():
......@@ -79,11 +82,13 @@ def _dates(p_word_before_cursor):
yield Completion(reldate, -len(value), display_meta=to_absolute(reldate))
class TopydoCompleter(Completer):
"""
Completer class that completes projects, contexts, dates and
subcommands.
"""
def __init__(self, p_todolist):
self.todolist = p_todolist
......@@ -106,7 +111,8 @@ class TopydoCompleter(Completer):
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)
is_first_word = not re.match(r'\s*\S+\s',
p_document.current_line_before_cursor)
if is_first_word:
return _subcommands(word_before_cursor)
......
......@@ -16,15 +16,18 @@
""" Entry file for the Python todo.txt CLI. """
import sys
import getopt
from topydo.cli.CLIApplicationBase import MAIN_OPTS, error
import sys
from topydo.cli.CLI import CLIApplication
from topydo.cli.CLIApplicationBase import MAIN_OPTS, error
# enable color on windows CMD
if "win32" in sys.platform:
import colorama
colorama.init()
def main():
""" Main entry point of the CLI. """
try:
......
......@@ -16,20 +16,21 @@
""" Provides the AddCommand class that implements the 'add' subcommand. """
from datetime import date
import re
from sys import stdin
import codecs
import re
from datetime import date
from os.path import expanduser
from sys import stdin
from topydo.lib.Config import config
from topydo.lib.Command import Command
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.Config import config
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException
class AddCommand(Command):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, # pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -60,11 +61,12 @@ class AddCommand(Command):
def _add_todo(self, p_todo_text):
def _preprocess_input_todo(p_todo_text):
"""
Preprocesses user input when adding a task.
Pre-processes user input when adding a task.
It detects a priority mid-sentence and puts it at the start.
"""
todo_text = re.sub(r'^(.+) (\([A-Z]\))(.*)$', r'\2 \1\3', p_todo_text)
todo_text = re.sub(r'^(.+) (\([A-Z]\))(.*)$', r'\2 \1\3',
p_todo_text)
return todo_text
......
......@@ -15,16 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException
class AppendCommand(Command):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(AppendCommand, self).__init__(
p_args, p_todolist, p_out, p_err, p_prompt=lambda a: None)
super(AppendCommand, self).__init__(p_args, p_todolist, p_out, p_err,
p_prompt)
def execute(self):
if not super(AppendCommand, self).execute():
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,6 +16,7 @@
from topydo.lib.Command import Command
class ArchiveCommand(Command):
def __init__(self, p_todolist, p_archive_list):
"""
......
......@@ -16,8 +16,9 @@
from topydo.lib.DCommand import DCommand
class DeleteCommand(DCommand):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,16 +14,17 @@
# 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 import Filter
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
class DepCommand(Command):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -135,9 +136,9 @@ class DepCommand(Command):
def help(self):
return """\
* add : Adds a dependency. Using 1 before 2 creates a dependency
from todo item 2 to 1.
* rm (alias: del) : Removes a dependency.
* ls : Lists all dependencies to or from a certain todo.
* clean (alias: gc): Removes redundant id or p tags.
* add : Adds a dependency. Using 1 before 2 creates a dependency
from todo item 2 to 1.
* rm (alias: del) : Removes a dependency.
* ls : Lists all dependencies to or from a certain todo.
* clean (alias: gc) : Removes redundant id or p tags.
"""
......@@ -15,10 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
class DepriCommand(MultiCommand):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......
......@@ -18,12 +18,14 @@ from datetime import date
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, NoRecurrenceException
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.Recurrence import NoRecurrenceException, advance_recurring_todo
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.Utils import date_string_to_date
class DoCommand(DCommand):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -47,7 +49,10 @@ class DoCommand(DCommand):
self.strict_recurrence = True
elif p_opt == "-d" or p_opt == "--date":
try:
self.completion_date = date_string_to_date(p_value)
self.completion_date = relative_date_to_date(p_value)
if not self.completion_date:
self.completion_date = date_string_to_date(p_value)
except ValueError:
self.completion_date = date.today()
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -15,17 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from subprocess import call, check_call, CalledProcessError
import tempfile
from subprocess import CalledProcessError, check_call
from six import u
from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.Config import config
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.Todo import Todo
from topydo.lib.TodoList import TodoList
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
# the true and only editor
DEFAULT_EDITOR = 'vi'
......@@ -34,10 +31,16 @@ DEFAULT_EDITOR = 'vi'
# cannot use super() inside the class itself
BASE_TODOLIST = lambda tl: super(TodoList, tl)
def _get_file_mtime(p_file):
return os.stat(p_file.name).st_mtime
def _is_edited(p_orig_mtime, p_file):
return p_orig_mtime < _get_file_mtime(p_file)
class EditCommand(MultiCommand):
def __init__(self, p_args, p_todolist, p_output, p_error, p_input):
super(EditCommand, self).__init__(p_args, p_todolist, p_output,
p_error, p_input)
p_error, p_input)
if len(self.args) == 0:
self.multi_mode = False
......@@ -85,14 +88,14 @@ class EditCommand(MultiCommand):
return 1
except(OSError):
self.error('There is no such editor as: ' + editor + '. '
'Check your $EDITOR and/or $PATH')
'Check your $EDITOR and/or $PATH')
def _catch_todo_errors(self):
errors = []
if len(self.invalid_numbers) > 1 or len(self.invalid_numbers) > 0 and len(self.todos) > 0:
for number in self.invalid_numbers:
errors.append(u("Invalid todo number given: {}.").format(number))
errors.append(u"Invalid todo number given: {}.".format(number))
elif len(self.invalid_numbers) == 1 and len(self.todos) == 0:
errors.append("Invalid todo number given.")
......@@ -105,10 +108,12 @@ class EditCommand(MultiCommand):
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
temp_todos = self._todos_to_temp()
orig_mtime = _get_file_mtime(temp_todos)
if not self._open_in_editor(temp_todos.name):
new_todos = self._todos_from_temp(temp_todos)
if len(new_todos) == len(self.todos):
if _is_edited(orig_mtime, temp_todos):
for todo in self.todos:
BASE_TODOLIST(self.todolist).delete(todo)
......@@ -116,8 +121,7 @@ class EditCommand(MultiCommand):
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.')
self.error('Editing aborted. Nothing to do.')
else:
self.error(self.usage())
......
......@@ -18,11 +18,13 @@ import sys
from topydo.lib.Command import Command
class ExitCommand(Command):
"""
A command that exits topydo. Used for the 'exit' and 'quit' subcommands on
the prompt CLI.
"""
def __init__(self, p_args, p_todolist, p_output, p_error, p_input):
super(ExitCommand, self).__init__(p_args, p_todolist, p_output, p_error,
p_input)
......@@ -32,4 +34,3 @@ class ExitCommand(Command):
return False
sys.exit(0)
......@@ -14,18 +14,17 @@
# 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.ExpressionCommand import ExpressionCommand
from topydo.lib.Config import config
from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.Filter import InstanceFilter
from topydo.lib.PrettyPrinter import pretty_printer_factory
from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterIndentFilter,
PrettyPrinterHideTagFilter
)
from topydo.lib.IcalPrinter import IcalPrinter
from topydo.lib.JsonPrinter import JsonPrinter
from topydo.lib.prettyprinters.Format import PrettyPrinterFormatFilter
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import get_terminal_size
class ListCommand(ExpressionCommand):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -35,27 +34,24 @@ class ListCommand(ExpressionCommand):
self.printer = None
self.sort_expression = config().sort_string()
self.show_all = False
self.ids = None
self.format = config().list_format()
def _poke_icalendar(self):
"""
Attempts to import the icalendar package. Returns True if it
succeeds, otherwise False.
Raises a SyntaxError when icalendar couldn't be imported (most likely
under Python 3.2.
"""
try:
import icalendar as _
except ImportError: # pragma: no cover
except ImportError: # pragma: no cover
self.error("icalendar package is not installed.")
return False
# may also raise SyntaxError, but we'll deal with that in execute()
return True
def _process_flags(self):
opts, args = self.getopt('f:s:x')
opts, args = self.getopt('f:F:i:n:Ns:x')
for opt, value in opts:
if opt == '-x':
......@@ -64,15 +60,56 @@ class ListCommand(ExpressionCommand):
self.sort_expression = value
elif opt == '-f':
if value == 'json':
from topydo.lib.JsonPrinter import JsonPrinter
self.printer = JsonPrinter()
elif value == 'ical':
if self._poke_icalendar():
from topydo.lib.IcalPrinter import IcalPrinter
self.printer = IcalPrinter(self.todolist)
else:
self.printer = None
elif opt == '-F':
self.format = value
elif opt == '-N':
# 2 lines are assumed to be taken up by printing the next prompt
# display at least one item
self.limit = max(get_terminal_size().lines - 2, 1)
elif opt == '-n':
try:
self.limit = int(value)
except ValueError:
pass # use default value in configuration
elif opt == '-i':
self.ids = value.split(',')
# when a user requests a specific ID, it should always be shown
self.show_all = True
self.args = args
def _filters(self):
"""
Additional filters to select particular todo items given with the -i
flag.
"""
filters = super(ListCommand, self)._filters()
if self.ids:
def get_todo(p_id):
"""
Safely obtains a todo item given the user-supplied ID.
Returns None if an invalid ID was entered.
"""
try:
return self.todolist.todo(p_id)
except InvalidTodoException:
return None
todos = [get_todo(i) for i in self.ids]
filters.append(InstanceFilter(todos))
return filters
def _print(self):
"""
Prints the todos in the right format.
......@@ -81,15 +118,14 @@ class ListCommand(ExpressionCommand):
printing). If a format was specified on the commandline, this format is
sent to the output.
"""
if self.printer == None:
if self.printer is None:
# create a standard printer with some filters
indent = config().list_indent()
final_format = ' ' * indent + self.format
hidden_tags = config().hidden_tags()
filters = []
filters.append(PrettyPrinterIndentFilter(indent))
filters.append(PrettyPrinterHideTagFilter(hidden_tags))
filters.append(PrettyPrinterFormatFilter(self.todolist, final_format))
self.printer = pretty_printer_factory(self.todolist, filters)
......@@ -101,7 +137,7 @@ class ListCommand(ExpressionCommand):
try:
self._process_flags()
except SyntaxError: # pragma: no cover
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
......@@ -110,7 +146,8 @@ class ListCommand(ExpressionCommand):
return True
def usage(self):
return """ Synopsis: ls [-x] [-s <sort_expression>] [-f <format>] [expression]"""
return """Synopsis: ls [-x] [-s <sort_expression>] [-f <output format>]
[-F <format string>] [-i <item numbers>] [-N | -n <integer>] [expression]"""
def help(self):
return """\
......@@ -124,12 +161,46 @@ When an expression is given, only the todos matching that expression are shown.
-f : Specify the output format, being 'text' (default), 'ical' or 'json'.
* 'text' - Text output with colors and identation if applicable.
* 'text' - Text output with colors and indentation if applicable.
* 'ical' - iCalendar (RFC 2445). Is not supported in Python 3.2. Be aware
that this is not a read-only operation, todo items may obtain
an 'ical' tag with a unique ID. Completed todo items may be
archived.
* 'json' - Javascript Object Notation (JSON)
-F : Specify the format of the text ('text' format), which may contain
placeholders that may be expanded if the todo has such attribute. If such
attribute does not exist, then it expands to an empty string.
%c: Absolute creation date.
%C: Relative creation date.
%d: Absolute due date.
%D: Relative due date.
%h: Relative due and start date (due in 3 days, started 3 days ago)
%H: Like %h with creation date.
%i: Todo number.
%I: Todo number padded with spaces (always 3 characters wide).
%k: List of tags separated by spaces (excluding hidden tags).
%K: List of all tags separated by spaces.
%p: Priority.
%P: Priority or placeholder space if no priority.
%s: Todo text.
%S: Todo text, truncated such that an item fits on one line.
%t: Absolute creation date.
%T: Relative creation date.
%x: 'x' followed by absolute completion date.
%X: 'x' followed by relative completion date.
\%: Literal percent sign.
Conditional characters can be added with blocks surrounded by curly
braces, they will only appear when a placeholder expanded to a value.
E.g. %{(}p{)} will print (C) when the todo item has priority C, or ''
(empty string) when an item has no priority set.
A tab character serves as a marker to start right alignment.
-i : Comma separated list of todo IDs to print.
-n : Number of items to display. Defaults to the value in the configuration.
-N : Limit number of items displayed such that they fit on the terminal.
-s : Sort the list according to a sort expression. Defaults to the expression
in the configuration.
-x : Show all todos (i.e. do not filter on dependencies or relevance).
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,8 +16,9 @@
from topydo.lib.Command import Command
class ListContextCommand(Command):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,8 +16,9 @@
from topydo.lib.Command import Command
class ListProjectCommand(Command):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......
......@@ -16,14 +16,15 @@
from datetime import date, timedelta
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.Config import config
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.Utils import date_string_to_date
class PostponeCommand(MultiCommand):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -84,8 +85,8 @@ Synopsis: postpone [-s] <NUMBER> [<NUMBER2> ...] <PATTERN>"
return """\
Postpone the todo item(s) with the given number(s) and the given pattern.
Postponing is done by adjusting the due date(s) of the todo(s), and if the -s flag is
given, the start date accordingly.
Postponing is done by adjusting the due date(s) of the todo(s), and if the -s
flag is given, the start date accordingly.
It is also possible to postpone items as complete with an expression using
the -e flag. Use -x to also process todo items that are normally invisible
......
......@@ -17,11 +17,12 @@
import re
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.Utils import is_valid_priority
class PriorityCommand(MultiCommand):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, # pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -32,7 +33,7 @@ class PriorityCommand(MultiCommand):
def _execute_multi_specific(self):
def normalize_priority(p_priority):
match = re.search(r'\b([A-Z])\b', p_priority)
match = re.search(r'\b([A-Z])\b', p_priority.upper())
return match.group(1) if match else p_priority
priority = normalize_priority(self.args[-1])
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command
from topydo.lib.ChangeSet import ChangeSet
from topydo.lib import TodoFile
from topydo.lib import TodoList
from topydo.lib.Config import config
class RevertCommand(Command):
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(RevertCommand, self).__init__(p_args, p_todolist, p_out, p_err,
p_prompt)
def execute(self):
if not super(RevertCommand, self).execute():
return False
archive_file = TodoFile.TodoFile(config().archive())
archive = TodoList.TodoList(archive_file.read())
last_change = ChangeSet()
try:
last_change.get_backup(self.todolist)
last_change.apply(self.todolist, archive)
archive_file.write(archive.print_todos())
last_change.delete()
self.out("Successfully reverted: " + last_change.call)
except (ValueError, KeyError):
self.error('No backup was found for the current state of ' + config().todotxt())
last_change.close()
def usage(self):
return """Synopsis: revert"""
def help(self):
return """\
Reverts the last command.
"""
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -18,8 +18,9 @@ from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config
from topydo.lib.Sorter import Sorter
class SortCommand(Command):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -35,11 +36,10 @@ class SortCommand(Command):
except InvalidCommandArgument:
expression = config().sort_string()
sorter = Sorter(expression) # TODO: validate
sorter = Sorter(expression) # TODO: validate
sorted_todos = sorter.sort(self.todolist.todos())
self.todolist.erase()
self.todolist.add_todos(sorted_todos)
self.todolist.replace(sorted_todos)
def usage(self):
return """Synopsis: sort [expression]"""
......
......@@ -15,11 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.Config import config
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException
class TagCommand(Command):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -67,7 +70,7 @@ class TagCommand(Command):
def _choose(self):
"""
Returns the chosen number of the tag value to process (or "all")
Returns the chosen number of the tag value to process (or "all").
"""
answer = "all"
......@@ -89,7 +92,16 @@ class TagCommand(Command):
return answer
def _convert_relative_dates(self):
if self.tag == config().tag_start() or self.tag == config().tag_due():
real_date = relative_date_to_date(self.value)
if real_date:
self.value = real_date.isoformat()
def _set_helper(self, p_old_value=""):
self._convert_relative_dates()
old_src = self.todo.source()
self.todo.set_tag(self.tag, self.value, self.force_add, p_old_value)
......@@ -103,7 +115,7 @@ class TagCommand(Command):
if answer == "all":
for value in self.current_values:
self._set_helper(value)
elif answer != None and self.value != self.current_values[answer]:
elif answer is not None and self.value != self.current_values[answer]:
self._set_helper(self.current_values[answer])
else:
......@@ -130,6 +142,6 @@ is omitted, the tag is removed from the todo item.
-a : Do not change the current value of the tag if it exists, but add a new
value.
-f : Force setting/removing all values of the tag. Prevents interaction with the
user.
-f : Force setting/removing all values of the tag. Prevents interaction with
the user.
"""
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" This module serves for managing todo and archive changesets. """
import json
import time
import zlib
from copy import deepcopy
from hashlib import sha1
from os import path
from topydo.lib.Config import config
from topydo.lib.TodoList import TodoList
def hash_todolist(p_todolist):
""" Calculates hash for TodoList.TodoList object. """
todolist_hash = sha1(p_todolist.print_todos().encode('utf-8')).hexdigest()
return todolist_hash
def get_backup_path():
""" Returns full path and filename of backup file """
dirname, filename = path.split(path.splitext(config().todotxt())[0])
filename = '.' + filename + '.bak'
return path.join(dirname, filename)
class ChangeSet(object):
""" Class for operations related with backup management. """
def __init__(self, p_todolist=None, p_archive=None, p_call=[]):
self.todolist = deepcopy(p_todolist)
self.archive = deepcopy(p_archive)
self.timestamp = str(int(time.time()))
self.call = ' '.join(p_call)
try:
self.json_file = open(get_backup_path(), 'r+b')
except IOError:
self.json_file = open(get_backup_path(), 'w+b')
self._read()
def _read(self):
"""
Reads backup file from json_file property and sets backup_dict property
with data decompressed and deserialized from that file. If no usable
data is found backup_dict is set to the empty dict.
"""
self.json_file.seek(0)
try:
data = zlib.decompress(self.json_file.read())
self.backup_dict = json.loads(data.decode('utf-8'))
except (EOFError, zlib.error):
self.backup_dict = {}
def _write(self):
"""
Writes data from backup_dict property in serialized and compressed form
to backup file pointed in json_file property.
"""
self.json_file.seek(0)
self.json_file.truncate()
dump = json.dumps(self.backup_dict)
dump_c = zlib.compress(dump.encode('utf-8'))
self.json_file.write(dump_c)
def add_archive(self, p_archive):
""" Sets deep copy of p_archive as archive attribute. """
self.archive = deepcopy(p_archive)
def add_todolist(self, p_todolist):
""" Sets deep copy of p_todolist as todolist attribute. """
self.todolist = deepcopy(p_todolist)
def save(self, p_todolist):
"""
Saves a tuple with archive, todolist and command with its arguments
into the backup file with unix timestamp as the key. Tuple is then
indexed in backup file with combination of hash calculated from
p_todolist and unix timestamp. Backup file is closed afterwards.
"""
self._trim()
current_hash = hash_todolist(p_todolist)
list_todo = (self.todolist.print_todos()+'\n').splitlines(True)
list_archive = (self.archive.print_todos()+'\n').splitlines(True)
self.backup_dict[self.timestamp] = (list_todo, list_archive, self.call)
index = self._get_index()
index.insert(0, (self.timestamp, current_hash))
self._save_index(index)
self._write()
self.close()
def delete(self, p_timestamp=None, p_write=True):
""" Removes backup from the backup file. """
timestamp = p_timestamp or self.timestamp
index = self._get_index()
try:
del self.backup_dict[timestamp]
index.remove(index[[change[0] for change in index].index(timestamp)])
self._save_index(index)
if p_write:
self._write()
except KeyError:
pass
def _get_index(self):
try:
index = self.backup_dict['index']
except KeyError:
self.backup_dict['index'] = []
index = self.backup_dict['index']
return index
def _save_index(self, p_index):
"""
Saves index of backups supplied in p_index into the backup_file
property with 'index' as the key.
"""
self.backup_dict['index'] = p_index
def _trim(self):
"""
Removes oldest backups that exceed the limit configured in backup_count
option.
Does not write back to file system, make sure to call self._write()
afterwards.
"""
index = self._get_index()
backup_limit = config().backup_count() - 1
for changeset in index[backup_limit:]:
self.delete(changeset[0], p_write=False)
def get_backup(self, p_todolist):
"""
Retrieves a backup for p_todolist from backup file and sets todolist,
archive and call attributes to appropriate data from it.
"""
change_hash = hash_todolist(p_todolist)
index = self._get_index()
self.timestamp = index[[change[1] for change in index].index(change_hash)][0]
d = self.backup_dict[self.timestamp]
self.todolist = TodoList(d[0])
self.archive = TodoList(d[1])
self.call = d[2]
def apply(self, p_todolist, p_archive):
""" Applies backup on supplied p_todolist. """
if self.todolist:
p_todolist.replace(self.todolist.todos())
if self.archive:
p_archive.replace(self.archive.todos())
def close(self):
""" Closes backup file. """
self.json_file.close()
......@@ -18,7 +18,8 @@
from topydo.lib.Config import config
NEUTRAL_COLOR = '\033[0m'
NEUTRAL_COLOR = '\033[0m'
class Colors(object):
def __init__(self):
......@@ -48,13 +49,13 @@ class Colors(object):
try:
if p_safe:
if 8 > int(p_int) >=0:
if 8 > int(p_int) >= 0:
return '\033[{};3{}m'.format(decoration, str(p_int))
elif 16 > int(p_int):
p_int = int(p_int) - 8
return '\033[{};1;3{}m'.format(decoration, str(p_int))
if 256 > int(p_int) >=0:
if 256 > int(p_int) >= 0:
return '\033[{};38;5;{}m'.format(decoration, str(p_int))
else:
return NEUTRAL_COLOR
......
......@@ -18,11 +18,13 @@ import getopt
from topydo.lib.PrettyPrinter import PrettyPrinter
class InvalidCommandArgument(Exception):
pass
class Command(object):
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -90,4 +92,3 @@ class Command(object):
def help(self):
""" Returns the help text for this command. """
raise NotImplementedError
......@@ -14,9 +14,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import configparser
import os
from six.moves import configparser
import shlex
class ConfigError(Exception):
def __init__(self, p_text):
......@@ -25,6 +25,7 @@ class ConfigError(Exception):
def __str__(self):
return self.text
class _Config:
def __init__(self, p_path=None, p_overrides=None):
"""
......@@ -40,6 +41,7 @@ class _Config:
"""
self.sections = [
'add',
'aliases',
'colorscheme',
'dep',
'ls',
......@@ -49,46 +51,72 @@ class _Config:
]
self.defaults = {
# topydo
'default_command': 'ls',
'colors': '1',
'filename' : 'todo.txt',
'archive_filename' : 'done.txt',
'identifiers': 'linenumber',
# add
'auto_creation_date': '1',
# ls
'hide_tags': 'id,p,ical',
'indent': 0,
'list_limit': '-1',
# tags
'tag_start': 't',
'tag_due': 'due',
'tag_star': 'star',
# sort
'keep_sorted': '0',
'sort_string': 'desc:importance,due,desc:priority',
'ignore_weekends': '1',
# dep
'append_parent_projects': '0',
'append_parent_contexts': '0',
# colorscheme
'project_color': 'red',
'context_color': 'magenta',
'metadata_color': 'green',
'link_color': 'cyan',
'priority_colors': 'A:cyan,B:yellow,C:blue',
'topydo': {
'default_command': 'ls',
'colors': '1',
'filename': 'todo.txt',
'archive_filename': 'done.txt',
'identifiers': 'linenumber',
'backup_count': '5',
},
'add': {
'auto_creation_date': '1',
},
'ls': {
'hide_tags': 'id,p,ical',
'indent': '0',
'list_limit': '-1',
'list_format': '|%I| %x %{(}p{)} %c %s %k %{due:}d %{t:}t',
},
'tags': {
'tag_start': 't',
'tag_due': 'due',
'tag_star': 'star',
},
'sort': {
'keep_sorted': '0',
'sort_string': 'desc:importance,due,desc:priority',
'ignore_weekends': '1',
},
'dep': {
'append_parent_projects': '0',
'append_parent_contexts': '0',
},
'colorscheme': {
'project_color': 'red',
'context_color': 'magenta',
'metadata_color': 'green',
'link_color': 'cyan',
'priority_colors': 'A:cyan,B:yellow,C:blue',
},
'aliases': {
'lsproj': 'lsprj',
'listprj': 'lsprj',
'listproj': 'lsprj',
'listproject': 'lsprj',
'listprojects': 'lsprj',
'listcon': 'lscon',
'listcontext': 'lscon',
'listcontexts': 'lscon',
},
}
self.config = {}
self.cp = configparser.ConfigParser(self.defaults)
self.cp = configparser.RawConfigParser()
for section in self.defaults:
self.cp.add_section(section)
for option, value in self.defaults[section].items():
self.cp.set(section, option, value)
files = [
"/etc/topydo.conf",
......@@ -100,11 +128,10 @@ class _Config:
# when a path is given, *only* use the values in that file, or the
# defaults listed above.
if p_path != None:
if p_path is not None:
files = [p_path]
self.cp.read(files)
self._supplement_sections()
if p_overrides:
......@@ -126,7 +153,7 @@ class _Config:
try:
return self.cp.getboolean('topydo', 'colors')
except ValueError:
return self.defaults['colors'] == '1'
return self.defaults['topydo']['colors'] == '1'
def todotxt(self):
return os.path.expanduser(self.cp.get('topydo', 'filename'))
......@@ -137,23 +164,32 @@ class _Config:
def identifiers(self):
return self.cp.get('topydo', 'identifiers')
def backup_count(self):
try:
value = self.cp.getint('topydo', 'backup_count')
if value < 0:
value = 0
return value
except ValueError:
return int(self.defaults['topydo']['backup_count'])
def list_limit(self):
try:
return self.cp.getint('ls', 'list_limit')
except ValueError:
return int(self.defaults['list_limit'])
return int(self.defaults['ls']['list_limit'])
def list_indent(self):
try:
return self.cp.getint('ls', 'indent')
except ValueError:
return int(self.defaults['indent'])
return int(self.defaults['ls']['indent'])
def keep_sorted(self):
try:
return self.cp.getboolean('sort', 'keep_sorted')
except ValueError:
return self.defaults['keep_sorted'] == '1'
return self.defaults['sort']['keep_sorted'] == '1'
def sort_string(self):
return self.cp.get('sort', 'sort_string')
......@@ -162,19 +198,19 @@ class _Config:
try:
return self.cp.getboolean('sort', 'ignore_weekends')
except ValueError:
return self.defaults['ignore_weekends'] == '1'
return self.defaults['sort']['ignore_weekends'] == '1'
def append_parent_projects(self):
try:
return self.cp.getboolean('dep', 'append_parent_projects')
except ValueError:
return self.defaults['append_parent_projects'] == '1'
return self.defaults['dep']['append_parent_projects'] == '1'
def append_parent_contexts(self):
try:
return self.cp.getboolean('dep', 'append_parent_contexts')
except ValueError:
return self.defaults['append_parent_contexts'] == '1'
return self.defaults['dep']['append_parent_contexts'] == '1'
def _get_tag(self, p_tag):
try:
......@@ -197,10 +233,13 @@ class _Config:
""" Returns a list of tags to be hidden from the 'ls' output. """
hidden_tags = self.cp.get('ls', 'hide_tags')
# pylint: disable=no-member
return [] if hidden_tags == '' else hidden_tags.split(',')
return [] if hidden_tags == '' else [tag.strip() for tag in
hidden_tags.split(',')]
def priority_colors(self):
""" Returns a dict with priorities as keys and color numbers as value. """
"""
Returns a dict with priorities as keys and color numbers as value.
"""
pri_colors_str = self.cp.get('colorscheme', 'priority_colors')
def _str_to_dict(p_string):
......@@ -213,11 +252,11 @@ class _Config:
try:
if pri_colors_str == '':
pri_colors_dict = {'A':'', 'B': '', 'C': ''}
pri_colors_dict = {'A': '', 'B': '', 'C': ''}
else:
pri_colors_dict = _str_to_dict(pri_colors_str)
except ValueError:
pri_colors_dict = _str_to_dict(self.defaults['priority_colors'])
pri_colors_dict = _str_to_dict(self.defaults['colorscheme']['priority_colors'])
return pri_colors_dict
......@@ -225,31 +264,52 @@ class _Config:
try:
return self.cp.get('colorscheme', 'project_color')
except ValueError:
return int(self.defaults['project_color'])
return int(self.defaults['colorscheme']['project_color'])
def context_color(self):
try:
return self.cp.get('colorscheme', 'context_color')
except ValueError:
return int(self.defaults['context_color'])
return int(self.defaults['colorscheme']['context_color'])
def metadata_color(self):
try:
return self.cp.get('colorscheme', 'metadata_color')
except ValueError:
return int(self.defaults['metadata_color'])
return int(self.defaults['colorscheme']['metadata_color'])
def link_color(self):
try:
return self.cp.get('colorscheme', 'link_color')
except ValueError:
return int(self.defaults['link_color'])
return int(self.defaults['colorscheme']['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'
return self.defaults['add']['auto_creation_date'] == '1'
def aliases(self):
"""
Returns dict with aliases names as keys and pairs of actual
subcommand and alias args as values.
"""
aliases = self.cp.items('aliases')
alias_dict = dict()
for alias, meaning in aliases:
meaning = shlex.split(meaning)
real_subcommand = meaning[0]
alias_args = meaning[1:]
alias_dict[alias] = (real_subcommand, alias_args)
return alias_dict
def list_format(self):
""" Returns the list format used by `ls` """
return self.cp.get('ls', 'list_format')
def config(p_path=None, p_overrides=None):
"""
......@@ -263,7 +323,7 @@ def config(p_path=None, p_overrides=None):
passed value instead. Structure: (section, option) => value
The previous configuration instance will be discarded.
"""
if not config.instance or p_path != None or p_overrides != None:
if not config.instance or p_path is not None or p_overrides is not None:
try:
config.instance = _Config(p_path, p_overrides)
except configparser.ParsingError as perr:
......
......@@ -18,7 +18,8 @@ import re
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
class DCommand(MultiCommand):
"""
......@@ -26,7 +27,7 @@ class DCommand(MultiCommand):
alike.
"""
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -35,7 +36,8 @@ class DCommand(MultiCommand):
self.force = False
self.length = len(self.todolist.todos()) # to determine newly activated todos
# to determine newly activated todos
self.length = len(self.todolist.todos())
def get_flags(self):
return ("f", ["force"])
......@@ -91,7 +93,7 @@ class DCommand(MultiCommand):
just before that point.
"""
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, _):
"""
......
......@@ -16,17 +16,19 @@
import re
from topydo.lib import Filter
from topydo.lib.Command import Command
from topydo.lib.Config import config
from topydo.lib import Filter
from topydo.lib.Sorter import Sorter
from topydo.lib.View import View
class ExpressionCommand(Command):
"""
A common class for commands operating on todos selected by expressions.
"""
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -35,6 +37,7 @@ class ExpressionCommand(Command):
self.sort_expression = config().sort_string()
self.show_all = False
self.limit = config().list_limit()
# 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
......@@ -50,7 +53,7 @@ class ExpressionCommand(Command):
filters += Filter.get_filter_list(args)
if not self.show_all:
filters.append(Filter.LimitFilter(config().list_limit()))
filters.append(Filter.LimitFilter(self.limit))
return filters
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -19,18 +19,19 @@ import re
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.Utils import date_string_to_date
class Filter(object):
def filter(self, p_todos):
"""
Filters a list of todos. Truncates the list after p_limit todo
items (or no maximum limit if omitted).
"""
return [t for t in p_todos if self.match(t)]
def match(self, _):
raise NotImplementedError
class NegationFilter(Filter):
def __init__(self, p_filter):
self._filter = p_filter
......@@ -38,6 +39,7 @@ class NegationFilter(Filter):
def match(self, p_todo):
return not self._filter.match(p_todo)
class AndFilter(Filter):
def __init__(self, p_filter1, p_filter2):
self._filter1 = p_filter1
......@@ -46,6 +48,7 @@ class AndFilter(Filter):
def match(self, p_todo):
return self._filter1.match(p_todo) and self._filter2.match(p_todo)
class OrFilter(Filter):
def __init__(self, p_filter1, p_filter2):
self._filter1 = p_filter1
......@@ -54,6 +57,7 @@ class OrFilter(Filter):
def match(self, p_todo):
return self._filter1.match(p_todo) or self._filter2.match(p_todo)
class GrepFilter(Filter):
""" Matches when the todo text contains a text. """
......@@ -63,7 +67,7 @@ class GrepFilter(Filter):
# convert to string in case we receive integers
self.expression = p_expression
if p_case_sensitive != None:
if p_case_sensitive is not None:
self.case_sensitive = p_case_sensitive
else:
# only be case sensitive when the expression contains at least one
......@@ -80,6 +84,7 @@ class GrepFilter(Filter):
return string.find(expr) != -1
class RelevanceFilter(Filter):
"""
Matches when the todo is relevant, i.e.:
......@@ -99,8 +104,10 @@ class RelevanceFilter(Filter):
return p_todo.is_active() and is_due
class DependencyFilter(Filter):
""" Matches when a todo has no unfinished child tasks. """
def __init__(self, p_todolist):
"""
Constructor.
......@@ -120,6 +127,7 @@ class DependencyFilter(Filter):
return not uncompleted
class InstanceFilter(Filter):
def __init__(self, p_todos):
"""
......@@ -143,6 +151,7 @@ class InstanceFilter(Filter):
except ValueError:
return False
class LimitFilter(Filter):
def __init__(self, p_limit):
super(LimitFilter, self).__init__()
......@@ -151,20 +160,54 @@ class LimitFilter(Filter):
def filter(self, p_todos):
return p_todos[:self.limit] if self.limit >= 0 else p_todos
ORDINAL_TAG_MATCH = r"(?P<key>[^:]*):(?P<operator><=?|=|>=?|!)?(?P<value>\S+)"
_OPERATOR_MATCH = r"(?P<operator><=?|=|>=?|!)?"
class OrdinalTagFilter(Filter):
def __init__(self, p_expression):
super(OrdinalTagFilter, self).__init__()
class OrdinalFilter(Filter):
""" Base class for ordinal filters. """
def __init__(self, p_expression, p_pattern):
super(OrdinalFilter, self).__init__()
self.expression = p_expression
match = re.match(ORDINAL_TAG_MATCH, self.expression)
match = re.match(p_pattern, self.expression)
if match:
self.key = match.group('key')
try:
self.key = match.group('key')
except IndexError:
pass
self.operator = match.group('operator') or '='
self.value = match.group('value')
def compare_operands(self, p_operand1, p_operand2):
"""
Returns True if conditional constructed from both operands and
self.operator is valid. Returns False otherwise.
"""
if self.operator == '<':
return p_operand1 < p_operand2
elif self.operator == '<=':
return p_operand1 <= p_operand2
elif self.operator == '=':
return p_operand1 == p_operand2
elif self.operator == '>=':
return p_operand1 >= p_operand2
elif self.operator == '>':
return p_operand1 > p_operand2
elif self.operator == '!':
return p_operand1 != p_operand2
return False
_VALUE_MATCH = r"(?P<value>\S+)"
_ORDINAL_TAG_MATCH = r"(?P<key>[^:]*):" + _OPERATOR_MATCH + _VALUE_MATCH
class OrdinalTagFilter(OrdinalFilter):
def __init__(self, p_expression):
super(OrdinalTagFilter, self).__init__(p_expression, _ORDINAL_TAG_MATCH)
def match(self, p_todo):
"""
Performs a match on a key:value tag in the todo.
......@@ -199,20 +242,80 @@ class OrdinalTagFilter(Filter):
grep = GrepFilter(self.expression)
return grep.match(p_todo)
if self.operator == '<':
return operand1 < operand2
elif self.operator == '<=':
return operand1 <= operand2
elif self.operator == '=':
return operand1 == operand2
elif self.operator == '>=':
return operand1 >= operand2
elif self.operator == '>':
return operand1 > operand2
elif self.operator == '!':
return operand1 != operand2
return self.compare_operands(operand1, operand2)
return False
class _DateAttributeFilter(OrdinalFilter):
def __init__(self, p_expression, p_match, p_getter):
super(_DateAttributeFilter, self).__init__(p_expression, p_match)
self.getter = p_getter
def match(self, p_todo):
operand1 = self.getter(p_todo)
operand2 = relative_date_to_date(self.value)
if not operand2:
operand2 = date_string_to_date(self.value)
if operand1 and operand2:
return self.compare_operands(operand1, operand2)
else:
return False
_CREATED_MATCH = r'creat(ion|ed?):' + _OPERATOR_MATCH + _VALUE_MATCH
class CreationFilter(_DateAttributeFilter):
def __init__(self, p_expression):
super(CreationFilter, self).__init__(
p_expression,
_CREATED_MATCH,
lambda t: t.creation_date() # pragma: no branch
)
_COMPLETED_MATCH = r'complet(ed?|ion):' + _OPERATOR_MATCH + _VALUE_MATCH
class CompletionFilter(_DateAttributeFilter):
def __init__(self, p_expression):
super(CompletionFilter, self).__init__(
p_expression,
_COMPLETED_MATCH,
lambda t: t.completion_date() # pragma: no branch
)
_PRIORITY_MATCH = r"\(" + _OPERATOR_MATCH + r"(?P<value>[A-Z]{1})\)"
class PriorityFilter(OrdinalFilter):
def __init__(self, p_expression):
super(PriorityFilter, self).__init__(p_expression, _PRIORITY_MATCH)
def match(self, p_todo):
"""
Performs a match on a priority in the todo.
It gets priority from p_todo and compares it with user-entered
expression based on the given operator (default ==). It does that however
in reversed order to obtain more intuitive result. Example: (>B) will
match todos with priority (A).
Items without priority are designated with corresponding operand set to
'ZZ', because python doesn't allow NoneType() and str() comparisons.
"""
operand1 = self.value
operand2 = p_todo.priority() or 'ZZ'
return self.compare_operands(operand1, operand2)
MATCHES = [
(_CREATED_MATCH, CreationFilter),
(_COMPLETED_MATCH, CompletionFilter),
(_ORDINAL_TAG_MATCH, OrdinalTagFilter),
(_PRIORITY_MATCH, PriorityFilter),
]
def get_filter_list(p_expression):
"""
......@@ -223,15 +326,22 @@ def get_filter_list(p_expression):
"""
result = []
for arg in p_expression:
if re.match(ORDINAL_TAG_MATCH, arg):
argfilter = OrdinalTagFilter(arg)
elif len(arg) > 1 and arg[0] == '-':
# when a word starts with -, exclude it
argfilter = GrepFilter(arg[1:])
argfilter = NegationFilter(argfilter)
else:
# when a word starts with -, it should be negated
is_negated = len(arg) > 1 and arg[0] == '-'
arg = arg[1:] if is_negated else arg
argfilter = None
for match, _filter in MATCHES:
if re.match(match, arg):
argfilter = _filter(arg)
break
if not argfilter:
argfilter = GrepFilter(arg)
if is_negated:
argfilter = NegationFilter(argfilter)
result.append(argfilter)
return result
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,11 +16,13 @@
""" Contains the class for a directed graph. """
class DirectedGraph(object):
"""
Represents a simple directed graph, used for tracking todo
dependencies. The nodes are very simple: just integers.
"""
def __init__(self):
self._edges = {}
self._edge_numbers = {}
......@@ -36,8 +38,7 @@ class DirectedGraph(object):
def add_edge(self, p_from, p_to, p_id=None):
"""
Adds an edge to the graph. The nodes will be added if they don't
exist.
Adds an edge to the graph. The nodes will be added if they don't exist.
The p_id is the id of the edge, if the client wishes to maintain this.
"""
......@@ -59,15 +60,13 @@ class DirectedGraph(object):
def incoming_neighbors(self, p_id, p_recursive=False):
"""
Returns a set of the direct neighbors that can reach the given
node.
Returns a set of the direct neighbors that can reach the given node.
"""
return self.reachable_nodes_reverse(p_id, p_recursive)
def outgoing_neighbors(self, p_id, p_recursive=False):
"""
Returns the set of the direct neighbors that the given node can
reach.
Returns the set of the direct neighbors that the given node can reach.
"""
return self.reachable_nodes(p_id, p_recursive)
......@@ -92,8 +91,8 @@ class DirectedGraph(object):
visited.add(current)
if p_reverse:
parents = [node for node, neighbors in self._edges.items() \
if current in neighbors]
parents = [node for node, neighbors in self._edges.items()
if current in neighbors]
stack = stack + parents
result = result.union(parents)
......@@ -130,8 +129,8 @@ class DirectedGraph(object):
"""
Returns True iff the given node has no incoming or outgoing edges.
"""
return len(self.incoming_neighbors(p_id)) == 0 \
and len(self.outgoing_neighbors(p_id)) == 0
return(len(self.incoming_neighbors(p_id)) == 0
and len(self.outgoing_neighbors(p_id)) == 0)
def has_edge(self, p_from, p_to):
""" Returns True when the graph has the given edge. """
......@@ -186,10 +185,10 @@ class DirectedGraph(object):
childpairs = \
[(c1, c2) for c1 in neighbors for c2 in neighbors if c1 != c2]
for pair in childpairs:
if self.has_path(pair[0], pair[1]) \
and not self.has_path(pair[0], from_node):
removals.add((from_node, pair[1]))
for child1, child2 in childpairs:
if self.has_path(child1, child2) \
and not self.has_path(child1, from_node):
removals.add((from_node, child2))
for edge in removals:
self.remove_edge(edge[0], edge[1])
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -23,10 +23,11 @@ from hashlib import sha1
_TABLE_SIZES = {
# we choose a large table size to reduce the chance of collisions.
3: 46649, # largest prime under zzz_36
4: 1679609 # largest prime under zzzz_36
3: 46649, # largest prime under zzz_36
4: 1679609 # largest prime under zzzz_36
}
def _to_base36(p_value):
"""
Converts integer to base36 string.
......@@ -43,7 +44,8 @@ def _to_base36(p_value):
return base36 or alphabet[0]
def hash_list_values(p_list, p_key=lambda i: i):
def hash_list_values(p_list, p_key=lambda i: i): # pragma: no branch
"""
Calculates a unique value for each item in the list, these can be used as
identifiers.
......@@ -79,4 +81,3 @@ def hash_list_values(p_list, p_key=lambda i: i):
result.append((item, _to_base36(hash_value)))
return result
......@@ -19,12 +19,13 @@ Provides a printer that transforms a list of Todo items to an iCalendar
file according to RFC 2445.
"""
from datetime import datetime, time
import random
import string
from datetime import datetime, time
from topydo.lib.PrettyPrinter import Printer
def _convert_priority(p_priority):
"""
Converts todo.txt priority to an iCalendar priority (RFC 2445).
......@@ -55,6 +56,7 @@ def _convert_priority(p_priority):
return result
class IcalPrinter(Printer):
"""
A printer that converts a list of Todo items to a string in iCalendar
......@@ -62,6 +64,7 @@ class IcalPrinter(Printer):
https://www.rfc-editor.org/rfc/rfc2445.txt
"""
def __init__(self, p_todolist):
super(IcalPrinter, self).__init__()
self.todolist = p_todolist
......@@ -69,10 +72,7 @@ class IcalPrinter(Printer):
try:
import icalendar
self.icalendar = icalendar
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
except ImportError: # pragma: no cover
self.icalendar = None
def print_list(self, p_todos):
......@@ -98,6 +98,7 @@ class IcalPrinter(Printer):
Gets a unique ID from a todo item, stored by the ical tag. If the
tag is not present, a random value is assigned to it and returned.
"""
def generate_uid(p_length=4):
"""
Generates a random string of the given length, used as
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -29,6 +29,7 @@ from topydo.lib.Config import config
IMPORTANCE_VALUE = {'A': 3, 'B': 2, 'C': 1}
def is_due_next_monday(p_todo):
""" Returns True when the given task is due next Monday. """
today = date.today()
......@@ -37,6 +38,7 @@ def is_due_next_monday(p_todo):
return due and due.weekday() == 0 and today.weekday() >= 4 and \
p_todo.days_till_due()
def importance(p_todo, p_ignore_weekend=config().ignore_weekends()):
"""
Calculates the importance of the given task.
......@@ -74,6 +76,7 @@ def importance(p_todo, p_ignore_weekend=config().ignore_weekends()):
return result if not p_todo.is_completed() else 0
def average_importance(p_todo, p_ignore_weekend=config().ignore_weekends()):
own_importance = importance(p_todo, p_ignore_weekend)
......
......@@ -23,6 +23,7 @@ import json
from topydo.lib.PrettyPrinter import Printer
def _convert_todo(p_todo):
""" Converts a Todo instance to a dictionary. """
creation_date = p_todo.creation_date()
......@@ -44,10 +45,12 @@ def _convert_todo(p_todo):
return result
class JsonPrinter(Printer):
"""
A printer that converts a list of Todo items to a string in JSON format.
"""
def __init__(self):
super(JsonPrinter, self).__init__()
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Utilities for formatting output with "list_format" option."""
import arrow
import re
from topydo.lib.Config import config
from topydo.lib.Utils import get_terminal_size
MAIN_PATTERN = (r'^({{(?P<before>.+?)}})?'
r'(?P<placeholder>{ph}|\[{ph}\])'
r'({{(?P<after>.+?)}})?'
r'(?P<whitespace> *)')
def _columns():
""" Returns the number of columns of the terminal. """
return get_terminal_size().columns
def _filler(p_str, p_len):
"""
Returns p_str preceded by additional spaces if p_str is shorter than p_len.
"""
to_fill = p_len - len(p_str)
return to_fill*' ' + p_str
def humanize_date(p_datetime):
""" Returns a relative date string from a datetime object. """
now = arrow.now()
date = now.replace(day=p_datetime.day, month=p_datetime.month, year=p_datetime.year)
return date.humanize()
def humanize_dates(p_due=None, p_start=None, p_creation=None):
"""
Returns string with humanized versions of p_due, p_start and p_creation.
Examples:
- all dates: "16 days ago, due in a month, started 2 days ago"
- p_due and p_start: "due in a month, started 2 days ago"
- p_creation and p_due: "16 days ago, due in a month"
"""
dates_list = []
if p_creation:
dates_list.append(humanize_date(p_creation))
if p_due:
dates_list.append('due ' + humanize_date(p_due))
if p_start:
now = arrow.now().date()
dates_list.append('{} {}'.format(
'started' if p_start <= now else 'starts',
humanize_date(p_start)
))
return ', '.join(dates_list)
def _strip_placeholder_braces(p_matchobj):
"""
Returns string with conditional braces around placeholder stripped and
percent sign glued into placeholder character.
Returned string is composed from 'start', 'before', 'placeholder', 'after',
'whitespace', and 'end' match-groups of p_matchobj. Conditional braces are
stripped from 'before' and 'after' groups. 'whitespace', 'start', and 'end'
groups are preserved without any change.
Using this function as an 'repl' argument in re.sub it is possible to turn:
%{(}B{)}
into:
(%B)
"""
before = p_matchobj.group('before') or ''
placeholder = p_matchobj.group('placeholder')
after = p_matchobj.group('after') or ''
whitespace = p_matchobj.group('whitespace') or ''
return before + '%' + placeholder + after + whitespace
def _unescape_percent_sign(p_str):
""" Strips backslashes from escaped percent signs in p_str. """
unescaped_str = re.sub(r'\\%', '%', p_str)
return unescaped_str
def _remove_redundant_spaces(p_str):
""" Removes spaces surrunding <TAB> character (\t) from p_str. """
clean_str = re.sub(' *\t *', '\t', p_str)
return clean_str
def _truncate(p_str, p_repl):
"""
Returns p_str with truncated and ended with '...' version of p_repl.
Place of the truncation is calculated depending on p_max_width.
"""
# 4 is for '...' and an extra space at the end
text_lim = _columns() - len(p_str) - 4
truncated_str = re.sub(re.escape(p_repl), p_repl[:text_lim] + '...', p_str)
return truncated_str
def _right_align(p_str):
"""
Returns p_str with content after <TAB> character aligned right.
Right alignment is done using proper number of spaces calculated from
'line_width' attribute.
"""
to_fill = _columns() - len(p_str)
if to_fill > 0:
p_str = re.sub('\t', ' '*to_fill, p_str)
else:
p_str = re.sub('\t', ' ', p_str)
return p_str
class ListFormatParser(object):
""" Parser of format string. """
def __init__(self, p_todolist, p_format=None):
self.format_string = re.sub(r'\\t', '\t', p_format or config().list_format())
self.todolist = p_todolist
self.one_line = False
self.placeholders = {
# absolute creation date
'c': lambda t: t.creation_date().isoformat() if t.creation_date() else '',
# relative creation date
'C': lambda t: humanize_date(t.creation_date()) if t.creation_date() else '',
# absolute due date
'd': lambda t: t.due_date().isoformat() if t.due_date() else '',
# relative due date
'D': lambda t: humanize_date(t.due_date()) if t.due_date() else '',
# relative dates: due, start
'h': lambda t: humanize_dates(t.due_date(), t.start_date()),
# relative dates in form: creation, due, start
'H': lambda t: humanize_dates(t.due_date(), t.start_date(), t.creation_date()),
# todo ID
'i': lambda t: str(self.todolist.number(t)),
# todo ID pre-filled with 1 or 2 spaces if its length is <3
'I': lambda t: _filler(str(self.todolist.number(t)), 3),
# list of tags (spaces) without hidden ones and due: and t:
'k': lambda t: ' '.join([u'{}:{}'.format(tag, value)
for tag, value in sorted(t.tags()) if
tag not in config().hidden_tags() +
[config().tag_start(), config().tag_due()]]),
# list of all tags (spaces)
'K': lambda t: ' '.join([u'{}:{}'.format(tag, value)
for tag, value in sorted(t.tags())]),
# priority
'p': lambda t: t.priority() if t.priority() else '',
# priority (or placeholder space)
'P': lambda t: t.priority() if t.priority() else ' ',
# text
's': lambda t: t.text(),
# text (truncated if necessary)
'S': lambda t: t.text(),
# absolute start date
't': lambda t: t.start_date().isoformat() if t.start_date() else '',
# relative start date
'T': lambda t: humanize_date(t.start_date()) if t.start_date() else '',
# absolute completion date
'x': lambda t: 'x ' + t.completion_date().isoformat() if t.is_completed() else '',
# relative completion date
'X': lambda t: 'x ' + humanize_date(t.completion_date()) if t.is_completed() else '',
}
self.format_list = self._preprocess_format()
def _preprocess_format(self):
"""
Preprocess the format_string attribute.
Splits the format string on each placeholder and returns a list of
tuples containing substring, placeholder name, and function
retrieving content for placeholder (getter).
Relevant placeholder functions (getters) are taken from
'placeholders' attribute which is a dict. If no matching placeholder
is found in 'placeholders' getter is set to None. Getter and
placeholder are also always set to None in first element of the
returned list, because it never contain a real placeholder (read
re.split documentation for further information).
"""
format_split = re.split(r'(?<!\\)%', self.format_string)
preprocessed_format = []
for idx, substr in enumerate(format_split):
if idx == 0:
getter = None
placeholder = None
else:
pattern = MAIN_PATTERN.format(ph=r'\S')
try:
placeholder = re.match(pattern, substr).group('placeholder').strip('[]')
except AttributeError:
placeholder = None
if placeholder == 'S':
self.one_line = True
try:
getter = self.placeholders[placeholder]
except KeyError:
getter = None
substr = re.sub(pattern, '', substr)
format_elem = (substr, placeholder, getter)
preprocessed_format.append(format_elem)
return preprocessed_format
def parse(self, p_todo):
"""
Returns fully parsed string from 'format_string' attribute with all
placeholders properly substituted by content obtained from p_todo.
It uses preprocessed form of 'format_string' (result of
ListFormatParser._preprocess_format) stored in 'format_list'
attribute.
"""
parsed_list = []
repl_trunc = None
for substr, placeholder, getter in self.format_list:
repl = getter(p_todo) if getter else ''
pattern = MAIN_PATTERN.format(ph=placeholder)
if placeholder == 'S':
repl_trunc = repl
if repl == '':
substr = re.sub(pattern, '', substr)
else:
substr = re.sub(pattern, _strip_placeholder_braces, substr)
substr = re.sub(r'(?<!\\)%({ph}|\[{ph}\])'.format(ph=placeholder), repl, substr)
parsed_list.append(substr)
parsed_str = _unescape_percent_sign(''.join(parsed_list))
parsed_str = _remove_redundant_spaces(parsed_str)
if self.one_line and len(parsed_str) >= _columns():
parsed_str = _truncate(parsed_str, repl_trunc)
if re.search('.*\t', parsed_str):
parsed_str = _right_align(parsed_str)
return parsed_str.rstrip()
......@@ -14,17 +14,16 @@
# 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 six import u
from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.TodoListBase import InvalidTodoException
class MultiCommand(ExpressionCommand):
"""
A common class for operations that can work with multiple todo IDs.
"""
def __init__(self, p_args, p_todolist,
def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
......@@ -42,7 +41,7 @@ class MultiCommand(ExpressionCommand):
def process_flag(self, p_option, p_value):
""" Default implementation of processing specific flags. """
pass
raise NotImplementedError
def _process_flags(self):
opts, long_opts = self.get_flags()
......@@ -62,7 +61,7 @@ class MultiCommand(ExpressionCommand):
self.todos = self._view().todos
def get_todos(self):
""" Gets todo objects from supplied todo IDs """
""" Gets todo objects from supplied todo IDs. """
if self.is_expression:
self.get_todos_from_expr()
else:
......@@ -79,17 +78,17 @@ class MultiCommand(ExpressionCommand):
def _catch_todo_errors(self):
"""
Returns None or list of error messages depending on number of valid todo
objects and number of invalid todo IDs.
Returns None or list of error messages depending on number of valid
todo objects and number of invalid todo IDs.
In case of multiple invalid todo IDs we generate separate error message for each
one of them with information about supplied ID.
In case of multiple invalid todo IDs we generate separate error message
for each one of them with information about supplied ID.
"""
errors = []
if len(self.invalid_numbers) > 1 or len(self.invalid_numbers) > 0 and len(self.todos) > 0:
for number in self.invalid_numbers:
errors.append(u("Invalid todo number given: {}.").format(number))
errors.append(u"Invalid todo number given: {}.".format(number))
elif len(self.invalid_numbers) == 1 and len(self.todos) == 0:
errors.append("Invalid todo number given.")
elif len(self.todos) == 0 and len(self.invalid_numbers) == 0:
......@@ -105,14 +104,14 @@ class MultiCommand(ExpressionCommand):
Operations specific for particular command dealing with multiple todo
IDs.
"""
pass
raise NotImplementedError
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
raise NotImplementedError
def execute(self):
if not super(MultiCommand, self).execute():
......
......@@ -14,10 +14,9 @@
# 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
)
from topydo.lib.prettyprinters.Colors import PrettyPrinterColorFilter
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
class Printer(object):
"""
......@@ -25,6 +24,7 @@ class Printer(object):
Subclasses must at least implement the print_todo method.
"""
def print_todo(self, p_todo):
raise NotImplementedError
......@@ -35,6 +35,7 @@ class Printer(object):
"""
return "\n".join([self.print_todo(todo) for todo in p_todos])
class PrettyPrinter(Printer):
"""
Prints todo items on a single line, decorated by the filters passed by
......@@ -44,6 +45,7 @@ class PrettyPrinter(Printer):
add colors, indentation, etc. These filters are found in the
PrettyPrinterFilter module.
"""
def __init__(self):
"""
Constructor.
......@@ -68,9 +70,9 @@ class PrettyPrinter(Printer):
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()
......
......@@ -14,19 +14,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Provides filters used for pretty printing. """
""" Provides a base class for pretty printer filters. """
import re
from six import u
from topydo.lib.Config import config
from topydo.lib.Colors import Colors, NEUTRAL_COLOR
class PrettyPrinterFilter(object):
"""
Base class for a pretty printer filter.
Subclasses must reimplement the filter method.
Subclasses must re-implement the filter method.
"""
def filter(self, p_todo_str, _):
......@@ -35,85 +30,3 @@ class PrettyPrinterFilter(object):
"""
raise NotImplementedError
class PrettyPrinterColorFilter(PrettyPrinterFilter):
"""
Adds colors to the todo string by inserting ANSI codes.
Should be passed as a filter in the filter list of pretty_print()
"""
def filter(self, p_todo_str, p_todo):
""" Applies the colors. """
colorscheme = Colors()
priority_colors = colorscheme.get_priority_colors()
project_color = colorscheme.get_project_color()
context_color = colorscheme.get_context_color()
metadata_color = colorscheme.get_metadata_color()
link_color = colorscheme.get_link_color()
if config().colors():
color = NEUTRAL_COLOR
try:
color = priority_colors[p_todo.priority()]
except KeyError:
pass
# color by priority
p_todo_str = 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',
metadata_color + r'\g<0>' + color,
p_todo_str)
# add link_color to any valid URL specified outside of the tag.
p_todo_str = re.sub(r'(^|\s)(\w+:){1}(//\S+)',
' ' + link_color + r'\2\3' + color,
p_todo_str)
p_todo_str += NEUTRAL_COLOR
return p_todo_str
class PrettyPrinterIndentFilter(PrettyPrinterFilter):
""" Adds indentation to the todo item. """
def __init__(self, p_indent=0):
super(PrettyPrinterIndentFilter, self).__init__()
self.indent = p_indent
def filter(self, p_todo_str, _):
""" Applies the indentation. """
return ' ' * self.indent + p_todo_str
class PrettyPrinterNumbers(PrettyPrinterFilter):
""" Prepends the todo's number, retrieved from the todolist. """
def __init__(self, p_todolist):
super(PrettyPrinterNumbers, self).__init__()
self.todolist = p_todolist
def filter(self, p_todo_str, p_todo):
""" Prepends the number to the todo string. """
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. """
def __init__(self, p_hidden_tags):
super(PrettyPrinterHideTagFilter, self).__init__()
self.hidden_tags = p_hidden_tags
def filter(self, p_todo_str, _):
for hidden_tag in self.hidden_tags:
# inspired from remove_tag in TodoBase
p_todo_str = re.sub(r'\s?\b' + hidden_tag + r':\S+\b', '',
p_todo_str)
return p_todo_str
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -22,9 +22,11 @@ from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.Todo import Todo
class NoRecurrenceException(Exception):
pass
def advance_recurring_todo(p_todo, p_offset=None, p_strict=False):
"""
Given a Todo item, return a new instance of a Todo item with the dates
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,9 +16,10 @@
""" This module deals with relative dates (2d, 5y, Monday, today, etc.) """
from datetime import date, timedelta
import calendar
import re
from datetime import date, timedelta
def _add_months(p_sourcedate, p_months):
"""
......@@ -35,6 +36,7 @@ def _add_months(p_sourcedate, p_months):
return date(year, month, day)
def _convert_pattern(p_length, p_periodunit, p_offset=None):
"""
Converts a pattern in the form [0-9][dwmy] and returns a date from the
......@@ -56,12 +58,12 @@ def _convert_pattern(p_length, p_periodunit, p_offset=None):
return result
def _convert_weekday_pattern(p_weekday):
"""
Converts a weekday name to an absolute date.
When today's day of the week is entered, it will return today and not next
week's.
When today's day of the week is entered, it will return next week's date.
"""
day_value = {
'mo': 0,
......@@ -78,9 +80,10 @@ def _convert_weekday_pattern(p_weekday):
day = date.today().weekday()
shift = (target_day - day) % 7
shift = 7 - (day - target_day) % 7
return date.today() + timedelta(shift)
def relative_date_to_date(p_date, p_offset=None):
"""
Transforms a relative date into a date object.
......@@ -88,10 +91,9 @@ def relative_date_to_date(p_date, p_offset=None):
The following formats are understood:
* [0-9][dwmy]
* 'today' or 'tomorrow'
* 'yesterday', 'today' or 'tomorrow'
* days of the week (in full or abbreviated)
"""
result = None
p_date = p_date.lower()
p_offset = p_offset or date.today()
......@@ -124,4 +126,7 @@ def relative_date_to_date(p_date, p_offset=None):
elif re.match('tom(orrow)?$', p_date):
result = _convert_pattern('1', 'd')
elif re.match('yes(terday)?$', p_date):
result = _convert_pattern('-1', 'd')
return result
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -16,15 +16,17 @@
""" This module provides functionality to sort lists with todo items. """
from datetime import date
import re
from datetime import date
from topydo.lib.Importance import average_importance, importance
from topydo.lib.Importance import importance, average_importance
def is_priority_field(p_field):
""" Returns True when the field name denotes the priority. """
return p_field.startswith('prio')
def get_field_function(p_field):
"""
Given a property (string) of a todo, return a function that attempts to
......@@ -36,29 +38,34 @@ def get_field_function(p_field):
if is_priority_field(p_field):
# assign dummy priority when a todo has no priority
result = lambda a: a.priority() or 'ZZ'
elif p_field == 'context' or p_field == 'contexts':
result = lambda a: sorted([c.lower() for c in a.contexts()])
elif p_field == 'creationdate' or p_field == 'creation':
# when a task has no creation date, push it to the end by assigning it
# the maximum possible date.
result = (lambda a: a.creation_date() if a.creation_date() \
else date.max)
result = (lambda a: a.creation_date() if a.creation_date()
else date.max)
elif p_field == 'done' or p_field == 'completed' or p_field == 'completion':
result = (lambda a: a.completion_date() if a.completion_date() \
else date.max)
result = (lambda a: a.completion_date() if a.completion_date()
else date.max)
elif p_field == 'importance':
result = importance
elif p_field == 'importance-avg' or p_field == 'importance-average':
result = average_importance
elif p_field == 'project' or p_field == 'projects':
result = lambda a: sorted([c.lower() for c in a.projects()])
elif p_field == 'text':
result = lambda a: a.text()
else:
# try to find the corresponding tag
# when a tag is not present, push it to the end of the list by giving
# it an artificially higher value
result = (lambda a: "0" + a.tag_value(p_field) if a.has_tag(p_field) \
else "1")
result = (lambda a: "0" + a.tag_value(p_field) if a.has_tag(p_field)
else "1")
return result
class Sorter(object):
"""
This class sorts a todo list.
......@@ -83,6 +90,7 @@ class Sorter(object):
specific search is done first. This relies on the fact that sorting is
stable.
"""
def __init__(self, p_sortstring="desc:priority"):
self.sortstring = p_sortstring
self.functions = []
......@@ -97,7 +105,6 @@ class Sorter(object):
sort operation is done first, relying on the stability of the sorted()
function.
"""
sorted_todos = p_todos
for function, order in reversed(self.functions):
sorted_todos = sorted(sorted_todos, key=function,
......@@ -113,8 +120,8 @@ class Sorter(object):
fields = self.sortstring.lower().split(',')
for field in fields:
parsed_field = re.match( \
r'(?P<order>(asc|desc)(ending)?:)?(?P<field>\S+)', \
parsed_field = re.match(
r'(?P<order>(asc|desc)(ending)?:)?(?P<field>\S+)',
field)
if not parsed_field:
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -24,6 +24,7 @@ from topydo.lib.Config import config
from topydo.lib.TodoBase import TodoBase
from topydo.lib.Utils import date_string_to_date
class Todo(TodoBase):
"""
This class adds common functionality with respect to dates to the Todo
......@@ -84,9 +85,10 @@ class Todo(TodoBase):
def length(self):
"""
Returns the length (in days) of the task, by considering the start date
and the due date. Returns 0 when one of these dates are missing.
and the due date. When there is no start date, its creation date is
used. Returns 0 when one of these dates is missing.
"""
start = self.start_date()
start = self.start_date() or self.creation_date()
due = self.due_date()
if start and due and start < due:
......@@ -94,4 +96,3 @@ class Todo(TodoBase):
return diff.days
else:
return 0
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -18,13 +18,13 @@
This module contains the class that represents a single todo item.
"""
from datetime import date
import re
from six import u
from datetime import date
from topydo.lib.TodoParser import parse_line
from topydo.lib.Utils import is_valid_priority
class TodoBase(object):
"""
This class represents a single todo item in a todo.txt file. It maintains
......@@ -56,7 +56,6 @@ class TodoBase(object):
Returns a list of all tag values associated with p_key. Returns
empty list if p_key does not exist.
"""
tags = self.fields['tags']
matches = [tag[1] for tag in tags if tag[0] == p_key]
return matches if len(matches) else []
......@@ -67,9 +66,8 @@ class TodoBase(object):
value is passed, it will only return true when there exists a tag with
the given key-value combination.
"""
result = [t for t in self.tag_values(p_key) \
if p_value == "" or t == p_value]
result = [t for t in self.tag_values(p_key)
if p_value == "" or t == p_value]
return len(result) > 0
def add_tag(self, p_key, p_value):
......@@ -90,7 +88,6 @@ class TodoBase(object):
When p_old_value is set, all tags having this value will be set to the
new value.
"""
if p_value == "":
self.remove_tag(p_key, p_old_value)
return
......@@ -99,8 +96,8 @@ class TodoBase(object):
if not p_force_add and value:
# remove old value from the tags
self.fields['tags'] = [t for t in self.fields['tags'] \
if not (t[0] == p_key and t[1] == value)]
self.fields['tags'] = [t for t in self.fields['tags']
if not (t[0] == p_key and t[1] == value)]
self.src = re.sub(
r'\b' + p_key + ':' + value + r'\b',
......@@ -122,8 +119,9 @@ class TodoBase(object):
# Build a new list that excludes the specified tag, match by value when
# p_value is given.
self.fields['tags'] = [t for t in self.fields['tags'] \
if not (t[0] == p_key and (p_value == "" or t[1] == p_value))]
self.fields['tags'] = [t for t in self.fields['tags']
if not (t[0] == p_key and (p_value == "" or
t[1] == p_value))]
# when value == "", match any value having key p_key
value = p_value if p_value != "" else r'\S+'
......@@ -143,13 +141,11 @@ class TodoBase(object):
Priority remains unchanged when an invalid priority is given, or when
the task was completed.
"""
if not self.is_completed() and \
(p_priority == None or is_valid_priority(p_priority)):
if not self.is_completed() and (p_priority is None or
is_valid_priority(p_priority)):
self.fields['priority'] = p_priority
priority_str = '' if p_priority == None else '(' + p_priority + ') '
priority_str = '' if p_priority is None else '(' + p_priority + ') '
self.src = re.sub(r'^(\([A-Z]\) )?', priority_str, self.src)
def priority(self):
......@@ -204,8 +200,9 @@ class TodoBase(object):
self.fields['completed'] = True
self.fields['completionDate'] = p_completion_date
self.src = re.sub(r'^(\([A-Z]\) )?', \
'x ' + p_completion_date.isoformat() + ' ', self.src)
self.src = re.sub(r'^(\([A-Z]\) )?',
'x ' + p_completion_date.isoformat() + ' ',
self.src)
def set_creation_date(self, p_date=date.today()):
"""
......@@ -213,16 +210,15 @@ class TodoBase(object):
"""
self.fields['creationDate'] = p_date
# not particulary pretty, but inspired by
# not particularly pretty, but inspired by
# http://bugs.python.org/issue1519638 non-existent matches trigger
# exceptions, hence the lambda
self.src = re.sub(
r'^(x \d{4}-\d{2}-\d{2} |\([A-Z]\) )?(\d{4}-\d{2}-\d{2} )?(.*)$',
lambda m: \
u("{}{} {}").format(m.group(1) or '', p_date.isoformat(), m.group(3)),
self.src)
lambda m:
u"{}{} {}".format(m.group(1) or '', p_date.isoformat(),
m.group(3)), self.src)
def creation_date(self):
""" Returns the creation date of a todo. """
return self.fields['creationDate']
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -20,6 +20,7 @@ This module deals with todo.txt files.
import codecs
class TodoFile(object):
"""
This class represents a todo.txt file, which can be read from or written
......@@ -48,7 +49,6 @@ class TodoFile(object):
p_todos can be a list of todo items, or a string that is just written
to the file.
"""
todofile = codecs.open(self.path, 'w', encoding="utf-8")
if p_todos is list:
......
......@@ -22,6 +22,7 @@ from topydo.lib.Config import config
from topydo.lib.Graph import DirectedGraph
from topydo.lib.TodoListBase import TodoListBase
class TodoList(TodoListBase):
"""
Provides operations for a todo list, such as adding items, removing them,
......@@ -37,7 +38,7 @@ class TodoList(TodoListBase):
The string will be parsed.
"""
# 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()
super(TodoList, self).__init__(p_todostrings)
......@@ -57,7 +58,6 @@ class TodoList(TodoListBase):
Makes sure that the dependency graph is consistent according to the
given todo.
"""
dep_id = p_todo.tag_value('id')
# maintain dependency graph
if dep_id:
......@@ -66,7 +66,7 @@ class TodoList(TodoListBase):
# connect all tasks we have in memory so far that refer to this
# task
for dep in \
[dep for dep in self._todos if dep.has_tag('p', dep_id)]:
[dep for dep in self._todos if dep.has_tag('p', dep_id)]:
self._depgraph.add_edge(hash(p_todo), hash(dep), dep_id)
......@@ -114,8 +114,7 @@ class TodoList(TodoListBase):
"""
def id_exists(p_id):
"""
Returns True if there exists a todo with the given parent
ID.
Returns True if there exists a todo with the given parent ID.
"""
for todo in self._todos:
if todo.has_tag('id', str(p_id)):
......@@ -148,7 +147,7 @@ class TodoList(TodoListBase):
self.append(p_to_todo, "@{}".format(context))
if p_from_todo != p_to_todo and not self._depgraph.has_edge(
hash(p_from_todo), hash(p_to_todo)):
hash(p_from_todo), hash(p_to_todo)):
dep_id = None
if p_from_todo.has_tag('id'):
......@@ -204,19 +203,39 @@ class TodoList(TodoListBase):
graph and removing unused dependency ids from the graph (in that
order).
"""
def clean_by_tag(tag_name):
""" Generic function to handle 'p' and 'id' tags. """
for todo in [todo for todo in self._todos
if todo.has_tag(tag_name)]:
def remove_tag(p_todo, p_tag, p_value):
"""
Removes a tag from a todo item.
"""
p_todo.remove_tag(p_tag, p_value)
self.dirty = True
def clean_parent_relations():
"""
Remove id: tags for todos without child todo items.
"""
value = todo.tag_value(tag_name)
for todo in [todo for todo in self._todos if todo.has_tag('id')]:
value = todo.tag_value('id')
if not self._depgraph.has_edge_id(value):
todo.remove_tag(tag_name, value)
self.dirty = True
remove_tag(todo, 'id', value)
def clean_orphan_relations():
"""
Remove p: tags for todos referring to a parent that is not in the
dependency graph anymore.
"""
for todo in [todo for todo in self._todos if todo.has_tag('p')]:
for value in todo.tag_values('p'):
parent = self.todo_by_dep_id(value)
if not self._depgraph.has_edge(hash(parent), hash(todo)):
remove_tag(todo, 'p', value)
self._depgraph.transitively_reduce()
clean_by_tag('p')
clean_by_tag('id')
clean_parent_relations()
clean_orphan_relations()
def _update_parent_cache(self):
"""
......@@ -225,6 +244,5 @@ class TodoList(TodoListBase):
This is used for calculating the average importance, that requires
access to a todo's parents.
"""
for todo in self._todos:
todo.attributes['parents'] = self.parents(todo)
......@@ -18,20 +18,21 @@
A list of todo items.
"""
from datetime import date
import re
from six import text_type
from datetime import date
from topydo.lib.Config import config
from topydo.lib import Filter
from topydo.lib.Config import config
from topydo.lib.HashListValues import hash_list_values
from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.Todo import Todo
from topydo.lib.View import View
class InvalidTodoException(Exception):
pass
class TodoListBase(object):
"""
Provides operations for a todo list, such as adding items, removing them,
......@@ -75,7 +76,7 @@ class TodoListBase(object):
try:
result = self._id_todo_map[p_identifier]
except KeyError:
pass # we'll try something else
pass # we'll try something else
return result
......@@ -83,10 +84,9 @@ class TodoListBase(object):
"""
Attempts to find the todo on the given line number.
When the identifier is a number but has leading zeroes, the result
When the identifier is a number but has leading zeros, the result
will be None.
"""
result = None
if config().identifiers() != 'text':
......@@ -126,12 +126,14 @@ class TodoListBase(object):
if not result:
# convert integer to text so we pass on a valid regex
result = todo_by_regexp(text_type(p_identifier))
result = todo_by_regexp(str(p_identifier))
return result
def add(self, p_src):
""" Given a todo string, parse it and put it to the end of the list. """
"""
Given a todo string, parse it and put it to the end of the list.
"""
todos = self.add_list([p_src])
return todos[0] if len(todos) else None
......@@ -169,6 +171,12 @@ class TodoListBase(object):
self._todos = []
self.dirty = True
def replace(self, p_todos):
""" Replaces whole todolist with todo objects supplied as p_todos. """
self.erase()
self.add_todos(p_todos)
self.dirty = True
def count(self):
""" Returns the number of todos on this list. """
return len(self._todos)
......@@ -246,8 +254,8 @@ class TodoListBase(object):
def _update_todo_ids(self):
# the idea is to have a hash that is independent of the position of the
# todo. Use the text (without tags) of the todo to keep the id as stable
# as possible (not influenced by priorities or due dates, etc.)
# todo. Use the text (without tags) of the todo to keep the id as
# stable as possible (not influenced by priorities or due dates, etc.)
self._todo_id_map = {}
self._id_todo_map = {}
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -37,13 +37,14 @@ _TAG_MATCH = re.compile('(?P<key>[^:]+):(?P<value>.+)')
_PROJECT_MATCH = re.compile(r'\+(\S*\w)')
_CONTEXT_MATCH = re.compile(r'@(\S*\w)')
def parse_line(p_string):
"""
Parses a single line as can be encountered in a todo.txt file.
First checks whether the standard elements are present, such as priority,
creation date, completeness check and the completion date.
Then the rest of the analyzed for any occurences of contexts, projects or
Then the rest of the analyzed for any occurrences of contexts, projects or
tags.
Returns an dictionary with the default values as shown below.
......@@ -102,4 +103,3 @@ def parse_line(p_string):
result['text'] = result['text'][:-1]
return result
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -18,9 +18,18 @@
Various utility functions.
"""
from datetime import date
import re
from collections import namedtuple
from datetime import date
# shutil.get_terminal_size was added to the standard library in Python 3.3
try:
from shutil import get_terminal_size as _get_terminal_size # pylint: disable=no-name-in-module
except ImportError:
from backports.shutil_get_terminal_size import get_terminal_size as _get_terminal_size # pylint: disable=import-error
def date_string_to_date(p_date):
"""
Given a date in YYYY-MM-DD, returns a Python date object. Returns None
......@@ -32,19 +41,39 @@ def date_string_to_date(p_date):
parsed_date = re.match(r'(\d{4})-(\d{2})-(\d{2})', p_date)
if parsed_date:
result = date(
int(parsed_date.group(1)), # year
int(parsed_date.group(2)), # month
int(parsed_date.group(3)) # day
int(parsed_date.group(1)), # year
int(parsed_date.group(2)), # month
int(parsed_date.group(3)) # day
)
else:
raise ValueError
return result
def is_valid_priority(p_priority):
return p_priority != None and re.match(r'^[A-Z]$', p_priority) != None
return p_priority is not None and re.match(r'^[A-Z]$', p_priority) is not None
def escape_ansi(p_string):
return escape_ansi.pattern.sub('', p_string)
escape_ansi.pattern = re.compile(r'\x1b[^m]*m')
def get_terminal_size():
"""
Try to determine terminal size at run time. If that is not possible,
returns the default size of 80x24.
"""
try:
sz = _get_terminal_size()
except ValueError:
"""
This can result from the 'underlying buffer being detached', which
occurs during running the unittest on Windows (but not on Linux?)
"""
terminal_size = namedtuple('Terminal_Size', 'columns lines')
sz = terminal_size((80, 24))
return sz
""" Version of Topydo. """
VERSION = '0.6'
VERSION = '0.9'
LICENSE = """Copyright (C) 2014 Bram Schoenmakers
LICENSE = """Copyright (C) 2014-2015 Bram Schoenmakers
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law."""
......@@ -16,12 +16,14 @@
""" A view is a list of todos, sorted and filtered. """
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.
"""
def __init__(self, p_sorter, p_filters, p_todolist):
self.todolist = p_todolist
self._sorter = p_sorter
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Provides a pretty printer filter that colorizes todo items. """
import re
from topydo.lib.Colors import NEUTRAL_COLOR, Colors
from topydo.lib.Config import config
from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter
class PrettyPrinterColorFilter(PrettyPrinterFilter):
"""
Adds colors to the todo string by inserting ANSI codes.
Should be passed as a filter in the filter list of pretty_print()
"""
def filter(self, p_todo_str, p_todo):
""" Applies the colors. """
if config().colors():
colorscheme = Colors()
priority_colors = colorscheme.get_priority_colors()
project_color = colorscheme.get_project_color()
context_color = colorscheme.get_context_color()
metadata_color = colorscheme.get_metadata_color()
link_color = colorscheme.get_link_color()
priority_color = NEUTRAL_COLOR
try:
priority_color = priority_colors[p_todo.priority()]
except KeyError:
pass
# color projects / contexts
p_todo_str = re.sub(
r'\B(\+|@)(\S*\w)',
lambda m: (
context_color if m.group(0)[0] == "@"
else project_color) + m.group(0) + priority_color,
p_todo_str)
# tags
p_todo_str = re.sub(r'\b\S+:[^/\s]\S*\b',
metadata_color + r'\g<0>' + priority_color,
p_todo_str)
# add link_color to any valid URL specified outside of the tag.
p_todo_str = re.sub(r'(^|\s)(\w+:){1}(//\S+)',
r'\1' + link_color + r'\2\3' + priority_color,
p_todo_str)
p_todo_str += NEUTRAL_COLOR
# color by priority
p_todo_str = priority_color + p_todo_str
return p_todo_str
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Provides a pretty printer filter that generates a todo string based on a format
string.
"""
from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter
from topydo.lib.ListFormat import ListFormatParser
class PrettyPrinterFormatFilter(PrettyPrinterFilter):
def __init__(self, p_todolist, p_format=None):
super(PrettyPrinterFormatFilter, self).__init__()
self.parser = ListFormatParser(p_todolist, p_format)
def filter(self, p_todo_str, p_todo):
p_todo_str = self.parser.parse(p_todo)
return p_todo_str
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,28 +14,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
""" Provides a pretty printer filter that inserts todo numbers. """
from topydo.lib.Config import config
from test.TopydoTest import TopydoTest
from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter
class ConfigTest(TopydoTest):
def test_config1(self):
self.assertEqual(config("test/data/config1").default_command(), 'do')
def test_config2(self):
self.assertNotEqual(config("").default_command(), 'do')
class PrettyPrinterNumbers(PrettyPrinterFilter):
""" Prepends the todo's number, retrieved from the todolist. """
def test_config3(self):
self.assertTrue(config("test/data/config2").ignore_weekends())
def __init__(self, p_todolist):
super(PrettyPrinterNumbers, self).__init__()
self.todolist = p_todolist
def test_config4(self):
""" Test that value in file is overridden by parameter. """
overrides = {
('topydo', 'default_command'): 'edit'
}
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)
self.assertEqual(config("test/data/config1", p_overrides=overrides).default_command(), 'edit')
if __name__ == '__main__':
unittest.main()
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