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

Merge branch 'master' into column-ui

parents 393d4d03 0c7a3aa3
[run] [run]
source = topydo source = topydo
branch = True
[report] [report]
exclude_lines = exclude_lines =
...@@ -11,5 +12,5 @@ exclude_lines = ...@@ -11,5 +12,5 @@ exclude_lines =
if __name__ == .__main__.: if __name__ == .__main__.:
omit = omit =
topydo/lib/ExitCommand.py topydo/commands/ExitCommand.py
topydo/lib/Version.py topydo/lib/Version.py
*.pyc *.py[cod]
*.sw? *.sw?
install install
.coverage .coverage
...@@ -11,6 +11,7 @@ build/ ...@@ -11,6 +11,7 @@ build/
develop-eggs/ develop-eggs/
dist/ dist/
eggs/ eggs/
htmlcov/
lib/ lib/
lib64/ lib64/
parts/ parts/
...@@ -19,6 +20,9 @@ var/ ...@@ -19,6 +20,9 @@ var/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
.eggs/
# Sublime Text # Sublime Text
*.sublime-* *.sublime-*
!/topydo/lib/
sudo: false # run on new infrastructure sudo: false # run on new infrastructure
language: python language: python
python: python:
- "2.7"
- "3.2"
- "3.3" - "3.3"
- "3.4" - "3.4"
- "3.5"
- "pypy3"
install: install:
- "python -m pip install pip --upgrade"
- "pip install ." - "pip install ."
- "pip install urwid" - "pip install urwid"
- "pip install icalendar" - "pip install .[ical]"
- "pip install .[prompt-toolkit]"
- "pip install .[test]"
- "pip install pylint" - "pip install pylint"
script: "./run-tests.sh" - "pip install codecov"
script:
- "green -vvr"
- "python -m pylint --errors-only topydo test"
# Cache Dependencies # Cache Dependencies
after_script:
- codecov
cache: cache:
directories: directories:
- $HOME/travis/.cache/pip - $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 0.6
--- ---
...@@ -16,7 +113,9 @@ ...@@ -16,7 +113,9 @@
--- ---
* Remove 'ical' subcommand in favor of 'topydo ls -f ical' * 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] [colorscheme]
project_color = project_color =
......
...@@ -5,11 +5,9 @@ smoothly into topydo. ...@@ -5,11 +5,9 @@ smoothly into topydo.
### General ### General
* This Github page defaults to the **stable** branch which is for **bug fixes * Use descriptive commit messages. The post
only**. If you would like to add a new feature, make sure to make a Pull [How to write a commit message](http://chris.beams.io/posts/git-commit/) by
Request on the `master` branch. Chris Beams has some good guidelines.
* Use descriptive commit messages.
### Coding style ### Coding style
...@@ -17,37 +15,63 @@ smoothly into topydo. ...@@ -17,37 +15,63 @@ smoothly into topydo.
possible. I won't be very picky about long lines, but please try to avoid possible. I won't be very picky about long lines, but please try to avoid
them. them.
* I strongly prefer simple and short functions, doing only one thing. I'll * 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. otherwise on a screen.
### Testing ### 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 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 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. 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): * 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. 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. 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 * Check the test coverage of your contributed code, in particular if you touched
touched code in the topydo.lib or topydo.command packages: code in the topydo.lib or topydo.command packages:
pip install coverage coverage report -m
coverage run setup.py test
coverage report
Or alternatively, for a more friendly output, run: Or alternatively, for a more friendly output, run:
coverage html 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). 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 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 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 inspired by the [todo.txt CLI][2] by Gina Trapani. This tool is actually a
...@@ -28,10 +28,20 @@ Simply install with: ...@@ -28,10 +28,20 @@ Simply install with:
### Optional dependencies ### Optional dependencies
* icalendar : To print your todo.txt file as an iCalendar file * [icalendar][7] : To print your todo.txt file as an iCalendar file
(not supported for Python 3.2). (not supported for Python 3.2).
* prompt-toolkit : For topydo's _prompt_ mode, which offers a shell-like * [prompt-toolkit][6] : For topydo's _prompt_ mode, which offers a shell-like
interface with auto-completion. interface with auto-completion.
* [arrow][8] : Used to turn dates into a human readable version.
* [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 Demo
---- ----
...@@ -43,4 +53,10 @@ Demo ...@@ -43,4 +53,10 @@ Demo
[2]: https://github.com/ginatrapani/todo.txt-cli [2]: https://github.com/ginatrapani/todo.txt-cli
[3]: https://github.com/bram85/todo.txt-tools [3]: https://github.com/bram85/todo.txt-tools
[4]: https://github.com/bram85/topydo/wiki [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): ...@@ -20,12 +20,6 @@ def find_version(*file_paths):
return version_match.group(1) return version_match.group(1)
raise RuntimeError("Unable to find version string.") raise RuntimeError("Unable to find version string.")
conditional_dependencies = {
"colorama>=0.2.5": "win32" in sys.platform,
}
setup( setup(
name = "topydo", name = "topydo",
packages = find_packages(exclude=["test"]), packages = find_packages(exclude=["test"]),
...@@ -35,13 +29,16 @@ setup( ...@@ -35,13 +29,16 @@ setup(
author_email = "me@bramschoenmakers.nl", author_email = "me@bramschoenmakers.nl",
url = "https://github.com/bram85/topydo", url = "https://github.com/bram85/topydo",
install_requires = [ install_requires = [
'six >= 1.9.0', 'arrow >= 0.7.0',
] + [p for p, cond in conditional_dependencies.items() if cond], ],
extras_require = { extras_require = {
':sys_platform=="win32"': ['colorama>=0.2.5'],
':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'],
'ical': ['icalendar'], 'ical': ['icalendar'],
'prompt-toolkit': ['prompt-toolkit >= 0.47'],
'urwid': ['urwid >= 1.3.0'], '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= { entry_points= {
'console_scripts': ['topydo = topydo.cli.UILoader:main'], 'console_scripts': ['topydo = topydo.cli.UILoader:main'],
...@@ -52,10 +49,12 @@ setup( ...@@ -52,10 +49,12 @@ setup(
"Intended Audience :: End Users/Desktop", "Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Natural Language :: English", "Natural Language :: English",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Utilities", "Topic :: Utilities",
], ],
long_description = """\ long_description = """\
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,10 +14,9 @@ ...@@ -14,10 +14,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest from test.topydo_testcase import TopydoTest
from topydo.lib.Utils import escape_ansi from topydo.lib.Utils import escape_ansi
from test.TopydoTest import TopydoTest
class CommandTest(TopydoTest): class CommandTest(TopydoTest):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
...@@ -32,6 +31,3 @@ class CommandTest(TopydoTest): ...@@ -32,6 +31,3 @@ class CommandTest(TopydoTest):
def error(self, p_error): def error(self, p_error):
if p_error: if p_error:
self.errors += escape_ansi(p_error + "\n") 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 (D) Bar @Context1 +Project2 p:1
(C) Baz @Context1 +Project1 key:value id:1 (C) Baz @Context1 +Project1 key:value id:1
(C) Drink beer @ home (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. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -19,6 +19,7 @@ from topydo.lib.Todo import Todo ...@@ -19,6 +19,7 @@ from topydo.lib.Todo import Todo
from topydo.lib.TodoFile import TodoFile from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
def load_file(p_filename): def load_file(p_filename):
""" """
Loads a todo file from the given filename and returns a list of todos. Loads a todo file from the given filename and returns a list of todos.
...@@ -26,6 +27,7 @@ def load_file(p_filename): ...@@ -26,6 +27,7 @@ def load_file(p_filename):
todolist = load_file_to_raw_list(p_filename) todolist = load_file_to_raw_list(p_filename)
return [Todo(src) for src in todolist] return [Todo(src) for src in todolist]
def load_file_to_raw_list(p_filename): def load_file_to_raw_list(p_filename):
""" """
Loads a todo file from the given filename and returns a list of todo 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): ...@@ -34,6 +36,7 @@ def load_file_to_raw_list(p_filename):
todofile = TodoFile(p_filename) todofile = TodoFile(p_filename)
return todofile.read() return todofile.read()
def load_file_to_todolist(p_filename): def load_file_to_todolist(p_filename):
""" """
Loads a todo file to a TodoList instance. Loads a todo file to a TodoList instance.
...@@ -41,10 +44,12 @@ def load_file_to_todolist(p_filename): ...@@ -41,10 +44,12 @@ def load_file_to_todolist(p_filename):
todolist = load_file_to_raw_list(p_filename) todolist = load_file_to_raw_list(p_filename)
return TodoList(todolist) return TodoList(todolist)
def todolist_to_string(p_list): def todolist_to_string(p_list):
""" Converts a todo list to a single string. """ """ Converts a todo list to a single string. """
return '\n'.join([t.source() for t in p_list]) return '\n'.join([t.source() for t in p_list])
def print_view(p_view): def print_view(p_view):
printer = PrettyPrinter() printer = PrettyPrinter()
return printer.print_list(p_view.todos) return printer.print_list(p_view.todos)
...@@ -14,8 +14,14 @@ ...@@ -14,8 +14,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date
import unittest 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' # We're searching for 'mock'
# pylint: disable=no-name-in-module # pylint: disable=no-name-in-module
...@@ -24,14 +30,6 @@ try: ...@@ -24,14 +30,6 @@ try:
except ImportError: except ImportError:
import mock 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): class AddCommandTest(CommandTest):
def setUp(self): def setUp(self):
...@@ -41,247 +39,318 @@ class AddCommandTest(CommandTest): ...@@ -41,247 +39,318 @@ class AddCommandTest(CommandTest):
def test_add_task(self): def test_add_task(self):
args = ["New todo"] 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() 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, "") self.assertEqual(self.errors, "")
def test_add_multiple_args(self): def test_add_multiple_args(self):
args = ["New", "todo"] 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() 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, "") self.assertEqual(self.errors, "")
def test_add_priority1(self): 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() command.execute()
self.assertEqual(self.todolist.todo(1).priority(), 'C') 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, "") self.assertEqual(self.errors, "")
def test_add_priority2(self): 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() command.execute()
self.assertEqual(self.todolist.todo(1).priority(), None) 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, "") self.assertEqual(self.errors, "")
def test_add_priority3(self): 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() command.execute()
self.assertEqual(self.todolist.todo(1).priority(), None) 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, "") self.assertEqual(self.errors, "")
def test_add_priority4(self): 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() command.execute()
self.assertEqual(self.todolist.todo(1).priority(), 'C') 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, "") self.assertEqual(self.errors, "")
def test_add_dep1(self): def test_add_dep01(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute() 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() command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo id:1") self.assertEqual(self.todolist.todo(1).source(),
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar p:1") self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(2).source(),
self.today + " Bar p:1")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep2(self): def test_add_dep02(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute() command.execute()
command = AddCommand.AddCommand(["Bar partof:1"], self.todolist) command = AddCommand.AddCommand(["Bar partof:1"], self.todolist)
command.execute() command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo id:1") self.assertEqual(self.todolist.todo(1).source(),
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar p:1") self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(2).source(),
self.today + " Bar p:1")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep3(self): def test_add_dep03(self):
command = AddCommand.AddCommand(["Foo"], self.todolist) command = AddCommand.AddCommand(["Foo"], self.todolist)
command.execute() 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() command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo p:1") self.assertEqual(self.todolist.todo(1).source(),
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar id:1") self.today + " Foo p:1")
self.assertEqual(self.todolist.todo(2).source(),
self.today + " Bar id:1")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep4(self): def test_add_de04(self):
""" Test for using an after: tag with non-existing value. """ """ 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() command.execute()
self.assertFalse(self.todolist.todo(1).has_tag("after")) self.assertFalse(self.todolist.todo(1).has_tag("after"))
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo") self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo")
self.assertEqual(self.output, "| 1| " + self.todolist.todo(1).source() + "\n") self.assertEqual(self.output,
"| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep5(self): def test_add_dep05(self):
""" Test for using an after: tag with non-existing value. """ """ 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() command.execute()
self.assertFalse(self.todolist.todo(1).has_tag("after")) self.assertFalse(self.todolist.todo(1).has_tag("after"))
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo") self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo")
self.assertEqual(self.output, "| 1| " + self.todolist.todo(1).source() + "\n") self.assertEqual(self.output,
"| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep6(self): def test_add_dep06(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute() 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.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() command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo id:1") self.assertEqual(self.todolist.todo(1).source(),
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar id:2") self.today + " Foo id:1")
self.assertEqual(self.todolist.todo(3).source(), self.today + " Baz p:1 p:2") 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, "") self.assertEqual(self.errors, "")
def test_add_dep7(self): def test_add_dep07(self):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["Foo"], self.todolist, self.out,
self.error)
command.execute() 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.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() command.execute()
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo p:1") self.assertEqual(self.todolist.todo(1).source(),
self.assertEqual(self.todolist.todo(2).source(), self.today + " Bar p:1") self.today + " Foo p:1")
self.assertEqual(self.todolist.todo(3).source(), self.today + " Baz id: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, "") self.assertEqual(self.errors, "")
def test_add_dep8(self): def test_add_dep08(self):
config("test/data/todolist-uid.conf") 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.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() command.execute()
self.assertEqual(self.todolist.todo('7ui').source(), "{} Foo p:1".format(self.today)) self.assertEqual(self.todolist.todo('7ui').source(),
self.assertEqual(self.todolist.todo('8to').source(), "{} Bar id:1".format(self.today)) "{} 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. By appending the parent's projects, the textual ID may change.
""" """
config("test/data/todolist-uid-projects.conf") config("test/data/todolist-uid-projects.conf")
# pass identitiy function to for writing output, we're not interested # pass identitiy function to for writing output, we're not interested
# in this output # 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.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.execute()
command = ListCommand.ListCommand(["Bar"], self.todolist, self.out, self.error) command = ListCommand.ListCommand(["Bar"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertEqual(self.output, "|kbn| {today} Bar p:1 +Project\n|kbn| {today} Bar +Project\n".format(today=self.today)) 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): 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. By appending the parent's contexts, the textual ID may change.
""" """
config("test/data/todolist-uid-contexts.conf") config("test/data/todolist-uid-contexts.conf")
# pass identitiy function to for writing output, we're not interested # pass identitiy function to for writing output, we're not interested
# in this output # 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.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.execute()
command = ListCommand.ListCommand(["Bar"], self.todolist, self.out, self.error) command = ListCommand.ListCommand(["Bar"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertEqual(self.output, "|wb3| {today} Bar p:1 @Context\n|wb3| {today} Bar @Context\n".format(today=self.today)) 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): 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() 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, "") self.assertEqual(self.errors, "")
def test_add_reldate2(self): 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() 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.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_empty(self): 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() command.execute()
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_add_unicode(self): 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() 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, "") 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): 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() 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, "") self.assertEqual(self.errors, "")
def test_add_from_file(self): def test_add_from_file(self):
command = AddCommand.AddCommand(["-f", "test/data/AddCommandTest-from_file.txt"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["-f", "test/data/AddCommandTest-from_file.txt"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output, 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, "") self.assertEqual(self.errors, "")
def test_add_task_without_date(self): def test_add_task_without_date(self):
config(p_overrides={('add', 'auto_creation_date'): '0'}) config(p_overrides={('add', 'auto_creation_date'): '0'})
args = ["New todo"] 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() command.execute()
self.assertEqual(self.todolist.todo(1).source(), "New todo") self.assertEqual(self.todolist.todo(1).source(), "New todo")
self.assertEqual(self.errors, "") 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): 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() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
import unittest import unittest
from test.command_testcase import CommandTest
from topydo.commands.AppendCommand import AppendCommand from topydo.commands.AppendCommand import AppendCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
class AppendCommandTest(CommandTest): class AppendCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(AppendCommandTest, self).setUp() super(AppendCommandTest, self).setUp()
...@@ -27,14 +28,16 @@ class AppendCommandTest(CommandTest): ...@@ -27,14 +28,16 @@ class AppendCommandTest(CommandTest):
self.todolist.add("Foo") self.todolist.add("Foo")
def test_append1(self): 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() command.execute()
self.assertEqual(self.output, "| 1| Foo Bar\n") self.assertEqual(self.output, "| 1| Foo Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_append2(self): 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() command.execute()
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
...@@ -55,7 +58,8 @@ class AppendCommandTest(CommandTest): ...@@ -55,7 +58,8 @@ class AppendCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_append5(self): 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() command.execute()
self.assertEqual(self.output, "| 1| Foo Bar Baz\n") self.assertEqual(self.output, "| 1| Foo Bar Baz\n")
...@@ -80,7 +84,8 @@ class AppendCommandTest(CommandTest): ...@@ -80,7 +84,8 @@ class AppendCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,11 +16,12 @@ ...@@ -16,11 +16,12 @@
import unittest import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ArchiveCommand import ArchiveCommand 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 from topydo.lib.TodoList import TodoList
class ArchiveCommandTest(CommandTest): class ArchiveCommandTest(CommandTest):
def test_archive(self): def test_archive(self):
todolist = load_file_to_todolist("test/data/ArchiveCommandTest.txt") todolist = load_file_to_todolist("test/data/ArchiveCommandTest.txt")
...@@ -36,4 +37,3 @@ class ArchiveCommandTest(CommandTest): ...@@ -36,4 +37,3 @@ class ArchiveCommandTest(CommandTest):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -17,9 +17,11 @@ ...@@ -17,9 +17,11 @@
""" Tests for the colorscheme functionality. """ """ Tests for the colorscheme functionality. """
import unittest 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 topydo.lib.Config import config
from test.TopydoTest import TopydoTest
class ColorsTest(TopydoTest): class ColorsTest(TopydoTest):
def test_project_color1(self): def test_project_color1(self):
...@@ -69,7 +71,7 @@ class ColorsTest(TopydoTest): ...@@ -69,7 +71,7 @@ class ColorsTest(TopydoTest):
color = Colors().get_context_color() color = Colors().get_context_color()
self.assertEqual(color, NEUTRAL_COLOR) self.assertEqual(color, NEUTRAL_COLOR)
def test_metadata_color1(self): def test_metadata_color1(self):
config(p_overrides={('colorscheme', 'metadata_color'): '128'}) config(p_overrides={('colorscheme', 'metadata_color'): '128'})
color = Colors().get_metadata_color() color = Colors().get_metadata_color()
...@@ -150,7 +152,7 @@ class ColorsTest(TopydoTest): ...@@ -150,7 +152,7 @@ class ColorsTest(TopydoTest):
self.assertEqual(color['A'], NEUTRAL_COLOR) self.assertEqual(color['A'], NEUTRAL_COLOR)
self.assertEqual(color['B'], 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): def test_empty_color_values(self):
config("test/data/ColorsTest5.conf") config("test/data/ColorsTest5.conf")
...@@ -183,3 +185,6 @@ class ColorsTest(TopydoTest): ...@@ -183,3 +185,6 @@ class ColorsTest(TopydoTest):
self.assertEqual(context_color, '\033[1;35m') self.assertEqual(context_color, '\033[1;35m')
self.assertEqual(link_color, '\033[4;36m') self.assertEqual(link_color, '\033[4;36m')
self.assertEqual(metadata_color, '\033[1;32m') self.assertEqual(metadata_color, '\033[1;32m')
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 @@ ...@@ -15,20 +15,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest import unittest
from six import u
from test.CommandTest import CommandTest from test.command_testcase import CommandTest
from topydo.lib.Config import config
from topydo.commands.DeleteCommand import DeleteCommand from topydo.commands.DeleteCommand import DeleteCommand
from topydo.lib.Config import config
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
def _yes_prompt(self): def _yes_prompt(self):
return "y" return "y"
def _no_prompt(self): def _no_prompt(self):
return "n" return "n"
class DeleteCommandTest(CommandTest): class DeleteCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(DeleteCommandTest, self).setUp() super(DeleteCommandTest, self).setUp()
...@@ -42,7 +44,8 @@ class DeleteCommandTest(CommandTest): ...@@ -42,7 +44,8 @@ class DeleteCommandTest(CommandTest):
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
def test_del1(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -51,7 +54,8 @@ class DeleteCommandTest(CommandTest): ...@@ -51,7 +54,8 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_del1_regex(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -60,29 +64,33 @@ class DeleteCommandTest(CommandTest): ...@@ -60,29 +64,33 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_del2(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.todolist.count(), 2) self.assertEqual(self.todolist.count(), 2)
self.assertEqual(self.output, "| 2| Bar p:1\nRemoved: Bar\nRemoved: Foo\n") self.assertEqual(self.output,
"| 2| Bar p:1\nRemoved: Bar\nRemoved: Foo\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_del3(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_del4(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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.output, "| 2| Bar p:1\nRemoved: Foo id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -125,7 +133,8 @@ class DeleteCommandTest(CommandTest): ...@@ -125,7 +133,8 @@ class DeleteCommandTest(CommandTest):
def test_multi_del1(self): def test_multi_del1(self):
""" Test deletion of multiple items. """ """ 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() command.execute()
result = "a @test with due:2015-06-03\na @test with +project" result = "a @test with due:2015-06-03\na @test with +project"
...@@ -135,7 +144,8 @@ class DeleteCommandTest(CommandTest): ...@@ -135,7 +144,8 @@ class DeleteCommandTest(CommandTest):
def test_multi_del2(self): def test_multi_del2(self):
""" Test deletion of multiple items. """ """ Test deletion of multiple items. """
command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _yes_prompt) command = DeleteCommand(["1", "2"], self.todolist, self.out,
self.error, _yes_prompt)
command.execute() command.execute()
result = "a @test with due:2015-06-03\na @test with +project" result = "a @test with due:2015-06-03\na @test with +project"
...@@ -145,7 +155,8 @@ class DeleteCommandTest(CommandTest): ...@@ -145,7 +155,8 @@ class DeleteCommandTest(CommandTest):
def test_multi_del3(self): def test_multi_del3(self):
""" Fail if any of supplied todo numbers is invalid. """ """ Fail if any of supplied todo numbers is invalid. """
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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -154,7 +165,8 @@ class DeleteCommandTest(CommandTest): ...@@ -154,7 +165,8 @@ class DeleteCommandTest(CommandTest):
def test_multi_del4(self): def test_multi_del4(self):
""" Check output when all supplied todo numbers are invalid. """ """ 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -162,16 +174,21 @@ class DeleteCommandTest(CommandTest): ...@@ -162,16 +174,21 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: A.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: A.\n")
def test_multi_del5(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
def test_expr_del1(self): 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() command.execute()
result = "Removed: a @test with due:2015-06-03\nRemoved: a @test with +project\n" result = "Removed: a @test with due:2015-06-03\nRemoved: a @test with +project\n"
...@@ -182,7 +199,8 @@ class DeleteCommandTest(CommandTest): ...@@ -182,7 +199,8 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_del2(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -190,14 +208,16 @@ class DeleteCommandTest(CommandTest): ...@@ -190,14 +208,16 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_del3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_del4(self): def test_expr_del4(self):
""" Remove only relevant todo items. """ """ 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() command.execute()
result = "Foo" result = "Foo"
...@@ -208,7 +228,8 @@ class DeleteCommandTest(CommandTest): ...@@ -208,7 +228,8 @@ class DeleteCommandTest(CommandTest):
def test_expr_del5(self): def test_expr_del5(self):
""" Force deleting unrelevant items with additional -x flag. """ """ 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -227,7 +248,8 @@ class DeleteCommandTest(CommandTest): ...@@ -227,7 +248,8 @@ class DeleteCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
import unittest import unittest
from test.command_testcase import CommandTest
from topydo.commands.DepCommand import DepCommand from topydo.commands.DepCommand import DepCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
class DepCommandTest(CommandTest): class DepCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(DepCommandTest, self).setUp() super(DepCommandTest, self).setUp()
...@@ -35,7 +36,8 @@ class DepCommandTest(CommandTest): ...@@ -35,7 +36,8 @@ class DepCommandTest(CommandTest):
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
def test_add1(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -44,7 +46,8 @@ class DepCommandTest(CommandTest): ...@@ -44,7 +46,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add2(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -53,7 +56,8 @@ class DepCommandTest(CommandTest): ...@@ -53,7 +56,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -61,7 +65,8 @@ class DepCommandTest(CommandTest): ...@@ -61,7 +65,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_add4(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -77,7 +82,8 @@ class DepCommandTest(CommandTest): ...@@ -77,7 +82,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_add6(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -86,7 +92,8 @@ class DepCommandTest(CommandTest): ...@@ -86,7 +92,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add7(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -95,7 +102,8 @@ class DepCommandTest(CommandTest): ...@@ -95,7 +102,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add8(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -104,7 +112,8 @@ class DepCommandTest(CommandTest): ...@@ -104,7 +112,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add9(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -117,7 +126,6 @@ class DepCommandTest(CommandTest): ...@@ -117,7 +126,6 @@ class DepCommandTest(CommandTest):
Helper function that checks the removal of the dependency from todo 1 Helper function that checks the removal of the dependency from todo 1
to todo 3. to todo 3.
""" """
command = DepCommand(p_args, self.todolist, self.out, self.error) command = DepCommand(p_args, self.todolist, self.out, self.error)
command.execute() command.execute()
...@@ -131,16 +139,17 @@ class DepCommandTest(CommandTest): ...@@ -131,16 +139,17 @@ class DepCommandTest(CommandTest):
self.rm_helper(["rm", "1", "to", "3"]) self.rm_helper(["rm", "1", "to", "3"])
def test_rm2(self): def test_rm2(self):
self.rm_helper(["rm", "1", "3"]) self.rm_helper(["rm", "1", "3"])
def test_del1(self): def test_del1(self):
self.rm_helper(["del", "1", "to", "3"]) self.rm_helper(["del", "1", "to", "3"])
def test_del2(self): def test_del2(self):
self.rm_helper(["del", "1", "3"]) self.rm_helper(["del", "1", "3"])
def test_rm3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -148,7 +157,8 @@ class DepCommandTest(CommandTest): ...@@ -148,7 +157,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_rm4(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -164,7 +174,8 @@ class DepCommandTest(CommandTest): ...@@ -164,7 +174,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_ls1(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -172,7 +183,8 @@ class DepCommandTest(CommandTest): ...@@ -172,7 +183,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_ls2(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -180,7 +192,8 @@ class DepCommandTest(CommandTest): ...@@ -180,7 +192,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_ls3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -188,7 +201,8 @@ class DepCommandTest(CommandTest): ...@@ -188,7 +201,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_ls4(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -212,7 +226,8 @@ class DepCommandTest(CommandTest): ...@@ -212,7 +226,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_ls7(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -220,7 +235,8 @@ class DepCommandTest(CommandTest): ...@@ -220,7 +235,8 @@ class DepCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def gc_helper(self, p_subcommand): def gc_helper(self, p_subcommand):
command = DepCommand([p_subcommand], self.todolist, self.out, self.error) command = DepCommand([p_subcommand], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -255,7 +271,8 @@ class DepCommandTest(CommandTest): ...@@ -255,7 +271,8 @@ class DepCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -15,12 +15,12 @@ ...@@ -15,12 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest import unittest
from six import u
from test.command_testcase import CommandTest
from topydo.commands.DepriCommand import DepriCommand from topydo.commands.DepriCommand import DepriCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
class DepriCommandTest(CommandTest): class DepriCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(DepriCommandTest, self).setUp() super(DepriCommandTest, self).setUp()
...@@ -63,7 +63,8 @@ class DepriCommandTest(CommandTest): ...@@ -63,7 +63,8 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_depri4(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -73,10 +74,10 @@ class DepriCommandTest(CommandTest): ...@@ -73,10 +74,10 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_depri1(self): 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() 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" result = "Priority removed.\n| 4| a @test with due:2015-06-03\nPriority removed.\n| 5| a @test with +project p:1\n"
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -84,7 +85,8 @@ class DepriCommandTest(CommandTest): ...@@ -84,7 +85,8 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_depri2(self): 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() command.execute()
result = "Priority removed.\n| 4| a @test with due:2015-06-03\n" result = "Priority removed.\n| 4| a @test with due:2015-06-03\n"
...@@ -94,29 +96,30 @@ class DepriCommandTest(CommandTest): ...@@ -94,29 +96,30 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_depri3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_depri4(self): def test_expr_depri4(self):
""" Don't remove priority from unrelevant todo items. """ """ Don't remove priority from unrelevant todo items. """
command = DepriCommand(["-e", "Bax"], self.todolist, self.out, self.error, None) command = DepriCommand(["-e", "Bax"], self.todolist, self.out,
self.error, None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_depri5(self): def test_expr_depri5(self):
""" Force unprioritizing unrelevant items with additional -x flag. """ """ Force unprioritizing unrelevant items with additional -x flag. """
command = DepriCommand(["-xe", "Bax"], self.todolist, self.out, self.error, None) command = DepriCommand(["-xe", "Bax"], self.todolist, self.out,
self.error, None)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, "Priority removed.\n| 6| Bax id:1\n") self.assertEqual(self.output, "Priority removed.\n| 6| Bax id:1\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_invalid1(self): def test_invalid1(self):
command = DepriCommand(["99"], self.todolist, self.out, self.error) command = DepriCommand(["99"], self.todolist, self.out, self.error)
command.execute() command.execute()
...@@ -126,7 +129,8 @@ class DepriCommandTest(CommandTest): ...@@ -126,7 +129,8 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_invalid2(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -134,7 +138,8 @@ class DepriCommandTest(CommandTest): ...@@ -134,7 +138,8 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
def test_invalid3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -142,13 +147,17 @@ class DepriCommandTest(CommandTest): ...@@ -142,13 +147,17 @@ class DepriCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: FooBar.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: FooBar.\n")
def test_invalid4(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.output) 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): def test_empty(self):
command = DepriCommand([], self.todolist, self.out, self.error) command = DepriCommand([], self.todolist, self.out, self.error)
...@@ -163,7 +172,8 @@ class DepriCommandTest(CommandTest): ...@@ -163,7 +172,8 @@ class DepriCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -14,20 +14,22 @@ ...@@ -14,20 +14,22 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date, timedelta
import unittest import unittest
from six import u from datetime import date, timedelta
from test.command_testcase import CommandTest
from topydo.commands.DoCommand import DoCommand from topydo.commands.DoCommand import DoCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
def _yes_prompt(self): def _yes_prompt(self):
return "y" return "y"
def _no_prompt(self): def _no_prompt(self):
return "n" return "n"
class DoCommandTest(CommandTest): class DoCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(DoCommandTest, self).setUp() super(DoCommandTest, self).setUp()
...@@ -55,17 +57,19 @@ class DoCommandTest(CommandTest): ...@@ -55,17 +57,19 @@ class DoCommandTest(CommandTest):
self.tomorrow = self.tomorrow.isoformat() self.tomorrow = self.tomorrow.isoformat()
def test_do1(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.todo(3).is_completed()) self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEqual(self.output, "Completed: x {} Baz p:1\n".format( self.assertEqual(self.output,
self.today)) "Completed: x {} Baz p:1\n".format(self.today))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_do_subtasks1(self): 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() 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) 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): ...@@ -79,7 +83,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_do_subtasks2(self): 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() command.execute()
result = "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {} Foo id:1\n".format(self.today) result = "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {} Foo id:1\n".format(self.today)
...@@ -98,7 +103,8 @@ class DoCommandTest(CommandTest): ...@@ -98,7 +103,8 @@ class DoCommandTest(CommandTest):
global prompt_shown global prompt_shown
prompt_shown = True 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() command.execute()
self.assertFalse(prompt_shown) self.assertFalse(prompt_shown)
...@@ -112,7 +118,8 @@ class DoCommandTest(CommandTest): ...@@ -112,7 +118,8 @@ class DoCommandTest(CommandTest):
global prompt_shown global prompt_shown
prompt_shown = True 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() command.execute()
self.assertFalse(prompt_shown) self.assertFalse(prompt_shown)
...@@ -168,7 +175,8 @@ class DoCommandTest(CommandTest): ...@@ -168,7 +175,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_invalid3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -202,7 +210,8 @@ class DoCommandTest(CommandTest): ...@@ -202,7 +210,8 @@ class DoCommandTest(CommandTest):
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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.assertFalse(self.output)
self.assertEqual(self.errors, "Todo has already been completed.\n") self.assertEqual(self.errors, "Todo has already been completed.\n")
...@@ -212,11 +221,13 @@ class DoCommandTest(CommandTest): ...@@ -212,11 +221,13 @@ class DoCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertTrue(self.todolist.todo(3).is_completed()) 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, "") self.assertEqual(self.errors, "")
def test_do_custom_date1(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -224,7 +235,8 @@ class DoCommandTest(CommandTest): ...@@ -224,7 +235,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_do_custom_date2(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -232,7 +244,8 @@ class DoCommandTest(CommandTest): ...@@ -232,7 +244,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_do_custom_date3(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -240,11 +253,13 @@ class DoCommandTest(CommandTest): ...@@ -240,11 +253,13 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_do_custom_date4(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_do_custom_date5(self): def test_do_custom_date5(self):
...@@ -252,7 +267,8 @@ class DoCommandTest(CommandTest): ...@@ -252,7 +267,8 @@ class DoCommandTest(CommandTest):
Make sure that the new recurrence date is correct when a custom Make sure that the new recurrence date is correct when a custom
date is given. 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -265,7 +281,8 @@ class DoCommandTest(CommandTest): ...@@ -265,7 +281,8 @@ class DoCommandTest(CommandTest):
due date as the offset. This todo item however, has no due date, then due date as the offset. This todo item however, has no due date, then
the completion date must be used as an offset. 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -277,15 +294,43 @@ class DoCommandTest(CommandTest): ...@@ -277,15 +294,43 @@ class DoCommandTest(CommandTest):
When a custom date is set, strict recurrence must still hold on to the When a custom date is set, strict recurrence must still hold on to the
due date as the offset. 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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.output, "| 12| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {yesterday} Strict due:2014-01-01 rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_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): 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() command.execute()
self.assertTrue(self.todolist.todo(1).is_completed()) self.assertTrue(self.todolist.todo(1).is_completed())
...@@ -295,7 +340,8 @@ class DoCommandTest(CommandTest): ...@@ -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)) 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): 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() command.execute()
self.assertTrue(self.todolist.todo(1).is_completed()) self.assertTrue(self.todolist.todo(1).is_completed())
...@@ -305,14 +351,17 @@ class DoCommandTest(CommandTest): ...@@ -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)) 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): 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() command.execute()
self.assertTrue(self.todolist.todo(3).is_completed()) 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): 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() command.execute()
self.assertFalse(self.todolist.todo(3).is_completed()) self.assertFalse(self.todolist.todo(3).is_completed())
...@@ -322,21 +371,27 @@ class DoCommandTest(CommandTest): ...@@ -322,21 +371,27 @@ class DoCommandTest(CommandTest):
""" """
Check output when all supplied todo numbers are invalid. 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() command.execute()
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 15.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 15.\n")
def test_multi_do6(self): def test_multi_do6(self):
""" Throw an error with invalid argument containing special characters. """ """
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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
def test_expr_do1(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -344,7 +399,8 @@ class DoCommandTest(CommandTest): ...@@ -344,7 +399,8 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_do2(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -352,21 +408,24 @@ class DoCommandTest(CommandTest): ...@@ -352,21 +408,24 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_do3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_do4(self): def test_expr_do4(self):
""" Don't do anything with unrelevant todo items. """ """ Don't do anything with unrelevant todo items. """
command = DoCommand(["-e", "Foo"], self.todolist, self.out, self.error, None) command = DoCommand(["-e", "Foo"], self.todolist, self.out, self.error,
None)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_do5(self): def test_expr_do5(self):
""" Force marking unrelevant items as done with additional -x flag. """ """ Force marking unrelevant items as done with additional -x flag. """
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() 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) 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): ...@@ -376,11 +435,15 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_invalid_recurrence(self): def test_invalid_recurrence(self):
""" Show error message when an item has an invalid recurrence pattern. """ """
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() 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") self.assertEqual(self.errors, "Warning: todo item has an invalid recurrence pattern.\n")
def test_empty(self): def test_empty(self):
...@@ -396,7 +459,8 @@ class DoCommandTest(CommandTest): ...@@ -396,7 +459,8 @@ class DoCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -14,8 +14,15 @@ ...@@ -14,8 +14,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import unittest 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' # We're searching for 'mock'
# pylint: disable=no-name-in-module # pylint: disable=no-name-in-module
try: try:
...@@ -23,14 +30,6 @@ try: ...@@ -23,14 +30,6 @@ try:
except ImportError: except ImportError:
import mock 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): class EditCommandTest(CommandTest):
def setUp(self): def setUp(self):
...@@ -39,105 +38,129 @@ class EditCommandTest(CommandTest): ...@@ -39,105 +38,129 @@ class EditCommandTest(CommandTest):
"Foo id:1", "Foo id:1",
"Bar p:1 @test", "Bar p:1 @test",
"Baz @test", "Baz @test",
u("Fo\u00f3B\u0105\u017a"), u"Fo\u00f3B\u0105\u017a",
] ]
self.todolist = TodoList(todos) 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') @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. """ """ Preserve dependencies after editing. """
mock_open_in_editor.return_value = 0 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 = EditCommand(["1"], self.todolist, self.out, self.error, None)
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") 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._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @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. """ """ Edit some todo. """
mock_open_in_editor.return_value = 0 mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Lazy Cat')] 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat")) self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat")
def test_edit3(self): def test_edit03(self):
""" Throw an error after invalid todo number given as argument. """ """ 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_edit4(self): def test_edit04(self):
""" Throw an error with pointing invalid argument. """ """ 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Invalid todo number given: 5.\n") self.assertEqual(self.errors, "Invalid todo number given: 5.\n")
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp') def test_edit05(self):
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') """
def test_edit5(self, mock_open_in_editor, mock_todos_from_temp): Throw an error with invalid argument containing special characters.
""" Don't let to delete todos acidentally while editing. """ """
mock_open_in_editor.return_value = 0 command = EditCommand([u"Fo\u00d3B\u0105r", "Bar"], self.todolist,
mock_todos_from_temp.return_value = [Todo('Only one line')] self.out, self.error, None)
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)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
@mock.patch('topydo.commands.EditCommand._is_edited')
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp') @mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
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. """ """ Edit todo with special characters. """
mock_open_in_editor.return_value = 0 mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Lazy Cat')] 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") 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._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @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. """ """ Edit todos matching expression. """
mock_open_in_editor.return_value = 0 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() 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.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, expected) self.assertEqual(self.output, expected)
self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog")) self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog")
@mock.patch('topydo.commands.EditCommand.check_call') @mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_archive(self, mock_call): def test_edit_archive(self, mock_call):
...@@ -148,7 +171,8 @@ class EditCommandTest(CommandTest): ...@@ -148,7 +171,8 @@ class EditCommandTest(CommandTest):
os.environ['EDITOR'] = editor os.environ['EDITOR'] = editor
archive = config().archive() 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() command.execute()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
...@@ -163,7 +187,7 @@ class EditCommandTest(CommandTest): ...@@ -163,7 +187,7 @@ class EditCommandTest(CommandTest):
os.environ['EDITOR'] = editor os.environ['EDITOR'] = editor
todotxt = config().todotxt() 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 = EditCommand([], self.todolist, self.out, self.error, None)
command.execute() command.execute()
...@@ -172,5 +196,14 @@ class EditCommandTest(CommandTest): ...@@ -172,5 +196,14 @@ class EditCommandTest(CommandTest):
self.assertEqual(self.todolist.print_todos(), result) self.assertEqual(self.todolist.print_todos(), result)
mock_call.assert_called_once_with([editor, todotxt]) 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,23 +16,25 @@ ...@@ -16,23 +16,25 @@
""" Tests for the filter functionality. """ """ Tests for the filter functionality. """
from datetime import date, timedelta
import unittest 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 topydo.lib import Filter
from test.TestFacilities import load_file, todolist_to_string, load_file_to_todolist
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
class FilterTest(TopydoTest): class FilterTest(TopydoTest):
def test_filter3(self): def test_filter03(self):
todo = Todo("(C) Relevant") todo = Todo("(C) Relevant")
relevance = Filter.RelevanceFilter() relevance = Filter.RelevanceFilter()
result = relevance.filter([todo]) result = relevance.filter([todo])
self.assertEqual(result, [todo]) self.assertEqual(result, [todo])
def test_filter4(self): def test_filter04(self):
""" Test case insensitive match. """ """ Test case insensitive match. """
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter('+project') grep = Filter.GrepFilter('+project')
...@@ -40,10 +42,10 @@ class FilterTest(TopydoTest): ...@@ -40,10 +42,10 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos) filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest1a-result.txt') reference = load_file('test/data/FilterTest1a-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter5(self): def test_filter05(self):
""" Test case sensitive match. """ """ Test case sensitive match. """
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter('+Project') grep = Filter.GrepFilter('+Project')
...@@ -51,10 +53,10 @@ class FilterTest(TopydoTest): ...@@ -51,10 +53,10 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos) filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest1b-result.txt') reference = load_file('test/data/FilterTest1b-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter6(self): def test_filter06(self):
""" Test case sensitive match (forced, with lowercase). """ """ Test case sensitive match (forced, with lowercase). """
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter('+project', True) grep = Filter.GrepFilter('+project', True)
...@@ -62,10 +64,10 @@ class FilterTest(TopydoTest): ...@@ -62,10 +64,10 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos) filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest1c-result.txt') reference = load_file('test/data/FilterTest1c-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter7(self): def test_filter07(self):
""" Tests the dependency filter. """ """ Tests the dependency filter. """
todolist = load_file_to_todolist('test/data/FilterTest2.txt') todolist = load_file_to_todolist('test/data/FilterTest2.txt')
depfilter = Filter.DependencyFilter(todolist) depfilter = Filter.DependencyFilter(todolist)
...@@ -73,10 +75,10 @@ class FilterTest(TopydoTest): ...@@ -73,10 +75,10 @@ class FilterTest(TopydoTest):
filtered_todos = depfilter.filter(todolist.todos()) filtered_todos = depfilter.filter(todolist.todos())
reference = load_file('test/data/FilterTest2-result.txt') reference = load_file('test/data/FilterTest2-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter8(self): def test_filter08(self):
""" Test case sensitive match (forced, with lowercase). """ """ Test case sensitive match (forced, with lowercase). """
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
grep = Filter.GrepFilter('+Project', False) grep = Filter.GrepFilter('+Project', False)
...@@ -84,11 +86,11 @@ class FilterTest(TopydoTest): ...@@ -84,11 +86,11 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos) filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest1a-result.txt') reference = load_file('test/data/FilterTest1a-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter9(self): def test_filter09(self):
""" Test instance filter """ """ Test instance filter. """
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
instance_filter = Filter.InstanceFilter(todos[2:]) instance_filter = Filter.InstanceFilter(todos[2:])
...@@ -97,7 +99,7 @@ class FilterTest(TopydoTest): ...@@ -97,7 +99,7 @@ class FilterTest(TopydoTest):
self.assertEqual(todos[2:], filtered_todos) self.assertEqual(todos[2:], filtered_todos)
def test_filter10(self): def test_filter10(self):
""" Test instance filter """ """ Test instance filter. """
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
instance_filter = Filter.InstanceFilter([]) instance_filter = Filter.InstanceFilter([])
...@@ -106,7 +108,7 @@ class FilterTest(TopydoTest): ...@@ -106,7 +108,7 @@ class FilterTest(TopydoTest):
self.assertEqual([], filtered_todos) self.assertEqual([], filtered_todos)
def test_filter11(self): def test_filter11(self):
""" Test instance filter """ """ Test instance filter. """
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
instance_filter = Filter.InstanceFilter(todos[2:]) instance_filter = Filter.InstanceFilter(todos[2:])
...@@ -131,7 +133,8 @@ class FilterTest(TopydoTest): ...@@ -131,7 +133,8 @@ class FilterTest(TopydoTest):
filtered_todos = limit_filter.filter(todos) filtered_todos = limit_filter.filter(todos)
self.assertEqual(len(filtered_todos), 1) 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): def test_filter14(self):
""" Test limit filter. """ """ Test limit filter. """
...@@ -158,8 +161,8 @@ class FilterTest(TopydoTest): ...@@ -158,8 +161,8 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos) filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest3-result.txt') reference = load_file('test/data/FilterTest3-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter17(self): def test_filter17(self):
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
...@@ -170,8 +173,8 @@ class FilterTest(TopydoTest): ...@@ -170,8 +173,8 @@ class FilterTest(TopydoTest):
filtered_todos = andfilter.filter(todos) filtered_todos = andfilter.filter(todos)
reference = load_file('test/data/FilterTest4-result.txt') reference = load_file('test/data/FilterTest4-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter18(self): def test_filter18(self):
todos = load_file('test/data/FilterTest1.txt') todos = load_file('test/data/FilterTest1.txt')
...@@ -182,8 +185,8 @@ class FilterTest(TopydoTest): ...@@ -182,8 +185,8 @@ class FilterTest(TopydoTest):
filtered_todos = grep.filter(todos) filtered_todos = grep.filter(todos)
reference = load_file('test/data/FilterTest5-result.txt') reference = load_file('test/data/FilterTest5-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter20(self): def test_filter20(self):
todos = load_file('test/data/FilterTest3.txt') todos = load_file('test/data/FilterTest3.txt')
...@@ -192,8 +195,8 @@ class FilterTest(TopydoTest): ...@@ -192,8 +195,8 @@ class FilterTest(TopydoTest):
filtered_todos = otf.filter(todos) filtered_todos = otf.filter(todos)
reference = load_file('test/data/FilterTest6-result.txt') reference = load_file('test/data/FilterTest6-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), \ self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter21(self): def test_filter21(self):
todos = load_file('test/data/FilterTest3.txt') todos = load_file('test/data/FilterTest3.txt')
...@@ -235,7 +238,7 @@ class FilterTest(TopydoTest): ...@@ -235,7 +238,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest8-result.txt') reference = load_file('test/data/FilterTest8-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter26(self): def test_filter26(self):
todos = load_file('test/data/FilterTest3.txt') todos = load_file('test/data/FilterTest3.txt')
...@@ -245,7 +248,7 @@ class FilterTest(TopydoTest): ...@@ -245,7 +248,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest9-result.txt') reference = load_file('test/data/FilterTest9-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter27(self): def test_filter27(self):
todos = load_file('test/data/FilterTest3.txt') todos = load_file('test/data/FilterTest3.txt')
...@@ -255,7 +258,7 @@ class FilterTest(TopydoTest): ...@@ -255,7 +258,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest10-result.txt') reference = load_file('test/data/FilterTest10-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter28(self): def test_filter28(self):
todos = load_file('test/data/FilterTest3.txt') todos = load_file('test/data/FilterTest3.txt')
...@@ -273,7 +276,7 @@ class FilterTest(TopydoTest): ...@@ -273,7 +276,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest11-result.txt') reference = load_file('test/data/FilterTest11-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter30(self): def test_filter30(self):
todos = load_file('test/data/FilterTest3.txt') todos = load_file('test/data/FilterTest3.txt')
...@@ -283,7 +286,7 @@ class FilterTest(TopydoTest): ...@@ -283,7 +286,7 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest12-result.txt') reference = load_file('test/data/FilterTest12-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
def test_filter31(self): def test_filter31(self):
todos = load_file('test/data/FilterTest3.txt') todos = load_file('test/data/FilterTest3.txt')
...@@ -293,7 +296,8 @@ class FilterTest(TopydoTest): ...@@ -293,7 +296,8 @@ class FilterTest(TopydoTest):
reference = load_file('test/data/FilterTest13-result.txt') reference = load_file('test/data/FilterTest13-result.txt')
self.assertEqual(todolist_to_string(filtered_todos), self.assertEqual(todolist_to_string(filtered_todos),
todolist_to_string(reference)) todolist_to_string(reference))
class OrdinalTagFilterTest(TopydoTest): class OrdinalTagFilterTest(TopydoTest):
def setUp(self): def setUp(self):
...@@ -374,6 +378,183 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -374,6 +378,183 @@ class OrdinalTagFilterTest(TopydoTest):
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(result[0].source(), self.todo3) 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__': if __name__ == '__main__':
unittest.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. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,8 +16,9 @@ ...@@ -16,8 +16,9 @@
import unittest import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Graph import DirectedGraph from topydo.lib.Graph import DirectedGraph
from test.TopydoTest import TopydoTest
class GraphTest(TopydoTest): class GraphTest(TopydoTest):
def setUp(self): def setUp(self):
...@@ -63,7 +64,8 @@ class GraphTest(TopydoTest): ...@@ -63,7 +64,8 @@ class GraphTest(TopydoTest):
self.assertEqual(self.graph.incoming_neighbors(1, True), set()) self.assertEqual(self.graph.incoming_neighbors(1, True), set())
def test_incoming_neighbors4(self): 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): def test_outgoing_neighbors1(self):
self.assertEqual(self.graph.outgoing_neighbors(1), set([2, 3])) self.assertEqual(self.graph.outgoing_neighbors(1), set([2, 3]))
...@@ -72,7 +74,8 @@ class GraphTest(TopydoTest): ...@@ -72,7 +74,8 @@ class GraphTest(TopydoTest):
self.assertEqual(self.graph.outgoing_neighbors(2), set([4])) self.assertEqual(self.graph.outgoing_neighbors(2), set([4]))
def test_outgoing_neighbors3(self): 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): def test_outgoing_neighbors4(self):
self.assertEqual(self.graph.outgoing_neighbors(3), set([5])) self.assertEqual(self.graph.outgoing_neighbors(3), set([5]))
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,31 +14,47 @@ ...@@ -14,31 +14,47 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date
import unittest 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.Config import config
from topydo.lib.Importance import importance from topydo.lib.Importance import importance
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
@freeze_time("2015, 11, 06")
class ImportanceTest(TopydoTest): class ImportanceTest(TopydoTest):
def test_importance1(self): def test_importance01(self):
todo = Todo("Foo") todo = Todo("Foo")
self.assertEqual(importance(todo), 2) self.assertEqual(importance(todo), 2)
def test_importance2(self): def test_importance02(self):
todo = Todo("(A) Foo") todo = Todo("(A) Foo")
self.assertEqual(importance(todo), 5) self.assertEqual(importance(todo), 5)
def test_importance3(self): def test_importance03(self):
todo = Todo("(A) Foo " + config().tag_star() + ":1") todo = Todo("(A) Foo " + config().tag_star() + ":1")
self.assertEqual(importance(todo), 6) self.assertEqual(importance(todo), 6)
def test_importance4(self): def test_importance04(self):
today_str = date.today().isoformat() today_str = date.today().isoformat()
todo = Todo("(C) Foo " + config().tag_due() + ":" + today_str) todo = Todo("(C) Foo " + config().tag_due() + ":" + today_str)
self.assertEqual(importance(todo), 8) 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__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -14,15 +14,19 @@ ...@@ -14,15 +14,19 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.JsonPrinter import JsonPrinter from topydo.lib.JsonPrinter import JsonPrinter
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
class JsonPrinterTest(TopydoTest): class JsonPrinterTest(TopydoTest):
""" """
Tests the functionality of printing a single todo item. Printing a list is Tests the functionality of printing a single todo item. Printing a list is
already covered by the ListCommand tests. already covered by the ListCommand tests.
""" """
def test_json(self): def test_json(self):
""" Print a single todo item. """ """ Print a single todo item. """
printer = JsonPrinter() printer = JsonPrinter()
...@@ -31,3 +35,6 @@ class JsonPrinterTest(TopydoTest): ...@@ -31,3 +35,6 @@ class JsonPrinterTest(TopydoTest):
result = printer.print_todo(todo) 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"}') 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. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,80 +14,95 @@ ...@@ -14,80 +14,95 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from six import u
import codecs import codecs
import re import re
import sys
import unittest 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 topydo.commands.ListCommand import ListCommand
from test.CommandTest import CommandTest from topydo.lib.Config import config
from test.TestFacilities import load_file_to_todolist
# 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): class ListCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(ListCommandTest, self).setUp() super(ListCommandTest, self).setUp()
self.todolist = load_file_to_todolist("test/data/ListCommandTest.txt") 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 = ListCommand([""], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list3(self): def test_list03(self):
command = ListCommand(["Context1"], self.todolist, self.out, self.error) command = ListCommand(["Context1"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n") self.assertEqual(self.output, "| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_list4(self): def test_list04(self):
command = ListCommand(["-x", "Context1"], self.todolist, self.out, self.error) command = ListCommand(["-x", "Context1"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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.output, "| 3| (C) Baz @Context1 +Project1 key:value\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_list5(self): def test_list05(self):
command = ListCommand(["-x"], self.todolist, self.out, self.error) command = ListCommand(["-x"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.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, "") self.assertEqual(self.errors, "")
def test_list6(self): def test_list06(self):
command = ListCommand(["Project3"], self.todolist, self.out, self.error) command = ListCommand(["Project3"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_list7(self): def test_list07(self):
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() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list8(self): def test_list08(self):
command = ListCommand(["--", "-project1"], self.todolist, self.out, self.error) command = ListCommand(["--", "-project1"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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.output, "| 4| (C) Drink beer @ home\n| 5| (C) 13 + 29 = 42\n| 2| (D) Bar @Context1 +Project2\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_list9(self): def test_list09(self):
command = ListCommand(["--", "-project1", "-Drink"], self.todolist, self.out, self.error) command = ListCommand(["--", "-project1", "-Drink"], self.todolist,
self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -95,7 +110,8 @@ class ListCommandTest(CommandTest): ...@@ -95,7 +110,8 @@ class ListCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_list10(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -109,25 +125,27 @@ class ListCommandTest(CommandTest): ...@@ -109,25 +125,27 @@ class ListCommandTest(CommandTest):
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list12(self): def test_list12(self):
config("test/data/listcommand.conf") 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list13(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list14(self): def test_list14(self):
...@@ -137,7 +155,7 @@ class ListCommandTest(CommandTest): ...@@ -137,7 +155,7 @@ class ListCommandTest(CommandTest):
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list15(self): def test_list15(self):
...@@ -155,19 +173,22 @@ class ListCommandTest(CommandTest): ...@@ -155,19 +173,22 @@ class ListCommandTest(CommandTest):
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list17(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list18(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -177,11 +198,12 @@ class ListCommandTest(CommandTest): ...@@ -177,11 +198,12 @@ class ListCommandTest(CommandTest):
""" Force showing all tags. """ """ Force showing all tags. """
config('test/data/listcommand-tags.conf') 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list20(self): def test_list20(self):
...@@ -189,15 +211,179 @@ class ListCommandTest(CommandTest): ...@@ -189,15 +211,179 @@ class ListCommandTest(CommandTest):
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_list21(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_help(self): def test_help(self):
...@@ -205,7 +391,9 @@ class ListCommandTest(CommandTest): ...@@ -205,7 +391,9 @@ class ListCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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): class ListCommandUnicodeTest(CommandTest):
def setUp(self): def setUp(self):
...@@ -213,16 +401,18 @@ class ListCommandUnicodeTest(CommandTest): ...@@ -213,16 +401,18 @@ class ListCommandUnicodeTest(CommandTest):
self.todolist = load_file_to_todolist("test/data/ListCommandUnicodeTest.txt") self.todolist = load_file_to_todolist("test/data/ListCommandUnicodeTest.txt")
def test_list_unicode1(self): def test_list_unicode1(self):
""" Unicode filters """ """ Unicode filters."""
command = ListCommand([u("\u25c4")], self.todolist, self.out, self.error) command = ListCommand([u"\u25c4"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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) self.assertEqual(self.output, expected)
class ListCommandJsonTest(CommandTest): class ListCommandJsonTest(CommandTest):
def test_json(self): def test_json(self):
todolist = load_file_to_todolist("test/data/ListCommandTest.txt") todolist = load_file_to_todolist("test/data/ListCommandTest.txt")
...@@ -233,7 +423,8 @@ class ListCommandJsonTest(CommandTest): ...@@ -233,7 +423,8 @@ class ListCommandJsonTest(CommandTest):
self.assertFalse(todolist.is_dirty()) self.assertFalse(todolist.is_dirty())
jsontext = "" 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() jsontext = json.read()
self.assertEqual(self.output, jsontext) self.assertEqual(self.output, jsontext)
...@@ -248,12 +439,14 @@ class ListCommandJsonTest(CommandTest): ...@@ -248,12 +439,14 @@ class ListCommandJsonTest(CommandTest):
self.assertFalse(todolist.is_dirty()) self.assertFalse(todolist.is_dirty())
jsontext = "" 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() jsontext = json.read()
self.assertEqual(self.output, jsontext) self.assertEqual(self.output, jsontext)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def replace_ical_tags(p_text): def replace_ical_tags(p_text):
# replace identifiers with dots, since they're random. # replace identifiers with dots, since they're random.
result = re.sub(r'\bical:....\b', 'ical:....', p_text) result = re.sub(r'\bical:....\b', 'ical:....', p_text)
...@@ -261,43 +454,29 @@ def replace_ical_tags(p_text): ...@@ -261,43 +454,29 @@ def replace_ical_tags(p_text):
return result return result
IS_PYTHON_32 = (sys.version_info.major, sys.version_info.minor) == (3, 2)
class ListCommandIcalTest(CommandTest): class ListCommandIcalTest(CommandTest):
def setUp(self): def setUp(self):
self.maxDiff = None self.maxDiff = None
@unittest.skipIf(IS_PYTHON_32, "icalendar is not supported for Python 3.2")
def test_ical(self): def test_ical(self):
todolist = load_file_to_todolist("test/data/ListCommandIcalTest.txt") 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() command.execute()
self.assertTrue(todolist.is_dirty()) self.assertTrue(todolist.is_dirty())
icaltext = "" 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() 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, "") 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): def test_ical_unicode(self):
todolist = load_file_to_todolist("test/data/ListCommandUnicodeTest.txt") todolist = load_file_to_todolist("test/data/ListCommandUnicodeTest.txt")
...@@ -307,10 +486,12 @@ class ListCommandIcalTest(CommandTest): ...@@ -307,10 +486,12 @@ class ListCommandIcalTest(CommandTest):
self.assertTrue(todolist.is_dirty()) self.assertTrue(todolist.is_dirty())
icaltext = "" 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() 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, "") self.assertEqual(self.errors, "")
if __name__ == '__main__': if __name__ == '__main__':
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,9 +16,10 @@ ...@@ -16,9 +16,10 @@
import unittest import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListContextCommand import ListContextCommand from topydo.commands.ListContextCommand import ListContextCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
class ListContextCommandTest(CommandTest): class ListContextCommandTest(CommandTest):
def test_contexts1(self): def test_contexts1(self):
...@@ -26,7 +27,7 @@ class ListContextCommandTest(CommandTest): ...@@ -26,7 +27,7 @@ class ListContextCommandTest(CommandTest):
command = ListContextCommand([""], todolist, self.out, self.error) command = ListContextCommand([""], todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output,"Context1\nContext2\n") self.assertEqual(self.output, "Context1\nContext2\n")
self.assertFalse(self.errors) self.assertFalse(self.errors)
def test_contexts2(self): def test_contexts2(self):
...@@ -34,7 +35,7 @@ class ListContextCommandTest(CommandTest): ...@@ -34,7 +35,7 @@ class ListContextCommandTest(CommandTest):
command = ListContextCommand(["aaa"], todolist, self.out, self.error) command = ListContextCommand(["aaa"], todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output,"Context1\nContext2\n") self.assertEqual(self.output, "Context1\nContext2\n")
self.assertFalse(self.errors) self.assertFalse(self.errors)
def test_help(self): def test_help(self):
...@@ -42,7 +43,8 @@ class ListContextCommandTest(CommandTest): ...@@ -42,7 +43,8 @@ class ListContextCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.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. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,9 +16,10 @@ ...@@ -16,9 +16,10 @@
import unittest import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListProjectCommand import ListProjectCommand from topydo.commands.ListProjectCommand import ListProjectCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
class ListProjectCommandTest(CommandTest): class ListProjectCommandTest(CommandTest):
def test_projects1(self): def test_projects1(self):
...@@ -42,7 +43,8 @@ class ListProjectCommandTest(CommandTest): ...@@ -42,7 +43,8 @@ class ListProjectCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,14 +14,14 @@ ...@@ -14,14 +14,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date, timedelta
import unittest import unittest
from six import u from datetime import date, timedelta
from test.command_testcase import CommandTest
from topydo.commands.PostponeCommand import PostponeCommand from topydo.commands.PostponeCommand import PostponeCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
class PostponeCommandTest(CommandTest): class PostponeCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(PostponeCommandTest, self).setUp() super(PostponeCommandTest, self).setUp()
...@@ -42,38 +42,45 @@ class PostponeCommandTest(CommandTest): ...@@ -42,38 +42,45 @@ class PostponeCommandTest(CommandTest):
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
def test_postpone1(self): def test_postpone01(self):
command = PostponeCommand(["1", "1w"], self.todolist, self.out, self.error) command = PostponeCommand(["1", "1w"], self.todolist, self.out,
self.error)
command.execute() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_postpone2(self): def test_postpone02(self):
command = PostponeCommand(["2", "1w"], self.todolist, self.out, self.error) command = PostponeCommand(["2", "1w"], self.todolist, self.out,
self.error)
command.execute() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_postpone3(self): def test_postpone03(self):
command = PostponeCommand(["-s", "2", "1w"], self.todolist, self.out, self.error) command = PostponeCommand(["-s", "2", "1w"], self.todolist, self.out,
self.error)
command.execute() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_postpone4(self): def test_postpone04(self):
command = PostponeCommand(["3", "1w"], self.todolist, self.out, self.error) command = PostponeCommand(["3", "1w"], self.todolist, self.out,
self.error)
command.execute() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
...@@ -82,8 +89,9 @@ class PostponeCommandTest(CommandTest): ...@@ -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.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), self.start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_postpone5(self): def test_postpone05(self):
command = PostponeCommand(["-s", "3", "1w"], self.todolist, self.out, self.error) command = PostponeCommand(["-s", "3", "1w"], self.todolist, self.out,
self.error)
command.execute() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
...@@ -94,18 +102,21 @@ class PostponeCommandTest(CommandTest): ...@@ -94,18 +102,21 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), start.isoformat())) self.assertEqual(self.output, "| 3| Baz due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_postpone6(self): def test_postpone06(self):
command = PostponeCommand(["4", "1w"], self.todolist, self.out, self.error) command = PostponeCommand(["4", "1w"], self.todolist, self.out,
self.error)
command.execute() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_postpone7(self): def test_postpone07(self):
command = PostponeCommand(["5", "1w"], self.todolist, self.out, self.error) command = PostponeCommand(["5", "1w"], self.todolist, self.out,
self.error)
command.execute() command.execute()
due = self.future + timedelta(7) due = self.future + timedelta(7)
...@@ -115,8 +126,9 @@ class PostponeCommandTest(CommandTest): ...@@ -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.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), self.future_start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_postpone8(self): def test_postpone08(self):
command = PostponeCommand(["-s", "5", "1w"], self.todolist, self.out, self.error) command = PostponeCommand(["-s", "5", "1w"], self.todolist, self.out,
self.error)
command.execute() command.execute()
due = self.future + timedelta(7) due = self.future + timedelta(7)
...@@ -127,8 +139,9 @@ class PostponeCommandTest(CommandTest): ...@@ -127,8 +139,9 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), start.isoformat())) self.assertEqual(self.output, "| 5| Future due:{} t:{}\n".format(due.isoformat(), start.isoformat()))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_postpone9(self): def test_postpone09(self):
command = PostponeCommand(["1", "foo"], self.todolist, self.out, self.error) command = PostponeCommand(["1", "foo"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -136,7 +149,8 @@ class PostponeCommandTest(CommandTest): ...@@ -136,7 +149,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid date pattern given.\n") self.assertEqual(self.errors, "Invalid date pattern given.\n")
def test_postpone10(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -144,7 +158,8 @@ class PostponeCommandTest(CommandTest): ...@@ -144,7 +158,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_postpone11(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -160,17 +175,20 @@ class PostponeCommandTest(CommandTest): ...@@ -160,17 +175,20 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_postpone13(self): 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() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_postpone14(self): 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() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
...@@ -180,7 +198,8 @@ class PostponeCommandTest(CommandTest): ...@@ -180,7 +198,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_postpone15(self): 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() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
...@@ -190,7 +209,8 @@ class PostponeCommandTest(CommandTest): ...@@ -190,7 +209,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_postpone16(self): 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() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
...@@ -202,7 +222,8 @@ class PostponeCommandTest(CommandTest): ...@@ -202,7 +222,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_postpone17(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -210,7 +231,8 @@ class PostponeCommandTest(CommandTest): ...@@ -210,7 +231,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid date pattern given.\n") self.assertEqual(self.errors, "Invalid date pattern given.\n")
def test_postpone18(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -218,7 +240,8 @@ class PostponeCommandTest(CommandTest): ...@@ -218,7 +240,8 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 123.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 123.\n")
def test_postpone19(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -227,15 +250,18 @@ class PostponeCommandTest(CommandTest): ...@@ -227,15 +250,18 @@ class PostponeCommandTest(CommandTest):
def test_postpone20(self): def test_postpone20(self):
""" Throw an error with invalid argument containing special characters. """ """ 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
def test_expr_postpone1(self): 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() command.execute()
due = self.today + timedelta(14) due = self.today + timedelta(14)
...@@ -247,33 +273,38 @@ class PostponeCommandTest(CommandTest): ...@@ -247,33 +273,38 @@ class PostponeCommandTest(CommandTest):
def test_expr_postpone2(self): def test_expr_postpone2(self):
cmd_args = ["-e", "t:{}".format(self.start.isoformat()), "due:tod", "1w"] 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() command.execute()
due = self.today + timedelta(7) 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.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.output, result) self.assertEqual(self.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_postpone3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_postpone4(self): def test_expr_postpone4(self):
""" Don't postpone unrelevant todo items. """ """ 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_postpone5(self): def test_expr_postpone5(self):
""" Force postponing unrelevant items with additional -x flag. """ """ Force postponing unrelevant items with additional -x flag. """
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() command.execute()
due = self.today + timedelta(7) due = self.today + timedelta(7)
...@@ -284,12 +315,13 @@ class PostponeCommandTest(CommandTest): ...@@ -284,12 +315,13 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_help(self): def test_help(self):
command = PostponeCommand(["help"], self.todolist, self.out, self.error) command = PostponeCommand(["help"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -15,12 +15,12 @@ ...@@ -15,12 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest import unittest
from six import u
from test.command_testcase import CommandTest
from topydo.commands.PriorityCommand import PriorityCommand from topydo.commands.PriorityCommand import PriorityCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
class PriorityCommandTest(CommandTest): class PriorityCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(PriorityCommandTest, self).setUp() super(PriorityCommandTest, self).setUp()
...@@ -35,15 +35,18 @@ class PriorityCommandTest(CommandTest): ...@@ -35,15 +35,18 @@ class PriorityCommandTest(CommandTest):
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
def test_set_prio1(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_set_prio2(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -51,15 +54,18 @@ class PriorityCommandTest(CommandTest): ...@@ -51,15 +54,18 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_prio3(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_set_prio4(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -67,7 +73,8 @@ class PriorityCommandTest(CommandTest): ...@@ -67,7 +73,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_prio5(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -76,17 +83,28 @@ class PriorityCommandTest(CommandTest): ...@@ -76,17 +83,28 @@ class PriorityCommandTest(CommandTest):
def test_set_prio6(self): def test_set_prio6(self):
""" Allow priority to be set including parentheses. """ """ 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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.output, "Priority changed from A to C\n| 1| (C) Foo\nPriority set to C.\n| 2| (C) Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_prio1(self): def test_set_prio7(self):
command = PriorityCommand(["-e", "@test", "C"], self.todolist, self.out, self.error, None) """ Allow lowercase priority to be set. """
command = PriorityCommand(["Foo", "2", "c"], self.todolist, self.out,
self.error)
command.execute() 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" 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): ...@@ -95,7 +113,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_prio2(self): 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() command.execute()
result = "Priority changed from B to C\n| 3| (C) a @test with due:2015-06-03\n" result = "Priority changed from B to C\n| 3| (C) a @test with due:2015-06-03\n"
...@@ -105,29 +124,35 @@ class PriorityCommandTest(CommandTest): ...@@ -105,29 +124,35 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_expr_prio3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_prio4(self): def test_expr_prio4(self):
""" Don't prioritize unrelevant todo items. """ """ 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_expr_prio5(self): def test_expr_prio5(self):
""" Force prioritizing unrelevant items with additional -x flag. """ """ Force prioritizing unrelevant items with additional -x flag. """
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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_invalid1(self): def test_invalid1(self):
command = PriorityCommand(["99", "A"], self.todolist, self.out, self.error) command = PriorityCommand(["99", "A"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -135,7 +160,8 @@ class PriorityCommandTest(CommandTest): ...@@ -135,7 +160,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given.\n") self.assertEqual(self.errors, "Invalid todo number given.\n")
def test_invalid2(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -143,7 +169,8 @@ class PriorityCommandTest(CommandTest): ...@@ -143,7 +169,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\n") self.assertEqual(self.errors, "Invalid todo number given: 99.\n")
def test_invalid3(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -151,7 +178,8 @@ class PriorityCommandTest(CommandTest): ...@@ -151,7 +178,8 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 98.\nInvalid todo number given: 99.\n") self.assertEqual(self.errors, "Invalid todo number given: 98.\nInvalid todo number given: 99.\n")
def test_invalid4(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -175,20 +203,25 @@ class PriorityCommandTest(CommandTest): ...@@ -175,20 +203,25 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_invalid7(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n")) self.assertEqual(self.errors,
u"Invalid todo number given: Fo\u00d3B\u0105r.\n")
def test_invalid8(self): def test_invalid8(self):
""" """
Test that there's only one capital surrounded by non-word Test that there's only one capital surrounded by non-word
characters that makes up a priority. 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -200,12 +233,14 @@ class PriorityCommandTest(CommandTest): ...@@ -200,12 +233,14 @@ class PriorityCommandTest(CommandTest):
Test that there's only one capital surrounded by non-word Test that there's only one capital surrounded by non-word
characters that makes up a priority. 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid priority given.\n") self.assertEqual(self.errors, "Invalid priority given.\n")
def test_empty(self): def test_empty(self):
command = PriorityCommand([], self.todolist, self.out, self.error) command = PriorityCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
...@@ -215,11 +250,13 @@ class PriorityCommandTest(CommandTest): ...@@ -215,11 +250,13 @@ class PriorityCommandTest(CommandTest):
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_help(self): def test_help(self):
command = PriorityCommand(["help"], self.todolist, self.out, self.error) command = PriorityCommand(["help"], self.todolist, self.out,
self.error)
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,13 +14,14 @@ ...@@ -14,13 +14,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date, timedelta
import unittest import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config 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 topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
class RecurrenceTest(TopydoTest): class RecurrenceTest(TopydoTest):
def setUp(self): def setUp(self):
...@@ -164,12 +165,14 @@ class RecurrenceTest(TopydoTest): ...@@ -164,12 +165,14 @@ class RecurrenceTest(TopydoTest):
def test_no_recurrence(self): def test_no_recurrence(self):
self.todo.remove_tag('rec') 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): def test_invalid_recurrence(self):
""" Throw exception when 'rec' tag has an invalid value. """ """ Throw exception when 'rec' tag has an invalid value. """
self.todo.set_tag('rec', '1') 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,21 +14,23 @@ ...@@ -14,21 +14,23 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date, timedelta
import unittest 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 topydo.lib.RelativeDate import relative_date_to_date
from test.TopydoTest import TopydoTest
@freeze_time('2015, 11, 06')
class RelativeDateTester(TopydoTest): class RelativeDateTester(TopydoTest):
def setUp(self): def setUp(self):
super(RelativeDateTester, self).setUp() super(RelativeDateTester, self).setUp()
self.today = date.today() self.yesterday = date(2015, 11, 5)
self.tomorrow = self.today + timedelta(1) self.today = date(2015, 11, 6)
self.tomorrow = date(2015, 11, 7)
self.monday = self.today self.monday = date(2015, 11, 9)
if self.monday.weekday() != 0: self.friday = date(2015, 11, 13)
self.monday += timedelta(7 - self.today.weekday() % 7)
def test_zero_days(self): def test_zero_days(self):
result = relative_date_to_date('0d') result = relative_date_to_date('0d')
...@@ -40,7 +42,7 @@ class RelativeDateTester(TopydoTest): ...@@ -40,7 +42,7 @@ class RelativeDateTester(TopydoTest):
def test_one_week(self): def test_one_week(self):
result = relative_date_to_date('1w') 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): def test_one_month(self):
test_date = date(2015, 1, 10) test_date = date(2015, 1, 10)
...@@ -103,8 +105,7 @@ class RelativeDateTester(TopydoTest): ...@@ -103,8 +105,7 @@ class RelativeDateTester(TopydoTest):
self.assertEqual(result, self.today) self.assertEqual(result, self.today)
def test_today3(self): def test_today3(self):
result = relative_date_to_date('today', \ result = relative_date_to_date('today', self.tomorrow)
date.today() + timedelta(1))
self.assertEqual(result, self.today) self.assertEqual(result, self.today)
def test_tomorrow1(self): def test_tomorrow1(self):
...@@ -115,6 +116,14 @@ class RelativeDateTester(TopydoTest): ...@@ -115,6 +116,14 @@ class RelativeDateTester(TopydoTest):
result = relative_date_to_date('tom') result = relative_date_to_date('tom')
self.assertEqual(result, self.tomorrow) 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): def test_monday1(self):
result = relative_date_to_date('monday') result = relative_date_to_date('monday')
self.assertEqual(result, self.monday) self.assertEqual(result, self.monday)
...@@ -133,15 +142,23 @@ class RelativeDateTester(TopydoTest): ...@@ -133,15 +142,23 @@ class RelativeDateTester(TopydoTest):
def test_offset1(self): def test_offset1(self):
result = relative_date_to_date('1d', self.tomorrow) 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): def test_negative_period1(self):
result = relative_date_to_date('-1d') 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): def test_negative_period2(self):
result = relative_date_to_date('-0d') result = relative_date_to_date('-0d')
self.assertTrue(result, self.today) 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__': if __name__ == '__main__':
unittest.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. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
import unittest 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 topydo.commands.SortCommand import SortCommand
from test.CommandTest import CommandTest from topydo.lib.Config import config
from test.TestFacilities import load_file_to_todolist
class SortCommandTest(CommandTest): class SortCommandTest(CommandTest):
def setUp(self): def setUp(self):
...@@ -27,17 +28,19 @@ class SortCommandTest(CommandTest): ...@@ -27,17 +28,19 @@ class SortCommandTest(CommandTest):
self.todolist = load_file_to_todolist("test/data/SorterTest1.txt") self.todolist = load_file_to_todolist("test/data/SorterTest1.txt")
def test_sort1(self): def test_sort1(self):
""" Alphabetically sorted """ """ Alphabetically sorted. """
command = SortCommand(["text"], self.todolist, self.out, self.error) command = SortCommand(["text"], self.todolist, self.out, self.error)
command.execute() 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): def test_sort2(self):
command = SortCommand([], self.todolist, self.out, self.error) command = SortCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(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): def test_sort3(self):
""" Check that order does not influence the UID of a todo. """ """ Check that order does not influence the UID of a todo. """
...@@ -55,7 +58,8 @@ class SortCommandTest(CommandTest): ...@@ -55,7 +58,8 @@ class SortCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,11 +16,12 @@ ...@@ -16,11 +16,12 @@
import unittest 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.Config import config
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from test.TestFacilities import load_file, todolist_to_string, load_file_to_todolist, print_view
from test.TopydoTest import TopydoTest
class SorterTest(TopydoTest): class SorterTest(TopydoTest):
def sort_file(self, p_filename, p_filename_ref, p_sorter): def sort_file(self, p_filename, p_filename_ref, p_sorter):
...@@ -36,80 +37,93 @@ class SorterTest(TopydoTest): ...@@ -36,80 +37,93 @@ class SorterTest(TopydoTest):
self.assertEqual(todos_sorted, todos_ref) self.assertEqual(todos_sorted, todos_ref)
self.assertEqual(todolist_to_string(todos), text_before) self.assertEqual(todolist_to_string(todos), text_before)
def test_sort1(self): def test_sort01(self):
""" Alphabetically sorted """ """ Alphabetically sorted. """
sorter = Sorter('text') 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. Ascendingly sorted by priority. Also checks stableness of the sort.
""" """
sorter = Sorter('prio') 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. Ascendingly sorted by priority. Also checks stableness of the sort.
""" """
sorter = Sorter('asc:prio') 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 Descendingly sorted by priority. Also checks stableness of the
sort. sort.
""" """
sorter = Sorter('desc:prio') 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): def test_sort04(self):
""" Ascendingly sorted by due date """ """ Ascendingly sorted by due date. """
sorter = Sorter(config().tag_due()) 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): def test_sort05(self):
""" Descendingly sorted by due date """ """ Descendingly sorted by due date. """
sorter = Sorter('desc:due') 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): def test_sort06(self):
""" Ascendingly sorted by creation date """ """ Ascendingly sorted by creation date. """
sorter = Sorter('creation') 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. """ """ Ascendingly sorted by completion date. """
sorter = Sorter('completion') 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): def test_sort08(self):
""" Descendingly sorted by importance """ """ Descendingly sorted by importance. """
sorter = Sorter('desc: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 Sort on multiple levels: first descending importance, then
ascending priority. ascending priority.
""" """
sorter = Sorter('desc:importance,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): def test_sort10(self):
""" Deal with garbage input. """ """ Deal with garbage input. """
sorter = Sorter('') 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): def test_sort11(self):
""" Deal with garbage input. """ """ Deal with garbage input. """
sorter = Sorter('fnord') 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): def test_sort12(self):
""" Deal with garbage input. """ """ Deal with garbage input. """
sorter = Sorter('desc:importance,,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_sort13(self): def test_sort13(self):
""" """
...@@ -119,7 +133,8 @@ class SorterTest(TopydoTest): ...@@ -119,7 +133,8 @@ class SorterTest(TopydoTest):
dependencies the average importance should be equal. dependencies the average importance should be equal.
""" """
sorter = Sorter('desc:importance-avg') 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): def test_sort14(self):
sorter = Sorter('desc:importance-average') sorter = Sorter('desc:importance-average')
...@@ -155,5 +170,24 @@ class SorterTest(TopydoTest): ...@@ -155,5 +170,24 @@ class SorterTest(TopydoTest):
self.assertEqual(print_view(view), todolist_to_string(result)) 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,12 +14,14 @@ ...@@ -14,12 +14,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from freezegun import freeze_time
import unittest import unittest
from test.command_testcase import CommandTest
from topydo.commands.TagCommand import TagCommand from topydo.commands.TagCommand import TagCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
class TagCommandTest(CommandTest): class TagCommandTest(CommandTest):
def setUp(self): def setUp(self):
super(TagCommandTest, self).setUp() super(TagCommandTest, self).setUp()
...@@ -33,7 +35,8 @@ class TagCommandTest(CommandTest): ...@@ -33,7 +35,8 @@ class TagCommandTest(CommandTest):
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
def test_add_tag1(self): 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() command.execute()
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22") self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22")
...@@ -42,7 +45,8 @@ class TagCommandTest(CommandTest): ...@@ -42,7 +45,8 @@ class TagCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
def test_add_tag2(self): 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() command.execute()
self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22") self.assertEqual(self.todolist.todo(1).source(), "Foo due:2014-10-22")
...@@ -51,64 +55,74 @@ class TagCommandTest(CommandTest): ...@@ -51,64 +55,74 @@ class TagCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
def test_add_tag3(self): 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() command.execute()
self.assertEqual(self.todolist.todo(2).source(), "Bar due:2014-10-22 due:2014-10-19") self.assertEqual(self.todolist.todo(2).source(),
self.assertEqual(self.output, "| 2| Bar due:2014-10-22 due:2014-10-19\n") "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.assertEqual(self.errors, "")
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
def test_add_tag4(self): 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() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number.\n") self.assertEqual(self.errors, "Invalid todo number.\n")
def test_set_tag4(self): def test_set_tag04(self):
command = TagCommand(["3", "due", "2014-10-20"], self.todolist, self.out, self.error) command = TagCommand(["3", "due", "2014-10-20"], self.todolist,
self.out, self.error)
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "| 3| Baz due:2014-10-20\n") self.assertEqual(self.output, "| 3| Baz due:2014-10-20\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_tag5(self): def test_set_tag05(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "all") command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "all")
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_tag6(self): def test_set_tag06(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "1") command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "1")
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_tag7(self): def test_set_tag07(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "2") command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "2")
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-20\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_tag8(self): def test_set_tag08(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "") command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "")
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_tag9(self): def test_set_tag09(self):
command = TagCommand(["4", "due", "2014-10-20"], self.todolist, self.out, self.error, lambda t: "99") command = TagCommand(["4", "due", "2014-10-20"], self.todolist,
self.out, self.error, lambda t: "99")
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
...@@ -116,14 +130,39 @@ class TagCommandTest(CommandTest): ...@@ -116,14 +130,39 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_set_tag10(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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, "") 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 = TagCommand(["1", "due"], self.todolist, self.out, self.error)
command.execute() command.execute()
...@@ -131,7 +170,7 @@ class TagCommandTest(CommandTest): ...@@ -131,7 +170,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output, "| 1| Foo\n") self.assertEqual(self.output, "| 1| Foo\n")
self.assertEqual(self.errors, "") 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 = TagCommand(["2", "due"], self.todolist, self.out, self.error)
command.execute() command.execute()
...@@ -139,39 +178,44 @@ class TagCommandTest(CommandTest): ...@@ -139,39 +178,44 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output, "| 2| Bar\n") self.assertEqual(self.output, "| 2| Bar\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_rm_tag3(self): def test_rm_tag03(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error, lambda t: "all") command = TagCommand(["4", "due"], self.todolist, self.out,
self.error, lambda t: "all")
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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, "") self.assertEqual(self.errors, "")
def test_rm_tag4(self): def test_rm_tag04(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error, lambda t: "1") command = TagCommand(["4", "due"], self.todolist, self.out, self.error,
lambda t: "1")
command.execute() command.execute()
self.assertTrue(self.todolist.is_dirty()) 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.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-22\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_rm_tag6(self): def test_rm_tag06(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error, lambda t: "99") command = TagCommand(["4", "due"], self.todolist, self.out, self.error,
lambda t: "99")
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_rm_tag7(self): def test_rm_tag07(self):
command = TagCommand(["4", "due"], self.todolist, self.out, self.error, lambda t: "A") command = TagCommand(["4", "due"], self.todolist, self.out, self.error,
lambda t: "A")
command.execute() command.execute()
self.assertFalse(self.todolist.is_dirty()) 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.output, " 1. 2014-10-20\n 2. 2014-10-22\n| 4| Fnord due:2014-10-20 due:2014-10-22\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_rm_tag8(self): def test_rm_tag08(self):
command = TagCommand(["5", "due"], self.todolist, self.out, self.error) command = TagCommand(["5", "due"], self.todolist, self.out, self.error)
command.execute() command.execute()
...@@ -179,7 +223,7 @@ class TagCommandTest(CommandTest): ...@@ -179,7 +223,7 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number.\n") 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 = TagCommand(["A", "due"], self.todolist, self.out, self.error)
command.execute() command.execute()
...@@ -188,7 +232,8 @@ class TagCommandTest(CommandTest): ...@@ -188,7 +232,8 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number.\n") self.assertEqual(self.errors, "Invalid todo number.\n")
def test_rm_tag10(self): 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() command.execute()
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -208,7 +253,8 @@ class TagCommandTest(CommandTest): ...@@ -208,7 +253,8 @@ class TagCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.output, "") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,20 +14,23 @@ ...@@ -14,20 +14,23 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import date, timedelta
import unittest import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from test.TopydoTest import TopydoTest
def today_date(): def today_date():
today = date.today() today = date.today()
return today.isoformat() return today.isoformat()
def tomorrow_date(): def tomorrow_date():
tomorrow = date.today() + timedelta(days=1) tomorrow = date.today() + timedelta(days=1)
return tomorrow.isoformat() return tomorrow.isoformat()
class TodoTest(TopydoTest): class TodoTest(TopydoTest):
def test_due_date1(self): def test_due_date1(self):
todo = Todo("(C) Foo due:2014-06-09") todo = Todo("(C) Foo due:2014-06-09")
...@@ -37,7 +40,7 @@ class TodoTest(TopydoTest): ...@@ -37,7 +40,7 @@ class TodoTest(TopydoTest):
def test_false_date(self): def test_false_date(self):
todo = Todo("(C) Foo due:2014-04-31") todo = Todo("(C) Foo due:2014-04-31")
self.assertEqual( todo.due_date(), None ) self.assertEqual(todo.due_date(), None)
def test_active1(self): def test_active1(self):
todo = Todo("(C) Foo due:2014-01-01") todo = Todo("(C) Foo due:2014-01-01")
...@@ -87,5 +90,29 @@ class TodoTest(TopydoTest): ...@@ -87,5 +90,29 @@ class TodoTest(TopydoTest):
todo = Todo("(C) Foo t:2014-01-01 due:2014-01-02") todo = Todo("(C) Foo t:2014-01-01 due:2014-01-02")
self.assertEqual(todo.length(), 1) 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,12 +16,13 @@ ...@@ -16,12 +16,13 @@
""" Tests for the TodoBase class. """ """ Tests for the TodoBase class. """
from datetime import date, timedelta
import re import re
import unittest import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.TodoBase import TodoBase from topydo.lib.TodoBase import TodoBase
from test.TopydoTest import TopydoTest
class TodoBaseTester(TopydoTest): class TodoBaseTester(TopydoTest):
def test_parse_tag(self): def test_parse_tag(self):
...@@ -85,7 +86,7 @@ class TodoBaseTester(TopydoTest): ...@@ -85,7 +86,7 @@ class TodoBaseTester(TopydoTest):
def test_set_tag_double_value(self): def test_set_tag_double_value(self):
todo = TodoBase("(C) Foo foo:bar baz:bar") 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.has_tag('foo'))
self.assertTrue(todo.tag_value('foo'), 'blah') self.assertTrue(todo.tag_value('foo'), 'blah')
...@@ -97,7 +98,8 @@ class TodoBaseTester(TopydoTest): ...@@ -97,7 +98,8 @@ class TodoBaseTester(TopydoTest):
todo.set_tag('foo', 'blah') todo.set_tag('foo', 'blah')
self.assertTrue(todo.has_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): def test_set_tag_empty_value(self):
todo = TodoBase("(C) Foo foo:bar foo:baz") todo = TodoBase("(C) Foo foo:bar foo:baz")
...@@ -108,14 +110,12 @@ class TodoBaseTester(TopydoTest): ...@@ -108,14 +110,12 @@ class TodoBaseTester(TopydoTest):
def test_tag_empty_value(self): def test_tag_empty_value(self):
""" Tag should not be recorded when there is no value. """ """ Tag should not be recorded when there is no value. """
todo = TodoBase("(C) Foo foo:") todo = TodoBase("(C) Foo foo:")
self.assertFalse(todo.has_tag('foo')) self.assertFalse(todo.has_tag('foo'))
def test_tag_empty_key(self): def test_tag_empty_key(self):
""" Tag should not be recorded when there is no key. """ """ Tag should not be recorded when there is no key. """
todo = TodoBase("(C) Foo :bar") todo = TodoBase("(C) Foo :bar")
self.assertFalse(todo.has_tag('')) self.assertFalse(todo.has_tag(''))
...@@ -259,8 +259,8 @@ class TodoBaseTester(TopydoTest): ...@@ -259,8 +259,8 @@ class TodoBaseTester(TopydoTest):
today_str = today.isoformat() today_str = today.isoformat()
self.assertEqual(todo.fields['completionDate'], today) self.assertEqual(todo.fields['completionDate'], today)
self.assertTrue(re.match('^x ' + today_str + ' 2014-06-12 Foo', \ self.assertTrue(re.match('^x ' + today_str + ' 2014-06-12 Foo',
todo.src)) todo.src))
def test_set_complete3(self): def test_set_complete3(self):
todo = TodoBase("Foo") todo = TodoBase("Foo")
...@@ -280,7 +280,8 @@ class TodoBaseTester(TopydoTest): ...@@ -280,7 +280,8 @@ class TodoBaseTester(TopydoTest):
today_str = today.isoformat() today_str = today.isoformat()
self.assertEqual(todo.fields['completionDate'], today) 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): def test_set_complete5(self):
todo = TodoBase("x 2014-06-13 Foo") todo = TodoBase("x 2014-06-13 Foo")
...@@ -302,7 +303,7 @@ class TodoBaseTester(TopydoTest): ...@@ -302,7 +303,7 @@ class TodoBaseTester(TopydoTest):
todo.set_source_text(new_text) todo.set_source_text(new_text)
self.assertEqual(todo.src, new_text) self.assertEqual(todo.src, new_text)
self.assertEqual(todo.priority(),'C') self.assertEqual(todo.priority(), 'C')
def test_set_creation_date1(self): def test_set_creation_date1(self):
todo = TodoBase("Foo") todo = TodoBase("Foo")
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,11 +14,11 @@ ...@@ -14,11 +14,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from six import u
import unittest import unittest
from test.TestFacilities import load_file from test.facilities import load_file
from test.TopydoTest import TopydoTest from test.topydo_testcase import TopydoTest
class TodoFileTest(TopydoTest): class TodoFileTest(TopydoTest):
def test_empty_file(self): def test_empty_file(self):
...@@ -29,7 +29,8 @@ class TodoFileTest(TopydoTest): ...@@ -29,7 +29,8 @@ class TodoFileTest(TopydoTest):
def test_utf_8(self): def test_utf_8(self):
todofile = load_file('test/data/utf-8.txt') 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -19,32 +19,32 @@ ...@@ -19,32 +19,32 @@
import re import re
import unittest import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from topydo.lib.TodoFile import TodoFile from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from topydo.lib.TodoListBase import TodoListBase from topydo.lib.TodoListBase import InvalidTodoException, TodoListBase
from test.TopydoTest import TopydoTest
class TodoListTester(TopydoTest): class TodoListTester(TopydoTest):
def setUp(self): def setUp(self):
super(TodoListTester, self).setUp() super(TodoListTester, self).setUp()
self.todofile = TodoFile('test/data/TodoListTest.txt') self.todofile = TodoFile('test/data/TodoListTest.txt')
lines = [line for line in self.todofile.read() \ lines = [line for line in self.todofile.read()
if re.search(r'\S', line)] if re.search(r'\S', line)]
self.text = ''.join(lines) self.text = ''.join(lines)
self.todolist = TodoListBase(lines) self.todolist = TodoListBase(lines)
def test_contexts(self): def test_contexts(self):
self.assertEqual(set(['Context1', 'Context2']), \ self.assertEqual(set(['Context1', 'Context2']),
self.todolist.contexts()) self.todolist.contexts())
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_projects(self): def test_projects(self):
self.assertEqual(set(['Project1', 'Project2']), \ self.assertEqual(set(['Project1', 'Project2']),
self.todolist.projects()) self.todolist.projects())
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_add1(self): def test_add1(self):
...@@ -53,10 +53,10 @@ class TodoListTester(TopydoTest): ...@@ -53,10 +53,10 @@ class TodoListTester(TopydoTest):
todo = self.todolist.add(text) todo = self.todolist.add(text)
self.assertEqual(self.todolist.todo(count+1).source(), text) self.assertEqual(self.todolist.todo(count+1).source(), text)
self.assertEqual(set(['Project1', 'Project2', 'Project3']), \ self.assertEqual(set(['Project1', 'Project2', 'Project3']),
self.todolist.projects()) self.todolist.projects())
self.assertEqual(set(['Context1', 'Context2', 'Context3']), \ self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts()) self.todolist.contexts())
self.assertEqual(self.todolist.number(todo), 6) self.assertEqual(self.todolist.number(todo), 6)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
...@@ -70,7 +70,8 @@ class TodoListTester(TopydoTest): ...@@ -70,7 +70,8 @@ class TodoListTester(TopydoTest):
self.todolist.add('\n(C) New task') self.todolist.add('\n(C) New task')
self.assertEqual(self.todolist.count(), count + 1) 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') self.assertEqual(self.todolist.todo(count + 1).priority(), 'C')
def test_add3b(self): def test_add3b(self):
...@@ -78,7 +79,8 @@ class TodoListTester(TopydoTest): ...@@ -78,7 +79,8 @@ class TodoListTester(TopydoTest):
self.todolist.add('(C) New task\n') self.todolist.add('(C) New task\n')
self.assertEqual(self.todolist.count(), count + 1) 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') self.assertEqual(self.todolist.todo(count + 1).priority(), 'C')
def test_add4(self): def test_add4(self):
...@@ -96,8 +98,8 @@ class TodoListTester(TopydoTest): ...@@ -96,8 +98,8 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo(2) todo = self.todolist.todo(2)
self.todolist.delete(todo) self.todolist.delete(todo)
self.assertEqual(self.todolist.todo(2).source(), \ self.assertEqual(self.todolist.todo(2).source(),
"(C) Baz @Context1 +Project1 key:value") "(C) Baz @Context1 +Project1 key:value")
self.assertEqual(self.todolist.count(), count - 1) self.assertEqual(self.todolist.count(), count - 1)
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertRaises(InvalidTodoException, self.todolist.number, todo) self.assertRaises(InvalidTodoException, self.todolist.number, todo)
...@@ -116,10 +118,10 @@ class TodoListTester(TopydoTest): ...@@ -116,10 +118,10 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo(3) todo = self.todolist.todo(3)
self.todolist.append(todo, "@Context3") self.todolist.append(todo, "@Context3")
self.assertEqual(todo.source(), \ self.assertEqual(todo.source(),
"(C) Baz @Context1 +Project1 key:value @Context3") "(C) Baz @Context1 +Project1 key:value @Context3")
self.assertEqual(set(['Context1', 'Context2', 'Context3']), \ self.assertEqual(set(['Context1', 'Context2', 'Context3']),
self.todolist.contexts()) self.todolist.contexts())
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
def test_append2(self): def test_append2(self):
...@@ -128,8 +130,8 @@ class TodoListTester(TopydoTest): ...@@ -128,8 +130,8 @@ class TodoListTester(TopydoTest):
self.todolist.append(todo, "foo:bar") self.todolist.append(todo, "foo:bar")
self.assertEqual(todo.text(), text) self.assertEqual(todo.text(), text)
self.assertEqual(todo.source(), \ self.assertEqual(todo.source(),
"(C) Baz @Context1 +Project1 key:value foo:bar") "(C) Baz @Context1 +Project1 key:value foo:bar")
def test_append3(self): def test_append3(self):
todo = self.todolist.todo(3) todo = self.todolist.todo(3)
...@@ -141,7 +143,8 @@ class TodoListTester(TopydoTest): ...@@ -141,7 +143,8 @@ class TodoListTester(TopydoTest):
def test_todo(self): def test_todo(self):
count = self.todolist.count() 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()) self.assertFalse(self.todolist.is_dirty())
def test_count(self): def test_count(self):
...@@ -197,7 +200,8 @@ class TodoListTester(TopydoTest): ...@@ -197,7 +200,8 @@ class TodoListTester(TopydoTest):
def test_uid1(self): def test_uid1(self):
config("test/data/todolist-uid.conf") 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): def test_uid2(self):
""" Changing the priority should not change the identifier. """ """ Changing the priority should not change the identifier. """
...@@ -205,25 +209,25 @@ class TodoListTester(TopydoTest): ...@@ -205,25 +209,25 @@ class TodoListTester(TopydoTest):
todo = self.todolist.todo('t5c') todo = self.todolist.todo('t5c')
self.todolist.set_priority(todo, 'B') 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): def test_uid3(self):
""" """
Must be able to handle integers when text identifiers are enabled. Must be able to handle integers when text identifiers are enabled.
""" """
config("test/data/todolist-uid.conf") config("test/data/todolist-uid.conf")
self.assertRaises(InvalidTodoException, self.todolist.todo, 1) self.assertRaises(InvalidTodoException, self.todolist.todo, 1)
def test_new_uid(self): def test_new_uid(self):
""" Make sure that item has new text ID after append. """ """ Make sure that item has new text ID after append. """
config("test/data/todolist-uid.conf") config("test/data/todolist-uid.conf")
todo = self.todolist.todo('t5c') todo = self.todolist.todo('t5c')
self.todolist.append(todo, "A") self.todolist.append(todo, "A")
self.assertNotEqual(self.todolist.number(todo), 't5c') self.assertNotEqual(self.todolist.number(todo), 't5c')
class TodoListDependencyTester(TopydoTest): class TodoListDependencyTester(TopydoTest):
def setUp(self): def setUp(self):
super(TodoListDependencyTester, self).setUp() super(TodoListDependencyTester, self).setUp()
...@@ -241,24 +245,24 @@ class TodoListDependencyTester(TopydoTest): ...@@ -241,24 +245,24 @@ class TodoListDependencyTester(TopydoTest):
def test_check_dep(self): def test_check_dep(self):
children = self.todolist.children(self.todolist.todo(1)) children = self.todolist.children(self.todolist.todo(1))
self.assertEqual(sorted([todo.source() for todo in children]), \ self.assertEqual(sorted([todo.source() for todo in children]),
sorted(['Bar p:1', 'Baz p:1 id:2', 'Buzz p:2'])) sorted(['Bar p:1', 'Baz p:1 id:2', 'Buzz p:2']))
children = self.todolist.children(self.todolist.todo(1), True) children = self.todolist.children(self.todolist.todo(1), True)
self.assertEqual(sorted([todo.source() for todo in children]), \ self.assertEqual(sorted([todo.source() for todo in children]),
sorted(['Bar p:1', 'Baz p:1 id:2'])) sorted(['Bar p:1', 'Baz p:1 id:2']))
children = self.todolist.children(self.todolist.todo(3)) children = self.todolist.children(self.todolist.todo(3))
self.assertEqual(sorted([todo.source() for todo in children]), \ self.assertEqual(sorted([todo.source() for todo in children]),
['Buzz p:2']) ['Buzz p:2'])
parents = self.todolist.parents(self.todolist.todo(4)) parents = self.todolist.parents(self.todolist.todo(4))
self.assertEqual(sorted([todo.source() for todo in parents]), \ self.assertEqual(sorted([todo.source() for todo in parents]),
sorted(['Foo id:1', 'Baz p:1 id:2'])) sorted(['Foo id:1', 'Baz p:1 id:2']))
parents = self.todolist.parents(self.todolist.todo(4), True) parents = self.todolist.parents(self.todolist.todo(4), True)
self.assertEqual(sorted([todo.source() for todo in parents]), \ self.assertEqual(sorted([todo.source() for todo in parents]),
['Baz p:1 id:2']) ['Baz p:1 id:2'])
self.assertEqual(self.todolist.children(self.todolist.todo(2)), []) self.assertEqual(self.todolist.children(self.todolist.todo(2)), [])
self.assertEqual(self.todolist.parents(self.todolist.todo(1)), []) self.assertEqual(self.todolist.parents(self.todolist.todo(1)), [])
...@@ -276,7 +280,6 @@ class TodoListDependencyTester(TopydoTest): ...@@ -276,7 +280,6 @@ class TodoListDependencyTester(TopydoTest):
Make sure that previous add_dependency invocation stored the Make sure that previous add_dependency invocation stored the
edge_id properly. edge_id properly.
""" """
todo1 = self.todolist.todo(1) todo1 = self.todolist.todo(1)
todo4 = self.todolist.todo(4) todo4 = self.todolist.todo(4)
todo5 = self.todolist.todo(5) todo5 = self.todolist.todo(5)
...@@ -291,7 +294,6 @@ class TodoListDependencyTester(TopydoTest): ...@@ -291,7 +294,6 @@ class TodoListDependencyTester(TopydoTest):
""" """
Test that projects are not added double. Test that projects are not added double.
""" """
todo6 = self.todolist.todo(6) todo6 = self.todolist.todo(6)
todo7 = self.todolist.todo(7) todo7 = self.todolist.todo(7)
projects = todo7.projects().copy() projects = todo7.projects().copy()
...@@ -329,6 +331,15 @@ class TodoListDependencyTester(TopydoTest): ...@@ -329,6 +331,15 @@ class TodoListDependencyTester(TopydoTest):
self.assertEqual(str(self.todolist), old) 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): def test_remove_todo_check_children(self):
todo = self.todolist.todo(2) todo = self.todolist.todo(2)
self.todolist.delete(todo) self.todolist.delete(todo)
...@@ -361,16 +372,26 @@ class TodoListDependencyTester(TopydoTest): ...@@ -361,16 +372,26 @@ class TodoListDependencyTester(TopydoTest):
self.assertTrue(todolist.todo_by_dep_id('1')) self.assertTrue(todolist.todo_by_dep_id('1'))
self.assertFalse(todolist.todo_by_dep_id('2')) self.assertFalse(todolist.todo_by_dep_id('2'))
class TodoListCleanDependencyTester(TopydoTest): class TodoListCleanDependencyTester(TopydoTest):
"""
Tests for cleaning up the graph:
* Transitive reduction
* Remove obsolete id: tags
* Remove obsolete p: tags
"""
def setUp(self): def setUp(self):
super(TodoListCleanDependencyTester, self).setUp() super(TodoListCleanDependencyTester, self).setUp()
self.todolist = TodoList([]) 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("Bar p:1")
self.todolist.add("Baz p:1 id:2") self.todolist.add("Baz p:1 id:2")
self.todolist.add("Buzz p:2") self.todolist.add("Buzz p:2")
def test_clean_dependencies(self):
self.todolist.clean_dependencies() self.todolist.clean_dependencies()
self.assertFalse(self.todolist.todo(1).has_tag('p')) self.assertFalse(self.todolist.todo(1).has_tag('p'))
...@@ -378,5 +399,40 @@ class TodoListCleanDependencyTester(TopydoTest): ...@@ -378,5 +399,40 @@ class TodoListCleanDependencyTester(TopydoTest):
self.assertTrue(self.todolist.todo(2).has_tag('id', '2')) self.assertTrue(self.todolist.todo(2).has_tag('id', '2'))
self.assertTrue(self.todolist.todo(3).has_tag('p', '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__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,12 +16,13 @@ ...@@ -16,12 +16,13 @@
import unittest 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 import Filter
from topydo.lib.Sorter import Sorter 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.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from test.TopydoTest import TopydoTest
class ViewTest(TopydoTest): class ViewTest(TopydoTest):
def test_view(self): def test_view(self):
...@@ -38,4 +39,3 @@ class ViewTest(TopydoTest): ...@@ -38,4 +39,3 @@ class ViewTest(TopydoTest):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -18,10 +18,10 @@ import unittest ...@@ -18,10 +18,10 @@ import unittest
from topydo.lib.Config import config from topydo.lib.Config import config
class TopydoTest(unittest.TestCase): class TopydoTest(unittest.TestCase):
def tearDown(self): def tearDown(self):
""" """
Make sure that every test case leaves a clean configuration. Make sure that every test case leaves a clean configuration.
""" """
config("") config("")
...@@ -5,7 +5,9 @@ default_command = ls ...@@ -5,7 +5,9 @@ default_command = ls
; filename = todo.txt ; filename = todo.txt
; archive_filename = done.txt ; archive_filename = done.txt
colors = 1 colors = 1
identifiers = linenumber ; or: text ; identifiers can be 'linenumber' or 'text'
identifiers = linenumber
backup_count = 5
[add] [add]
auto_creation_date = 1 auto_creation_date = 1
...@@ -14,6 +16,7 @@ auto_creation_date = 1 ...@@ -14,6 +16,7 @@ auto_creation_date = 1
hide_tags = id,p,ical hide_tags = id,p,ical
indent = 0 indent = 0
list_limit = -1 list_limit = -1
list_format = |%I| %x %{(}p{)} %c %s %k %{due:}d %{t:}t
[tags] [tags]
tag_start = t tag_start = t
...@@ -45,3 +48,18 @@ append_parent_contexts = 0 ...@@ -45,3 +48,18 @@ append_parent_contexts = 0
; context_color = magenta ; context_color = magenta
; metadata_color = green ; metadata_color = green
; link_color = light-cyan ; 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 = { ...@@ -32,21 +32,14 @@ _SUBCOMMAND_MAP = {
'depri': 'DepriCommand', 'depri': 'DepriCommand',
'do': 'DoCommand', 'do': 'DoCommand',
'edit': 'EditCommand', 'edit': 'EditCommand',
'exit': 'ExitCommand', # used for the prompt 'exit': 'ExitCommand', # used for the prompt
'ls': 'ListCommand', 'ls': 'ListCommand',
'lscon': 'ListContextCommand', 'lscon': 'ListContextCommand',
'listcon': 'ListContextCommand',
'listcontext': 'ListContextCommand',
'listcontexts': 'ListContextCommand',
'lsprj': 'ListProjectCommand', 'lsprj': 'ListProjectCommand',
'lsproj': 'ListProjectCommand',
'listprj': 'ListProjectCommand',
'listproj': 'ListProjectCommand',
'listproject': 'ListProjectCommand',
'listprojects': 'ListProjectCommand',
'postpone': 'PostponeCommand', 'postpone': 'PostponeCommand',
'pri': 'PriorityCommand', 'pri': 'PriorityCommand',
'quit': 'ExitCommand', 'quit': 'ExitCommand',
'revert': 'RevertCommand',
'rm': 'DeleteCommand', 'rm': 'DeleteCommand',
'sort': 'SortCommand', 'sort': 'SortCommand',
'tag': 'TagCommand', 'tag': 'TagCommand',
...@@ -54,8 +47,7 @@ _SUBCOMMAND_MAP = { ...@@ -54,8 +47,7 @@ _SUBCOMMAND_MAP = {
def get_subcommand(p_args): def get_subcommand(p_args):
""" """
Retrieves the to-be executed Command and returns a tuple Retrieves the to-be executed Command and returns a tuple (Command, args).
(Command, args).
If args is an empty list, then the Command that corresponds with the If args is an empty list, then the Command that corresponds with the
default command specified in the configuration will be returned. default command specified in the configuration will be returned.
...@@ -79,13 +71,43 @@ def get_subcommand(p_args): ...@@ -79,13 +71,43 @@ def get_subcommand(p_args):
__import__(modulename, globals(), locals(), [classname], 0) __import__(modulename, globals(), locals(), [classname], 0)
return getattr(sys.modules[modulename], classname) 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 result = None
args = p_args args = p_args
alias_map = config().aliases()
try: try:
subcommand = p_args[0] 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) result = import_subcommand(subcommand)
args = args[1:] args = args[1:]
elif subcommand == 'help': elif subcommand == 'help':
...@@ -100,13 +122,16 @@ def get_subcommand(p_args): ...@@ -100,13 +122,16 @@ def get_subcommand(p_args):
pass pass
else: else:
p_command = config().default_command() 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) result = import_subcommand(p_command)
# leave args unchanged # leave args unchanged
except IndexError: except IndexError:
p_command = config().default_command() 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) result = import_subcommand(p_command)
return (result, args) return (result, args)
...@@ -34,10 +34,12 @@ except ConfigError as config_error: ...@@ -34,10 +34,12 @@ except ConfigError as config_error:
from topydo.Commands import get_subcommand from topydo.Commands import get_subcommand
from topydo.lib import TodoList from topydo.lib import TodoList
class CLIApplication(CLIApplicationBase): class CLIApplication(CLIApplicationBase):
""" """
Class that represents the (original) Command Line Interface of Topydo. Class that represents the (original) Command Line Interface of Topydo.
""" """
def __init__(self): def __init__(self):
super(CLIApplication, self).__init__() super(CLIApplication, self).__init__()
...@@ -50,7 +52,7 @@ class CLIApplication(CLIApplicationBase): ...@@ -50,7 +52,7 @@ class CLIApplication(CLIApplicationBase):
(subcommand, args) = get_subcommand(args) (subcommand, args) = get_subcommand(args)
if subcommand == None: if subcommand is None:
self._usage() self._usage()
if self._execute(subcommand, args) == False: if self._execute(subcommand, args) == False:
...@@ -58,6 +60,7 @@ class CLIApplication(CLIApplicationBase): ...@@ -58,6 +60,7 @@ class CLIApplication(CLIApplicationBase):
else: else:
self._post_execute() self._post_execute()
def main(): def main():
""" Main entry point of the CLI. """ """ Main entry point of the CLI. """
CLIApplication().run() CLIApplication().run()
......
...@@ -21,10 +21,10 @@ I/O on the command-line. ...@@ -21,10 +21,10 @@ I/O on the command-line.
import getopt import getopt
import sys import sys
from six import PY2
from six.moves import input
MAIN_OPTS = "ac:d:ht:v" MAIN_OPTS = "ac:d:ht:v"
READ_ONLY_COMMANDS = ('List', 'ListContext', 'ListProject')
def usage(): def usage():
""" Prints the command-line usage of topydo. """ """ Prints the command-line usage of topydo. """
...@@ -55,12 +55,14 @@ Available commands: ...@@ -55,12 +55,14 @@ Available commands:
* listprojects (lsprj) * listprojects (lsprj)
* postpone * postpone
* pri * pri
* revert
* sort * sort
* tag * tag
Run `topydo help <subcommand>` for command-specific help. Run `topydo help <subcommand>` for command-specific help.
""") """)
def write(p_file, p_string): def write(p_file, p_string):
""" """
Write p_string to file p_file, trailed by a newline character. Write p_string to file p_file, trailed by a newline character.
...@@ -73,11 +75,12 @@ def write(p_file, p_string): ...@@ -73,11 +75,12 @@ def write(p_file, p_string):
if p_string: if p_string:
p_file.write(p_string + "\n") p_file.write(p_string + "\n")
def error(p_string): def error(p_string):
""" Writes an error on the standard error. """ """ Writes an error on the standard error. """
write(sys.stderr, p_string) write(sys.stderr, p_string)
def version(): def version():
""" Print the current version and exit. """ """ Print the current version and exit. """
from topydo.lib.Version import VERSION, LICENSE from topydo.lib.Version import VERSION, LICENSE
...@@ -96,13 +99,12 @@ except ConfigError as config_error: ...@@ -96,13 +99,12 @@ except ConfigError as config_error:
error(str(config_error)) error(str(config_error))
sys.exit(1) sys.exit(1)
from topydo.commands.ArchiveCommand import ArchiveCommand
from topydo.commands.SortCommand import SortCommand
from topydo.lib import TodoFile from topydo.lib import TodoFile
from topydo.lib import TodoList from topydo.lib import TodoList
from topydo.lib import TodoListBase from topydo.lib import TodoListBase
from topydo.lib.Utils import escape_ansi from topydo.lib.Utils import escape_ansi
class CLIApplicationBase(object): class CLIApplicationBase(object):
""" """
Base class for a Command Line Interfaces (CLI) for topydo. Examples are the Base class for a Command Line Interfaces (CLI) for topydo. Examples are the
...@@ -110,10 +112,12 @@ class CLIApplicationBase(object): ...@@ -110,10 +112,12 @@ class CLIApplicationBase(object):
Handles input/output of the various subcommands. Handles input/output of the various subcommands.
""" """
def __init__(self): def __init__(self):
self.todolist = TodoList.TodoList([]) self.todolist = TodoList.TodoList([])
self.todofile = None self.todofile = None
self.do_archive = True self.do_archive = True
self.backup = None
def _usage(self): def _usage(self):
usage() usage()
...@@ -122,9 +126,6 @@ class CLIApplicationBase(object): ...@@ -122,9 +126,6 @@ class CLIApplicationBase(object):
def _process_flags(self): def _process_flags(self):
args = sys.argv[1:] args = sys.argv[1:]
if PY2:
args = [arg.decode('utf-8') for arg in args]
try: try:
opts, args = getopt.getopt(args, MAIN_OPTS) opts, args = getopt.getopt(args, MAIN_OPTS)
except getopt.GetoptError as e: except getopt.GetoptError as e:
...@@ -165,7 +166,11 @@ class CLIApplicationBase(object): ...@@ -165,7 +166,11 @@ class CLIApplicationBase(object):
archive_file = TodoFile.TodoFile(config().archive()) archive_file = TodoFile.TodoFile(config().archive())
archive = TodoListBase.TodoListBase(archive_file.read()) archive = TodoListBase.TodoListBase(archive_file.read())
if self.backup:
self.backup.add_archive(archive)
if archive: if archive:
from topydo.commands.ArchiveCommand import ArchiveCommand
command = ArchiveCommand(self.todolist, archive) command = ArchiveCommand(self.todolist, archive)
command.execute() command.execute()
...@@ -173,28 +178,34 @@ class CLIApplicationBase(object): ...@@ -173,28 +178,34 @@ class CLIApplicationBase(object):
archive_file.write(archive.print_todos()) archive_file.write(archive.print_todos())
def _help(self, args): def _help(self, args):
if args == None: if args is None:
pass # TODO pass # TODO
else: else:
pass # TODO pass # TODO
def _input(self): def is_read_only(self, p_command):
""" """ Returns True when the given command class is read-only. """
Returns a function that retrieves user input. read_only_commands = tuple(cmd + 'Command' for cmd in ('Revert', ) +
""" READ_ONLY_COMMANDS)
return input return p_command.__module__.endswith(read_only_commands)
def _execute(self, p_command, p_args): def _execute(self, p_command, p_args):
""" """
Execute a subcommand with arguments. p_command is a class (not an Execute a subcommand with arguments. p_command is a class (not an
object). 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( command = p_command(
p_args, p_args,
self.todolist, self.todolist,
lambda o: write(sys.stdout, o), lambda o: write(sys.stdout, o),
error, error,
self._input()) input)
if command.execute() != False: if command.execute() != False:
return True return True
...@@ -215,10 +226,15 @@ class CLIApplicationBase(object): ...@@ -215,10 +226,15 @@ class CLIApplicationBase(object):
self._archive() self._archive()
if config().keep_sorted(): if config().keep_sorted():
from topydo.commands.SortCommand import SortCommand
self._execute(SortCommand, []) self._execute(SortCommand, [])
if self.backup:
self.backup.save(self.todolist)
self.todofile.write(self.todolist.print_todos()) self.todofile.write(self.todolist.print_todos())
self.backup = None
def run(self): def run(self):
raise NotImplementedError raise NotImplementedError
...@@ -17,11 +17,12 @@ ...@@ -17,11 +17,12 @@
""" Entry file for the topydo Prompt interface (CLI). """ """ Entry file for the topydo Prompt interface (CLI). """
import os.path import os.path
import shlex
import sys import sys
from topydo.cli.CLIApplicationBase import CLIApplicationBase, error, usage from topydo.cli.CLIApplicationBase import CLIApplicationBase, error, usage
from topydo.cli.TopydoCompleter import TopydoCompleter 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 prompt_toolkit.history import InMemoryHistory
from topydo.lib.Config import config, ConfigError from topydo.lib.Config import config, ConfigError
...@@ -39,6 +40,7 @@ from topydo.Commands import get_subcommand ...@@ -39,6 +40,7 @@ from topydo.Commands import get_subcommand
from topydo.lib import TodoFile from topydo.lib import TodoFile
from topydo.lib import TodoList from topydo.lib import TodoList
def _todotxt_mtime(): def _todotxt_mtime():
""" """
Returns the mtime for the configured todo.txt file. Returns the mtime for the configured todo.txt file.
...@@ -49,11 +51,13 @@ def _todotxt_mtime(): ...@@ -49,11 +51,13 @@ def _todotxt_mtime():
# file not found # file not found
return None return None
class PromptApplication(CLIApplicationBase): class PromptApplication(CLIApplicationBase):
""" """
This class implements a variant of topydo's CLI showing a shell and This class implements a variant of topydo's CLI showing a shell and
offering auto-completion thanks to the prompt toolkit. offering auto-completion thanks to the prompt toolkit.
""" """
def __init__(self): def __init__(self):
super(PromptApplication, self).__init__() super(PromptApplication, self).__init__()
...@@ -69,7 +73,6 @@ class PromptApplication(CLIApplicationBase): ...@@ -69,7 +73,6 @@ class PromptApplication(CLIApplicationBase):
If the modification time of the todo.txt file is equal to the last time If the modification time of the todo.txt file is equal to the last time
it was checked, nothing will be done. it was checked, nothing will be done.
""" """
current_mtime = _todotxt_mtime() current_mtime = _todotxt_mtime()
if not self.todofile or self.mtime != current_mtime: if not self.todofile or self.mtime != current_mtime:
...@@ -77,8 +80,6 @@ class PromptApplication(CLIApplicationBase): ...@@ -77,8 +80,6 @@ class PromptApplication(CLIApplicationBase):
self.todolist = TodoList.TodoList(self.todofile.read()) self.todolist = TodoList.TodoList(self.todofile.read())
self.mtime = current_mtime self.mtime = current_mtime
# suppress upstream issue with Python 2.7
# pylint: disable=no-value-for-parameter
self.completer = TopydoCompleter(self.todolist) self.completer = TopydoCompleter(self.todolist)
def run(self): def run(self):
...@@ -90,32 +91,32 @@ class PromptApplication(CLIApplicationBase): ...@@ -90,32 +91,32 @@ class PromptApplication(CLIApplicationBase):
self._load_file() self._load_file()
try: try:
user_input = get_input(u'topydo> ', history=history, user_input = prompt(u'topydo> ', history=history,
completer=self.completer, completer=self.completer,
complete_while_typing=False).split() complete_while_typing=False)
user_input = shlex.split(user_input)
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
sys.exit(0) sys.exit(0)
mtime_after = _todotxt_mtime() 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
# refuse to perform operations such as 'del' and 'do' if the # todo.txt file has been changed in the background.
# 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.") error("WARNING: todo.txt file was modified by another application.\nTo prevent unintended changes, this operation was not executed.")
continue continue
(subcommand, args) = get_subcommand(user_input)
try: try:
if self._execute(subcommand, args) != False: if self._execute(subcommand, args) != False:
self._post_execute() self._post_execute()
except TypeError: except TypeError:
usage() usage()
def main(): def main():
""" Main entry point of the prompt interface. """ """ Main entry point of the prompt interface. """
PromptApplication().run() PromptApplication().run()
if __name__ == '__main__': if __name__ == '__main__':
main() main()
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
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. by the prompt toolkit.
""" """
...@@ -23,18 +23,21 @@ import datetime ...@@ -23,18 +23,21 @@ import datetime
import re import re
from prompt_toolkit.completion import Completer, Completion from prompt_toolkit.completion import Completer, Completion
from topydo.lib.Config import config
from topydo.Commands import _SUBCOMMAND_MAP from topydo.Commands import _SUBCOMMAND_MAP
from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date from topydo.lib.RelativeDate import relative_date_to_date
def _subcommands(p_word_before_cursor): def _subcommands(p_word_before_cursor):
""" Generator for subcommand name completion. """ """ 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)] sc.startswith(p_word_before_cursor)]
for command in subcommands: for command in subcommands:
yield Completion(command, -len(p_word_before_cursor)) yield Completion(command, -len(p_word_before_cursor))
def _dates(p_word_before_cursor): def _dates(p_word_before_cursor):
""" Generator for date completion. """ """ Generator for date completion. """
def _date_suggestions(): def _date_suggestions():
...@@ -79,11 +82,13 @@ def _dates(p_word_before_cursor): ...@@ -79,11 +82,13 @@ def _dates(p_word_before_cursor):
yield Completion(reldate, -len(value), display_meta=to_absolute(reldate)) yield Completion(reldate, -len(value), display_meta=to_absolute(reldate))
class TopydoCompleter(Completer): class TopydoCompleter(Completer):
""" """
Completer class that completes projects, contexts, dates and Completer class that completes projects, contexts, dates and
subcommands. subcommands.
""" """
def __init__(self, p_todolist): def __init__(self, p_todolist):
self.todolist = p_todolist self.todolist = p_todolist
...@@ -106,7 +111,8 @@ class TopydoCompleter(Completer): ...@@ -106,7 +111,8 @@ class TopydoCompleter(Completer):
def get_completions(self, p_document, _): def get_completions(self, p_document, _):
# include all characters except whitespaces (for + and @) # include all characters except whitespaces (for + and @)
word_before_cursor = p_document.get_word_before_cursor(True) word_before_cursor = p_document.get_word_before_cursor(True)
is_first_word = not re.match(r'\s*\S+\s', p_document.current_line_before_cursor) is_first_word = not re.match(r'\s*\S+\s',
p_document.current_line_before_cursor)
if is_first_word: if is_first_word:
return _subcommands(word_before_cursor) return _subcommands(word_before_cursor)
......
...@@ -16,15 +16,18 @@ ...@@ -16,15 +16,18 @@
""" Entry file for the Python todo.txt CLI. """ """ Entry file for the Python todo.txt CLI. """
import sys
import getopt import getopt
from topydo.cli.CLIApplicationBase import MAIN_OPTS, error import sys
from topydo.cli.CLI import CLIApplication from topydo.cli.CLI import CLIApplication
from topydo.cli.CLIApplicationBase import MAIN_OPTS, error
# enable color on windows CMD # enable color on windows CMD
if "win32" in sys.platform: if "win32" in sys.platform:
import colorama import colorama
colorama.init() colorama.init()
def main(): def main():
""" Main entry point of the CLI. """ """ Main entry point of the CLI. """
try: try:
......
...@@ -16,20 +16,21 @@ ...@@ -16,20 +16,21 @@
""" Provides the AddCommand class that implements the 'add' subcommand. """ """ Provides the AddCommand class that implements the 'add' subcommand. """
from datetime import date
import re
from sys import stdin
import codecs import codecs
import re
from datetime import date
from os.path import expanduser from os.path import expanduser
from sys import stdin
from topydo.lib.Config import config
from topydo.lib.Command import Command 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.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
class AddCommand(Command): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -60,11 +61,12 @@ class AddCommand(Command): ...@@ -60,11 +61,12 @@ class AddCommand(Command):
def _add_todo(self, p_todo_text): def _add_todo(self, p_todo_text):
def _preprocess_input_todo(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. 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 return todo_text
......
...@@ -15,16 +15,17 @@ ...@@ -15,16 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command, InvalidCommandArgument 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 from topydo.lib.TodoListBase import InvalidTodoException
class AppendCommand(Command): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
super(AppendCommand, self).__init__( super(AppendCommand, self).__init__(p_args, p_todolist, p_out, p_err,
p_args, p_todolist, p_out, p_err, p_prompt=lambda a: None) p_prompt)
def execute(self): def execute(self):
if not super(AppendCommand, self).execute(): if not super(AppendCommand, self).execute():
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
from topydo.lib.Command import Command from topydo.lib.Command import Command
class ArchiveCommand(Command): class ArchiveCommand(Command):
def __init__(self, p_todolist, p_archive_list): def __init__(self, p_todolist, p_archive_list):
""" """
......
...@@ -16,8 +16,9 @@ ...@@ -16,8 +16,9 @@
from topydo.lib.DCommand import DCommand from topydo.lib.DCommand import DCommand
class DeleteCommand(DCommand): class DeleteCommand(DCommand):
def __init__(self, p_args, p_todolist, def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None, p_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,16 +14,17 @@ ...@@ -14,16 +14,17 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib import Filter
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import Filter
from topydo.lib.PrettyPrinter import pretty_printer_factory from topydo.lib.PrettyPrinter import pretty_printer_factory
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.View import View from topydo.lib.View import View
class DepCommand(Command): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -135,9 +136,9 @@ class DepCommand(Command): ...@@ -135,9 +136,9 @@ class DepCommand(Command):
def help(self): def help(self):
return """\ return """\
* add : Adds a dependency. Using 1 before 2 creates a dependency * add : Adds a dependency. Using 1 before 2 creates a dependency
from todo item 2 to 1. from todo item 2 to 1.
* rm (alias: del) : Removes a dependency. * rm (alias: del) : Removes a dependency.
* ls : Lists all dependencies to or from a certain todo. * ls : Lists all dependencies to or from a certain todo.
* clean (alias: gc): Removes redundant id or p tags. * clean (alias: gc) : Removes redundant id or p tags.
""" """
...@@ -15,10 +15,11 @@ ...@@ -15,10 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.MultiCommand import MultiCommand from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
class DepriCommand(MultiCommand): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
......
...@@ -18,12 +18,14 @@ from datetime import date ...@@ -18,12 +18,14 @@ from datetime import date
from topydo.lib.DCommand import DCommand from topydo.lib.DCommand import DCommand
from topydo.lib.PrettyPrinter import PrettyPrinter from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.Recurrence import advance_recurring_todo, NoRecurrenceException 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 from topydo.lib.Utils import date_string_to_date
class DoCommand(DCommand): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -47,7 +49,10 @@ class DoCommand(DCommand): ...@@ -47,7 +49,10 @@ class DoCommand(DCommand):
self.strict_recurrence = True self.strict_recurrence = True
elif p_opt == "-d" or p_opt == "--date": elif p_opt == "-d" or p_opt == "--date":
try: 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: except ValueError:
self.completion_date = date.today() self.completion_date = date.today()
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -15,17 +15,14 @@ ...@@ -15,17 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os import os
from subprocess import call, check_call, CalledProcessError
import tempfile 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.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.Todo import Todo
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
# the true and only editor # the true and only editor
DEFAULT_EDITOR = 'vi' DEFAULT_EDITOR = 'vi'
...@@ -34,10 +31,16 @@ DEFAULT_EDITOR = 'vi' ...@@ -34,10 +31,16 @@ DEFAULT_EDITOR = 'vi'
# cannot use super() inside the class itself # cannot use super() inside the class itself
BASE_TODOLIST = lambda tl: super(TodoList, tl) BASE_TODOLIST = lambda tl: super(TodoList, tl)
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): class EditCommand(MultiCommand):
def __init__(self, p_args, p_todolist, p_output, p_error, p_input): def __init__(self, p_args, p_todolist, p_output, p_error, p_input):
super(EditCommand, self).__init__(p_args, p_todolist, p_output, super(EditCommand, self).__init__(p_args, p_todolist, p_output,
p_error, p_input) p_error, p_input)
if len(self.args) == 0: if len(self.args) == 0:
self.multi_mode = False self.multi_mode = False
...@@ -85,14 +88,14 @@ class EditCommand(MultiCommand): ...@@ -85,14 +88,14 @@ class EditCommand(MultiCommand):
return 1 return 1
except(OSError): except(OSError):
self.error('There is no such editor as: ' + editor + '. ' 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): def _catch_todo_errors(self):
errors = [] errors = []
if len(self.invalid_numbers) > 1 or len(self.invalid_numbers) > 0 and len(self.todos) > 0: if len(self.invalid_numbers) > 1 or len(self.invalid_numbers) > 0 and len(self.todos) > 0:
for number in self.invalid_numbers: 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: elif len(self.invalid_numbers) == 1 and len(self.todos) == 0:
errors.append("Invalid todo number given.") errors.append("Invalid todo number given.")
...@@ -105,10 +108,12 @@ class EditCommand(MultiCommand): ...@@ -105,10 +108,12 @@ class EditCommand(MultiCommand):
self.printer.add_filter(PrettyPrinterNumbers(self.todolist)) self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
temp_todos = self._todos_to_temp() temp_todos = self._todos_to_temp()
orig_mtime = _get_file_mtime(temp_todos)
if not self._open_in_editor(temp_todos.name): if not self._open_in_editor(temp_todos.name):
new_todos = self._todos_from_temp(temp_todos) 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: for todo in self.todos:
BASE_TODOLIST(self.todolist).delete(todo) BASE_TODOLIST(self.todolist).delete(todo)
...@@ -116,8 +121,7 @@ class EditCommand(MultiCommand): ...@@ -116,8 +121,7 @@ class EditCommand(MultiCommand):
self.todolist.add_todo(todo) self.todolist.add_todo(todo)
self.out(self.printer.print_todo(todo)) self.out(self.printer.print_todo(todo))
else: else:
self.error('Number of edited todos is not equal to ' self.error('Editing aborted. Nothing to do.')
'number of supplied todo IDs.')
else: else:
self.error(self.usage()) self.error(self.usage())
......
...@@ -18,11 +18,13 @@ import sys ...@@ -18,11 +18,13 @@ import sys
from topydo.lib.Command import Command from topydo.lib.Command import Command
class ExitCommand(Command): class ExitCommand(Command):
""" """
A command that exits topydo. Used for the 'exit' and 'quit' subcommands on A command that exits topydo. Used for the 'exit' and 'quit' subcommands on
the prompt CLI. the prompt CLI.
""" """
def __init__(self, p_args, p_todolist, p_output, p_error, p_input): def __init__(self, p_args, p_todolist, p_output, p_error, p_input):
super(ExitCommand, self).__init__(p_args, p_todolist, p_output, p_error, super(ExitCommand, self).__init__(p_args, p_todolist, p_output, p_error,
p_input) p_input)
...@@ -32,4 +34,3 @@ class ExitCommand(Command): ...@@ -32,4 +34,3 @@ class ExitCommand(Command):
return False return False
sys.exit(0) sys.exit(0)
...@@ -14,18 +14,17 @@ ...@@ -14,18 +14,17 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.Config import config 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.PrettyPrinter import pretty_printer_factory
from topydo.lib.PrettyPrinterFilter import ( from topydo.lib.prettyprinters.Format import PrettyPrinterFormatFilter
PrettyPrinterIndentFilter, from topydo.lib.TodoListBase import InvalidTodoException
PrettyPrinterHideTagFilter from topydo.lib.Utils import get_terminal_size
)
from topydo.lib.IcalPrinter import IcalPrinter
from topydo.lib.JsonPrinter import JsonPrinter
class ListCommand(ExpressionCommand): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -35,27 +34,24 @@ class ListCommand(ExpressionCommand): ...@@ -35,27 +34,24 @@ class ListCommand(ExpressionCommand):
self.printer = None self.printer = None
self.sort_expression = config().sort_string() self.sort_expression = config().sort_string()
self.show_all = False self.show_all = False
self.ids = None
self.format = config().list_format()
def _poke_icalendar(self): def _poke_icalendar(self):
""" """
Attempts to import the icalendar package. Returns True if it Attempts to import the icalendar package. Returns True if it
succeeds, otherwise False. succeeds, otherwise False.
Raises a SyntaxError when icalendar couldn't be imported (most likely
under Python 3.2.
""" """
try: try:
import icalendar as _ import icalendar as _
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
self.error("icalendar package is not installed.") self.error("icalendar package is not installed.")
return False return False
# may also raise SyntaxError, but we'll deal with that in execute()
return True return True
def _process_flags(self): 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: for opt, value in opts:
if opt == '-x': if opt == '-x':
...@@ -64,15 +60,56 @@ class ListCommand(ExpressionCommand): ...@@ -64,15 +60,56 @@ class ListCommand(ExpressionCommand):
self.sort_expression = value self.sort_expression = value
elif opt == '-f': elif opt == '-f':
if value == 'json': if value == 'json':
from topydo.lib.JsonPrinter import JsonPrinter
self.printer = JsonPrinter() self.printer = JsonPrinter()
elif value == 'ical': elif value == 'ical':
if self._poke_icalendar(): if self._poke_icalendar():
from topydo.lib.IcalPrinter import IcalPrinter
self.printer = IcalPrinter(self.todolist) self.printer = IcalPrinter(self.todolist)
else: else:
self.printer = None 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 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): def _print(self):
""" """
Prints the todos in the right format. Prints the todos in the right format.
...@@ -81,15 +118,14 @@ class ListCommand(ExpressionCommand): ...@@ -81,15 +118,14 @@ class ListCommand(ExpressionCommand):
printing). If a format was specified on the commandline, this format is printing). If a format was specified on the commandline, this format is
sent to the output. sent to the output.
""" """
if self.printer is None:
if self.printer == None:
# create a standard printer with some filters # create a standard printer with some filters
indent = config().list_indent() indent = config().list_indent()
final_format = ' ' * indent + self.format
hidden_tags = config().hidden_tags() hidden_tags = config().hidden_tags()
filters = [] filters = []
filters.append(PrettyPrinterIndentFilter(indent)) filters.append(PrettyPrinterFormatFilter(self.todolist, final_format))
filters.append(PrettyPrinterHideTagFilter(hidden_tags))
self.printer = pretty_printer_factory(self.todolist, filters) self.printer = pretty_printer_factory(self.todolist, filters)
...@@ -101,7 +137,7 @@ class ListCommand(ExpressionCommand): ...@@ -101,7 +137,7 @@ class ListCommand(ExpressionCommand):
try: try:
self._process_flags() self._process_flags()
except SyntaxError: # pragma: no cover except SyntaxError: # pragma: no cover
# importing icalendar failed, most likely due to Python 3.2 # importing icalendar failed, most likely due to Python 3.2
self.error("icalendar is not supported in this Python version.") self.error("icalendar is not supported in this Python version.")
return False return False
...@@ -110,7 +146,8 @@ class ListCommand(ExpressionCommand): ...@@ -110,7 +146,8 @@ class ListCommand(ExpressionCommand):
return True return True
def usage(self): 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): def help(self):
return """\ return """\
...@@ -124,12 +161,46 @@ When an expression is given, only the todos matching that expression are shown. ...@@ -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'. -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 * '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 that this is not a read-only operation, todo items may obtain
an 'ical' tag with a unique ID. Completed todo items may be an 'ical' tag with a unique ID. Completed todo items may be
archived. archived.
* 'json' - Javascript Object Notation (JSON) * '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 -s : Sort the list according to a sort expression. Defaults to the expression
in the configuration. in the configuration.
-x : Show all todos (i.e. do not filter on dependencies or relevance). -x : Show all todos (i.e. do not filter on dependencies or relevance).
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,8 +16,9 @@ ...@@ -16,8 +16,9 @@
from topydo.lib.Command import Command from topydo.lib.Command import Command
class ListContextCommand(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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,8 +16,9 @@ ...@@ -16,8 +16,9 @@
from topydo.lib.Command import Command from topydo.lib.Command import Command
class ListProjectCommand(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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
......
...@@ -16,14 +16,15 @@ ...@@ -16,14 +16,15 @@
from datetime import date, timedelta from datetime import date, timedelta
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.Config import config 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.RelativeDate import relative_date_to_date
from topydo.lib.Utils import date_string_to_date from topydo.lib.Utils import date_string_to_date
class PostponeCommand(MultiCommand): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -84,8 +85,8 @@ Synopsis: postpone [-s] <NUMBER> [<NUMBER2> ...] <PATTERN>" ...@@ -84,8 +85,8 @@ Synopsis: postpone [-s] <NUMBER> [<NUMBER2> ...] <PATTERN>"
return """\ return """\
Postpone the todo item(s) with the given number(s) and the given pattern. Postpone the todo item(s) with the given number(s) and the given pattern.
Postponing is done by adjusting the due date(s) of the todo(s), and if the -s flag is Postponing is done by adjusting the due date(s) of the todo(s), and if the -s
given, the start date accordingly. flag is given, the start date accordingly.
It is also possible to postpone items as complete with an expression using 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 the -e flag. Use -x to also process todo items that are normally invisible
......
...@@ -17,11 +17,12 @@ ...@@ -17,11 +17,12 @@
import re import re
from topydo.lib.MultiCommand import MultiCommand 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 from topydo.lib.Utils import is_valid_priority
class PriorityCommand(MultiCommand): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -32,7 +33,7 @@ class PriorityCommand(MultiCommand): ...@@ -32,7 +33,7 @@ class PriorityCommand(MultiCommand):
def _execute_multi_specific(self): def _execute_multi_specific(self):
def normalize_priority(p_priority): 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 return match.group(1) if match else p_priority
priority = normalize_priority(self.args[-1]) 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. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -18,8 +18,9 @@ from topydo.lib.Command import Command, InvalidCommandArgument ...@@ -18,8 +18,9 @@ from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
class SortCommand(Command): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -35,11 +36,10 @@ class SortCommand(Command): ...@@ -35,11 +36,10 @@ class SortCommand(Command):
except InvalidCommandArgument: except InvalidCommandArgument:
expression = config().sort_string() expression = config().sort_string()
sorter = Sorter(expression) # TODO: validate sorter = Sorter(expression) # TODO: validate
sorted_todos = sorter.sort(self.todolist.todos()) sorted_todos = sorter.sort(self.todolist.todos())
self.todolist.erase() self.todolist.replace(sorted_todos)
self.todolist.add_todos(sorted_todos)
def usage(self): def usage(self):
return """Synopsis: sort [expression]""" return """Synopsis: sort [expression]"""
......
...@@ -15,11 +15,14 @@ ...@@ -15,11 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command, InvalidCommandArgument 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 from topydo.lib.TodoListBase import InvalidTodoException
class TagCommand(Command): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -67,7 +70,7 @@ class TagCommand(Command): ...@@ -67,7 +70,7 @@ class TagCommand(Command):
def _choose(self): 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" answer = "all"
...@@ -89,7 +92,16 @@ class TagCommand(Command): ...@@ -89,7 +92,16 @@ class TagCommand(Command):
return answer 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=""): def _set_helper(self, p_old_value=""):
self._convert_relative_dates()
old_src = self.todo.source() old_src = self.todo.source()
self.todo.set_tag(self.tag, self.value, self.force_add, p_old_value) self.todo.set_tag(self.tag, self.value, self.force_add, p_old_value)
...@@ -103,7 +115,7 @@ class TagCommand(Command): ...@@ -103,7 +115,7 @@ class TagCommand(Command):
if answer == "all": if answer == "all":
for value in self.current_values: for value in self.current_values:
self._set_helper(value) 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]) self._set_helper(self.current_values[answer])
else: else:
...@@ -130,6 +142,6 @@ is omitted, the tag is removed from the todo item. ...@@ -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 -a : Do not change the current value of the tag if it exists, but add a new
value. value.
-f : Force setting/removing all values of the tag. Prevents interaction with the -f : Force setting/removing all values of the tag. Prevents interaction with
user. 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 @@ ...@@ -18,7 +18,8 @@
from topydo.lib.Config import config from topydo.lib.Config import config
NEUTRAL_COLOR = '\033[0m' NEUTRAL_COLOR = '\033[0m'
class Colors(object): class Colors(object):
def __init__(self): def __init__(self):
...@@ -48,13 +49,13 @@ class Colors(object): ...@@ -48,13 +49,13 @@ class Colors(object):
try: try:
if p_safe: if p_safe:
if 8 > int(p_int) >=0: if 8 > int(p_int) >= 0:
return '\033[{};3{}m'.format(decoration, str(p_int)) return '\033[{};3{}m'.format(decoration, str(p_int))
elif 16 > int(p_int): elif 16 > int(p_int):
p_int = int(p_int) - 8 p_int = int(p_int) - 8
return '\033[{};1;3{}m'.format(decoration, str(p_int)) 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)) return '\033[{};38;5;{}m'.format(decoration, str(p_int))
else: else:
return NEUTRAL_COLOR return NEUTRAL_COLOR
......
...@@ -18,11 +18,13 @@ import getopt ...@@ -18,11 +18,13 @@ import getopt
from topydo.lib.PrettyPrinter import PrettyPrinter from topydo.lib.PrettyPrinter import PrettyPrinter
class InvalidCommandArgument(Exception): class InvalidCommandArgument(Exception):
pass pass
class Command(object): 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -90,4 +92,3 @@ class Command(object): ...@@ -90,4 +92,3 @@ class Command(object):
def help(self): def help(self):
""" Returns the help text for this command. """ """ Returns the help text for this command. """
raise NotImplementedError raise NotImplementedError
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import configparser
import os import os
import shlex
from six.moves import configparser
class ConfigError(Exception): class ConfigError(Exception):
def __init__(self, p_text): def __init__(self, p_text):
...@@ -25,6 +25,7 @@ class ConfigError(Exception): ...@@ -25,6 +25,7 @@ class ConfigError(Exception):
def __str__(self): def __str__(self):
return self.text return self.text
class _Config: class _Config:
def __init__(self, p_path=None, p_overrides=None): def __init__(self, p_path=None, p_overrides=None):
""" """
...@@ -40,6 +41,7 @@ class _Config: ...@@ -40,6 +41,7 @@ class _Config:
""" """
self.sections = [ self.sections = [
'add', 'add',
'aliases',
'colorscheme', 'colorscheme',
'dep', 'dep',
'ls', 'ls',
...@@ -49,46 +51,72 @@ class _Config: ...@@ -49,46 +51,72 @@ class _Config:
] ]
self.defaults = { self.defaults = {
# topydo 'topydo': {
'default_command': 'ls', 'default_command': 'ls',
'colors': '1', 'colors': '1',
'filename' : 'todo.txt', 'filename': 'todo.txt',
'archive_filename' : 'done.txt', 'archive_filename': 'done.txt',
'identifiers': 'linenumber', 'identifiers': 'linenumber',
'backup_count': '5',
# add },
'auto_creation_date': '1',
'add': {
# ls 'auto_creation_date': '1',
'hide_tags': 'id,p,ical', },
'indent': 0,
'list_limit': '-1', 'ls': {
'hide_tags': 'id,p,ical',
# tags 'indent': '0',
'tag_start': 't', 'list_limit': '-1',
'tag_due': 'due', 'list_format': '|%I| %x %{(}p{)} %c %s %k %{due:}d %{t:}t',
'tag_star': 'star', },
# sort 'tags': {
'keep_sorted': '0', 'tag_start': 't',
'sort_string': 'desc:importance,due,desc:priority', 'tag_due': 'due',
'ignore_weekends': '1', 'tag_star': 'star',
},
# dep
'append_parent_projects': '0', 'sort': {
'append_parent_contexts': '0', 'keep_sorted': '0',
'sort_string': 'desc:importance,due,desc:priority',
# colorscheme 'ignore_weekends': '1',
'project_color': 'red', },
'context_color': 'magenta',
'metadata_color': 'green', 'dep': {
'link_color': 'cyan', 'append_parent_projects': '0',
'priority_colors': 'A:cyan,B:yellow,C:blue', '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.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 = [ files = [
"/etc/topydo.conf", "/etc/topydo.conf",
...@@ -100,11 +128,10 @@ class _Config: ...@@ -100,11 +128,10 @@ class _Config:
# when a path is given, *only* use the values in that file, or the # when a path is given, *only* use the values in that file, or the
# defaults listed above. # defaults listed above.
if p_path != None: if p_path is not None:
files = [p_path] files = [p_path]
self.cp.read(files) self.cp.read(files)
self._supplement_sections() self._supplement_sections()
if p_overrides: if p_overrides:
...@@ -126,7 +153,7 @@ class _Config: ...@@ -126,7 +153,7 @@ class _Config:
try: try:
return self.cp.getboolean('topydo', 'colors') return self.cp.getboolean('topydo', 'colors')
except ValueError: except ValueError:
return self.defaults['colors'] == '1' return self.defaults['topydo']['colors'] == '1'
def todotxt(self): def todotxt(self):
return os.path.expanduser(self.cp.get('topydo', 'filename')) return os.path.expanduser(self.cp.get('topydo', 'filename'))
...@@ -137,23 +164,32 @@ class _Config: ...@@ -137,23 +164,32 @@ class _Config:
def identifiers(self): def identifiers(self):
return self.cp.get('topydo', 'identifiers') 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): def list_limit(self):
try: try:
return self.cp.getint('ls', 'list_limit') return self.cp.getint('ls', 'list_limit')
except ValueError: except ValueError:
return int(self.defaults['list_limit']) return int(self.defaults['ls']['list_limit'])
def list_indent(self): def list_indent(self):
try: try:
return self.cp.getint('ls', 'indent') return self.cp.getint('ls', 'indent')
except ValueError: except ValueError:
return int(self.defaults['indent']) return int(self.defaults['ls']['indent'])
def keep_sorted(self): def keep_sorted(self):
try: try:
return self.cp.getboolean('sort', 'keep_sorted') return self.cp.getboolean('sort', 'keep_sorted')
except ValueError: except ValueError:
return self.defaults['keep_sorted'] == '1' return self.defaults['sort']['keep_sorted'] == '1'
def sort_string(self): def sort_string(self):
return self.cp.get('sort', 'sort_string') return self.cp.get('sort', 'sort_string')
...@@ -162,19 +198,19 @@ class _Config: ...@@ -162,19 +198,19 @@ class _Config:
try: try:
return self.cp.getboolean('sort', 'ignore_weekends') return self.cp.getboolean('sort', 'ignore_weekends')
except ValueError: except ValueError:
return self.defaults['ignore_weekends'] == '1' return self.defaults['sort']['ignore_weekends'] == '1'
def append_parent_projects(self): def append_parent_projects(self):
try: try:
return self.cp.getboolean('dep', 'append_parent_projects') return self.cp.getboolean('dep', 'append_parent_projects')
except ValueError: except ValueError:
return self.defaults['append_parent_projects'] == '1' return self.defaults['dep']['append_parent_projects'] == '1'
def append_parent_contexts(self): def append_parent_contexts(self):
try: try:
return self.cp.getboolean('dep', 'append_parent_contexts') return self.cp.getboolean('dep', 'append_parent_contexts')
except ValueError: except ValueError:
return self.defaults['append_parent_contexts'] == '1' return self.defaults['dep']['append_parent_contexts'] == '1'
def _get_tag(self, p_tag): def _get_tag(self, p_tag):
try: try:
...@@ -197,10 +233,13 @@ class _Config: ...@@ -197,10 +233,13 @@ class _Config:
""" Returns a list of tags to be hidden from the 'ls' output. """ """ Returns a list of tags to be hidden from the 'ls' output. """
hidden_tags = self.cp.get('ls', 'hide_tags') hidden_tags = self.cp.get('ls', 'hide_tags')
# pylint: disable=no-member # 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): 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') pri_colors_str = self.cp.get('colorscheme', 'priority_colors')
def _str_to_dict(p_string): def _str_to_dict(p_string):
...@@ -213,11 +252,11 @@ class _Config: ...@@ -213,11 +252,11 @@ class _Config:
try: try:
if pri_colors_str == '': if pri_colors_str == '':
pri_colors_dict = {'A':'', 'B': '', 'C': ''} pri_colors_dict = {'A': '', 'B': '', 'C': ''}
else: else:
pri_colors_dict = _str_to_dict(pri_colors_str) pri_colors_dict = _str_to_dict(pri_colors_str)
except ValueError: except ValueError:
pri_colors_dict = _str_to_dict(self.defaults['priority_colors']) pri_colors_dict = _str_to_dict(self.defaults['colorscheme']['priority_colors'])
return pri_colors_dict return pri_colors_dict
...@@ -225,31 +264,52 @@ class _Config: ...@@ -225,31 +264,52 @@ class _Config:
try: try:
return self.cp.get('colorscheme', 'project_color') return self.cp.get('colorscheme', 'project_color')
except ValueError: except ValueError:
return int(self.defaults['project_color']) return int(self.defaults['colorscheme']['project_color'])
def context_color(self): def context_color(self):
try: try:
return self.cp.get('colorscheme', 'context_color') return self.cp.get('colorscheme', 'context_color')
except ValueError: except ValueError:
return int(self.defaults['context_color']) return int(self.defaults['colorscheme']['context_color'])
def metadata_color(self): def metadata_color(self):
try: try:
return self.cp.get('colorscheme', 'metadata_color') return self.cp.get('colorscheme', 'metadata_color')
except ValueError: except ValueError:
return int(self.defaults['metadata_color']) return int(self.defaults['colorscheme']['metadata_color'])
def link_color(self): def link_color(self):
try: try:
return self.cp.get('colorscheme', 'link_color') return self.cp.get('colorscheme', 'link_color')
except ValueError: except ValueError:
return int(self.defaults['link_color']) return int(self.defaults['colorscheme']['link_color'])
def auto_creation_date(self): def auto_creation_date(self):
try: try:
return self.cp.getboolean('add', 'auto_creation_date') return self.cp.getboolean('add', 'auto_creation_date')
except ValueError: 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): def config(p_path=None, p_overrides=None):
""" """
...@@ -263,7 +323,7 @@ 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 passed value instead. Structure: (section, option) => value
The previous configuration instance will be discarded. 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: try:
config.instance = _Config(p_path, p_overrides) config.instance = _Config(p_path, p_overrides)
except configparser.ParsingError as perr: except configparser.ParsingError as perr:
......
...@@ -18,7 +18,8 @@ import re ...@@ -18,7 +18,8 @@ import re
from topydo.lib.MultiCommand import MultiCommand from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.PrettyPrinter import PrettyPrinter from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
class DCommand(MultiCommand): class DCommand(MultiCommand):
""" """
...@@ -26,7 +27,7 @@ class DCommand(MultiCommand): ...@@ -26,7 +27,7 @@ class DCommand(MultiCommand):
alike. alike.
""" """
def __init__(self, p_args, p_todolist, def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None, p_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -35,7 +36,8 @@ class DCommand(MultiCommand): ...@@ -35,7 +36,8 @@ class DCommand(MultiCommand):
self.force = False 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): def get_flags(self):
return ("f", ["force"]) return ("f", ["force"])
...@@ -91,7 +93,7 @@ class DCommand(MultiCommand): ...@@ -91,7 +93,7 @@ class DCommand(MultiCommand):
just before that point. just before that point.
""" """
return [todo for todo in self.todolist.todos()[:self.length] return [todo for todo in self.todolist.todos()[:self.length]
if not self._uncompleted_children(todo) and todo.is_active()] if not self._uncompleted_children(todo) and todo.is_active()]
def condition(self, _): def condition(self, _):
""" """
......
...@@ -16,17 +16,19 @@ ...@@ -16,17 +16,19 @@
import re import re
from topydo.lib import Filter
from topydo.lib.Command import Command from topydo.lib.Command import Command
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import Filter
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from topydo.lib.View import View from topydo.lib.View import View
class ExpressionCommand(Command): class ExpressionCommand(Command):
""" """
A common class for commands operating on todos selected by expressions. 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_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -35,6 +37,7 @@ class ExpressionCommand(Command): ...@@ -35,6 +37,7 @@ class ExpressionCommand(Command):
self.sort_expression = config().sort_string() self.sort_expression = config().sort_string()
self.show_all = False self.show_all = False
self.limit = config().list_limit()
# Commands using last argument differently (i.e as something other than # Commands using last argument differently (i.e as something other than
# todo ID/expression) have to set attribute below to True. # todo ID/expression) have to set attribute below to True.
self.last_argument = False self.last_argument = False
...@@ -50,7 +53,7 @@ class ExpressionCommand(Command): ...@@ -50,7 +53,7 @@ class ExpressionCommand(Command):
filters += Filter.get_filter_list(args) filters += Filter.get_filter_list(args)
if not self.show_all: if not self.show_all:
filters.append(Filter.LimitFilter(config().list_limit())) filters.append(Filter.LimitFilter(self.limit))
return filters return filters
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -19,18 +19,19 @@ import re ...@@ -19,18 +19,19 @@ import re
from topydo.lib.RelativeDate import relative_date_to_date from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.Utils import date_string_to_date from topydo.lib.Utils import date_string_to_date
class Filter(object): class Filter(object):
def filter(self, p_todos): def filter(self, p_todos):
""" """
Filters a list of todos. Truncates the list after p_limit todo Filters a list of todos. Truncates the list after p_limit todo
items (or no maximum limit if omitted). items (or no maximum limit if omitted).
""" """
return [t for t in p_todos if self.match(t)] return [t for t in p_todos if self.match(t)]
def match(self, _): def match(self, _):
raise NotImplementedError raise NotImplementedError
class NegationFilter(Filter): class NegationFilter(Filter):
def __init__(self, p_filter): def __init__(self, p_filter):
self._filter = p_filter self._filter = p_filter
...@@ -38,6 +39,7 @@ class NegationFilter(Filter): ...@@ -38,6 +39,7 @@ class NegationFilter(Filter):
def match(self, p_todo): def match(self, p_todo):
return not self._filter.match(p_todo) return not self._filter.match(p_todo)
class AndFilter(Filter): class AndFilter(Filter):
def __init__(self, p_filter1, p_filter2): def __init__(self, p_filter1, p_filter2):
self._filter1 = p_filter1 self._filter1 = p_filter1
...@@ -46,6 +48,7 @@ class AndFilter(Filter): ...@@ -46,6 +48,7 @@ class AndFilter(Filter):
def match(self, p_todo): def match(self, p_todo):
return self._filter1.match(p_todo) and self._filter2.match(p_todo) return self._filter1.match(p_todo) and self._filter2.match(p_todo)
class OrFilter(Filter): class OrFilter(Filter):
def __init__(self, p_filter1, p_filter2): def __init__(self, p_filter1, p_filter2):
self._filter1 = p_filter1 self._filter1 = p_filter1
...@@ -54,6 +57,7 @@ class OrFilter(Filter): ...@@ -54,6 +57,7 @@ class OrFilter(Filter):
def match(self, p_todo): def match(self, p_todo):
return self._filter1.match(p_todo) or self._filter2.match(p_todo) return self._filter1.match(p_todo) or self._filter2.match(p_todo)
class GrepFilter(Filter): class GrepFilter(Filter):
""" Matches when the todo text contains a text. """ """ Matches when the todo text contains a text. """
...@@ -63,7 +67,7 @@ class GrepFilter(Filter): ...@@ -63,7 +67,7 @@ class GrepFilter(Filter):
# convert to string in case we receive integers # convert to string in case we receive integers
self.expression = p_expression self.expression = p_expression
if p_case_sensitive != None: if p_case_sensitive is not None:
self.case_sensitive = p_case_sensitive self.case_sensitive = p_case_sensitive
else: else:
# only be case sensitive when the expression contains at least one # only be case sensitive when the expression contains at least one
...@@ -80,6 +84,7 @@ class GrepFilter(Filter): ...@@ -80,6 +84,7 @@ class GrepFilter(Filter):
return string.find(expr) != -1 return string.find(expr) != -1
class RelevanceFilter(Filter): class RelevanceFilter(Filter):
""" """
Matches when the todo is relevant, i.e.: Matches when the todo is relevant, i.e.:
...@@ -99,8 +104,10 @@ class RelevanceFilter(Filter): ...@@ -99,8 +104,10 @@ class RelevanceFilter(Filter):
return p_todo.is_active() and is_due return p_todo.is_active() and is_due
class DependencyFilter(Filter): class DependencyFilter(Filter):
""" Matches when a todo has no unfinished child tasks. """ """ Matches when a todo has no unfinished child tasks. """
def __init__(self, p_todolist): def __init__(self, p_todolist):
""" """
Constructor. Constructor.
...@@ -120,6 +127,7 @@ class DependencyFilter(Filter): ...@@ -120,6 +127,7 @@ class DependencyFilter(Filter):
return not uncompleted return not uncompleted
class InstanceFilter(Filter): class InstanceFilter(Filter):
def __init__(self, p_todos): def __init__(self, p_todos):
""" """
...@@ -143,6 +151,7 @@ class InstanceFilter(Filter): ...@@ -143,6 +151,7 @@ class InstanceFilter(Filter):
except ValueError: except ValueError:
return False return False
class LimitFilter(Filter): class LimitFilter(Filter):
def __init__(self, p_limit): def __init__(self, p_limit):
super(LimitFilter, self).__init__() super(LimitFilter, self).__init__()
...@@ -151,20 +160,54 @@ class LimitFilter(Filter): ...@@ -151,20 +160,54 @@ class LimitFilter(Filter):
def filter(self, p_todos): def filter(self, p_todos):
return p_todos[:self.limit] if self.limit >= 0 else 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): class OrdinalFilter(Filter):
super(OrdinalTagFilter, self).__init__() """ Base class for ordinal filters. """
def __init__(self, p_expression, p_pattern):
super(OrdinalFilter, self).__init__()
self.expression = p_expression self.expression = p_expression
match = re.match(ORDINAL_TAG_MATCH, self.expression) match = re.match(p_pattern, self.expression)
if match: if match:
self.key = match.group('key') try:
self.key = match.group('key')
except IndexError:
pass
self.operator = match.group('operator') or '=' self.operator = match.group('operator') or '='
self.value = match.group('value') 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): def match(self, p_todo):
""" """
Performs a match on a key:value tag in the todo. Performs a match on a key:value tag in the todo.
...@@ -199,20 +242,80 @@ class OrdinalTagFilter(Filter): ...@@ -199,20 +242,80 @@ class OrdinalTagFilter(Filter):
grep = GrepFilter(self.expression) grep = GrepFilter(self.expression)
return grep.match(p_todo) return grep.match(p_todo)
if self.operator == '<': return self.compare_operands(operand1, operand2)
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 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): def get_filter_list(p_expression):
""" """
...@@ -223,15 +326,22 @@ def get_filter_list(p_expression): ...@@ -223,15 +326,22 @@ def get_filter_list(p_expression):
""" """
result = [] result = []
for arg in p_expression: for arg in p_expression:
if re.match(ORDINAL_TAG_MATCH, arg): # when a word starts with -, it should be negated
argfilter = OrdinalTagFilter(arg) is_negated = len(arg) > 1 and arg[0] == '-'
elif len(arg) > 1 and arg[0] == '-': arg = arg[1:] if is_negated else arg
# when a word starts with -, exclude it
argfilter = GrepFilter(arg[1:]) argfilter = None
argfilter = NegationFilter(argfilter) for match, _filter in MATCHES:
else: if re.match(match, arg):
argfilter = _filter(arg)
break
if not argfilter:
argfilter = GrepFilter(arg) argfilter = GrepFilter(arg)
if is_negated:
argfilter = NegationFilter(argfilter)
result.append(argfilter) result.append(argfilter)
return result return result
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,11 +16,13 @@ ...@@ -16,11 +16,13 @@
""" Contains the class for a directed graph. """ """ Contains the class for a directed graph. """
class DirectedGraph(object): class DirectedGraph(object):
""" """
Represents a simple directed graph, used for tracking todo Represents a simple directed graph, used for tracking todo
dependencies. The nodes are very simple: just integers. dependencies. The nodes are very simple: just integers.
""" """
def __init__(self): def __init__(self):
self._edges = {} self._edges = {}
self._edge_numbers = {} self._edge_numbers = {}
...@@ -36,8 +38,7 @@ class DirectedGraph(object): ...@@ -36,8 +38,7 @@ class DirectedGraph(object):
def add_edge(self, p_from, p_to, p_id=None): 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 Adds an edge to the graph. The nodes will be added if they don't exist.
exist.
The p_id is the id of the edge, if the client wishes to maintain this. The p_id is the id of the edge, if the client wishes to maintain this.
""" """
...@@ -59,15 +60,13 @@ class DirectedGraph(object): ...@@ -59,15 +60,13 @@ class DirectedGraph(object):
def incoming_neighbors(self, p_id, p_recursive=False): def incoming_neighbors(self, p_id, p_recursive=False):
""" """
Returns a set of the direct neighbors that can reach the given Returns a set of the direct neighbors that can reach the given node.
node.
""" """
return self.reachable_nodes_reverse(p_id, p_recursive) return self.reachable_nodes_reverse(p_id, p_recursive)
def outgoing_neighbors(self, p_id, p_recursive=False): def outgoing_neighbors(self, p_id, p_recursive=False):
""" """
Returns the set of the direct neighbors that the given node can Returns the set of the direct neighbors that the given node can reach.
reach.
""" """
return self.reachable_nodes(p_id, p_recursive) return self.reachable_nodes(p_id, p_recursive)
...@@ -92,8 +91,8 @@ class DirectedGraph(object): ...@@ -92,8 +91,8 @@ class DirectedGraph(object):
visited.add(current) visited.add(current)
if p_reverse: if p_reverse:
parents = [node for node, neighbors in self._edges.items() \ parents = [node for node, neighbors in self._edges.items()
if current in neighbors] if current in neighbors]
stack = stack + parents stack = stack + parents
result = result.union(parents) result = result.union(parents)
...@@ -130,8 +129,8 @@ class DirectedGraph(object): ...@@ -130,8 +129,8 @@ class DirectedGraph(object):
""" """
Returns True iff the given node has no incoming or outgoing edges. Returns True iff the given node has no incoming or outgoing edges.
""" """
return len(self.incoming_neighbors(p_id)) == 0 \ return(len(self.incoming_neighbors(p_id)) == 0
and len(self.outgoing_neighbors(p_id)) == 0 and len(self.outgoing_neighbors(p_id)) == 0)
def has_edge(self, p_from, p_to): def has_edge(self, p_from, p_to):
""" Returns True when the graph has the given edge. """ """ Returns True when the graph has the given edge. """
...@@ -186,10 +185,10 @@ class DirectedGraph(object): ...@@ -186,10 +185,10 @@ class DirectedGraph(object):
childpairs = \ childpairs = \
[(c1, c2) for c1 in neighbors for c2 in neighbors if c1 != c2] [(c1, c2) for c1 in neighbors for c2 in neighbors if c1 != c2]
for pair in childpairs: for child1, child2 in childpairs:
if self.has_path(pair[0], pair[1]) \ if self.has_path(child1, child2) \
and not self.has_path(pair[0], from_node): and not self.has_path(child1, from_node):
removals.add((from_node, pair[1])) removals.add((from_node, child2))
for edge in removals: for edge in removals:
self.remove_edge(edge[0], edge[1]) self.remove_edge(edge[0], edge[1])
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -23,10 +23,11 @@ from hashlib import sha1 ...@@ -23,10 +23,11 @@ from hashlib import sha1
_TABLE_SIZES = { _TABLE_SIZES = {
# we choose a large table size to reduce the chance of collisions. # we choose a large table size to reduce the chance of collisions.
3: 46649, # largest prime under zzz_36 3: 46649, # largest prime under zzz_36
4: 1679609 # largest prime under zzzz_36 4: 1679609 # largest prime under zzzz_36
} }
def _to_base36(p_value): def _to_base36(p_value):
""" """
Converts integer to base36 string. Converts integer to base36 string.
...@@ -43,7 +44,8 @@ def _to_base36(p_value): ...@@ -43,7 +44,8 @@ def _to_base36(p_value):
return base36 or alphabet[0] 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 Calculates a unique value for each item in the list, these can be used as
identifiers. identifiers.
...@@ -79,4 +81,3 @@ def hash_list_values(p_list, p_key=lambda i: i): ...@@ -79,4 +81,3 @@ def hash_list_values(p_list, p_key=lambda i: i):
result.append((item, _to_base36(hash_value))) result.append((item, _to_base36(hash_value)))
return result return result
...@@ -19,12 +19,13 @@ Provides a printer that transforms a list of Todo items to an iCalendar ...@@ -19,12 +19,13 @@ Provides a printer that transforms a list of Todo items to an iCalendar
file according to RFC 2445. file according to RFC 2445.
""" """
from datetime import datetime, time
import random import random
import string import string
from datetime import datetime, time
from topydo.lib.PrettyPrinter import Printer from topydo.lib.PrettyPrinter import Printer
def _convert_priority(p_priority): def _convert_priority(p_priority):
""" """
Converts todo.txt priority to an iCalendar priority (RFC 2445). Converts todo.txt priority to an iCalendar priority (RFC 2445).
...@@ -55,6 +56,7 @@ def _convert_priority(p_priority): ...@@ -55,6 +56,7 @@ def _convert_priority(p_priority):
return result return result
class IcalPrinter(Printer): class IcalPrinter(Printer):
""" """
A printer that converts a list of Todo items to a string in iCalendar A printer that converts a list of Todo items to a string in iCalendar
...@@ -62,6 +64,7 @@ class IcalPrinter(Printer): ...@@ -62,6 +64,7 @@ class IcalPrinter(Printer):
https://www.rfc-editor.org/rfc/rfc2445.txt https://www.rfc-editor.org/rfc/rfc2445.txt
""" """
def __init__(self, p_todolist): def __init__(self, p_todolist):
super(IcalPrinter, self).__init__() super(IcalPrinter, self).__init__()
self.todolist = p_todolist self.todolist = p_todolist
...@@ -69,10 +72,7 @@ class IcalPrinter(Printer): ...@@ -69,10 +72,7 @@ class IcalPrinter(Printer):
try: try:
import icalendar import icalendar
self.icalendar = icalendar self.icalendar = icalendar
except (SyntaxError, ImportError): # pragma: no cover except ImportError: # pragma: no cover
# icalendar does not support Python 3.2 resulting in a SyntaxError. Since
# this is an optional dependency, dropping Python 3.2 support altogether is
# too much. Therefore just disable the iCalendar functionality
self.icalendar = None self.icalendar = None
def print_list(self, p_todos): def print_list(self, p_todos):
...@@ -98,6 +98,7 @@ class IcalPrinter(Printer): ...@@ -98,6 +98,7 @@ class IcalPrinter(Printer):
Gets a unique ID from a todo item, stored by the ical tag. If the 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. tag is not present, a random value is assigned to it and returned.
""" """
def generate_uid(p_length=4): def generate_uid(p_length=4):
""" """
Generates a random string of the given length, used as Generates a random string of the given length, used as
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -29,6 +29,7 @@ from topydo.lib.Config import config ...@@ -29,6 +29,7 @@ from topydo.lib.Config import config
IMPORTANCE_VALUE = {'A': 3, 'B': 2, 'C': 1} IMPORTANCE_VALUE = {'A': 3, 'B': 2, 'C': 1}
def is_due_next_monday(p_todo): def is_due_next_monday(p_todo):
""" Returns True when the given task is due next Monday. """ """ Returns True when the given task is due next Monday. """
today = date.today() today = date.today()
...@@ -37,6 +38,7 @@ def is_due_next_monday(p_todo): ...@@ -37,6 +38,7 @@ def is_due_next_monday(p_todo):
return due and due.weekday() == 0 and today.weekday() >= 4 and \ return due and due.weekday() == 0 and today.weekday() >= 4 and \
p_todo.days_till_due() p_todo.days_till_due()
def importance(p_todo, p_ignore_weekend=config().ignore_weekends()): def importance(p_todo, p_ignore_weekend=config().ignore_weekends()):
""" """
Calculates the importance of the given task. Calculates the importance of the given task.
...@@ -74,6 +76,7 @@ def importance(p_todo, p_ignore_weekend=config().ignore_weekends()): ...@@ -74,6 +76,7 @@ def importance(p_todo, p_ignore_weekend=config().ignore_weekends()):
return result if not p_todo.is_completed() else 0 return result if not p_todo.is_completed() else 0
def average_importance(p_todo, p_ignore_weekend=config().ignore_weekends()): def average_importance(p_todo, p_ignore_weekend=config().ignore_weekends()):
own_importance = importance(p_todo, p_ignore_weekend) own_importance = importance(p_todo, p_ignore_weekend)
......
...@@ -23,6 +23,7 @@ import json ...@@ -23,6 +23,7 @@ import json
from topydo.lib.PrettyPrinter import Printer from topydo.lib.PrettyPrinter import Printer
def _convert_todo(p_todo): def _convert_todo(p_todo):
""" Converts a Todo instance to a dictionary. """ """ Converts a Todo instance to a dictionary. """
creation_date = p_todo.creation_date() creation_date = p_todo.creation_date()
...@@ -44,10 +45,12 @@ def _convert_todo(p_todo): ...@@ -44,10 +45,12 @@ def _convert_todo(p_todo):
return result return result
class JsonPrinter(Printer): class JsonPrinter(Printer):
""" """
A printer that converts a list of Todo items to a string in JSON format. A printer that converts a list of Todo items to a string in JSON format.
""" """
def __init__(self): def __init__(self):
super(JsonPrinter, self).__init__() 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 @@ ...@@ -14,17 +14,16 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from six import u
from topydo.lib.ExpressionCommand import ExpressionCommand from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
class MultiCommand(ExpressionCommand): class MultiCommand(ExpressionCommand):
""" """
A common class for operations that can work with multiple todo IDs. A common class for operations that can work with multiple todo IDs.
""" """
def __init__(self, p_args, p_todolist, def __init__(self, p_args, p_todolist, #pragma: no branch
p_out=lambda a: None, p_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
...@@ -42,7 +41,7 @@ class MultiCommand(ExpressionCommand): ...@@ -42,7 +41,7 @@ class MultiCommand(ExpressionCommand):
def process_flag(self, p_option, p_value): def process_flag(self, p_option, p_value):
""" Default implementation of processing specific flags. """ """ Default implementation of processing specific flags. """
pass raise NotImplementedError
def _process_flags(self): def _process_flags(self):
opts, long_opts = self.get_flags() opts, long_opts = self.get_flags()
...@@ -62,7 +61,7 @@ class MultiCommand(ExpressionCommand): ...@@ -62,7 +61,7 @@ class MultiCommand(ExpressionCommand):
self.todos = self._view().todos self.todos = self._view().todos
def get_todos(self): def get_todos(self):
""" Gets todo objects from supplied todo IDs """ """ Gets todo objects from supplied todo IDs. """
if self.is_expression: if self.is_expression:
self.get_todos_from_expr() self.get_todos_from_expr()
else: else:
...@@ -79,17 +78,17 @@ class MultiCommand(ExpressionCommand): ...@@ -79,17 +78,17 @@ class MultiCommand(ExpressionCommand):
def _catch_todo_errors(self): def _catch_todo_errors(self):
""" """
Returns None or list of error messages depending on number of valid todo Returns None or list of error messages depending on number of valid
objects and number of invalid todo IDs. todo objects and number of invalid todo IDs.
In case of multiple invalid todo IDs we generate separate error message for each In case of multiple invalid todo IDs we generate separate error message
one of them with information about supplied ID. for each one of them with information about supplied ID.
""" """
errors = [] errors = []
if len(self.invalid_numbers) > 1 or len(self.invalid_numbers) > 0 and len(self.todos) > 0: if len(self.invalid_numbers) > 1 or len(self.invalid_numbers) > 0 and len(self.todos) > 0:
for number in self.invalid_numbers: 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: elif len(self.invalid_numbers) == 1 and len(self.todos) == 0:
errors.append("Invalid todo number given.") errors.append("Invalid todo number given.")
elif len(self.todos) == 0 and len(self.invalid_numbers) == 0: elif len(self.todos) == 0 and len(self.invalid_numbers) == 0:
...@@ -105,14 +104,14 @@ class MultiCommand(ExpressionCommand): ...@@ -105,14 +104,14 @@ class MultiCommand(ExpressionCommand):
Operations specific for particular command dealing with multiple todo Operations specific for particular command dealing with multiple todo
IDs. IDs.
""" """
pass raise NotImplementedError
def _execute_not_multi(self): def _execute_not_multi(self):
""" """
Some commands can do something else besides operating on multiple todo Some commands can do something else besides operating on multiple todo
IDs. This method is a wrapper for those other operations. IDs. This method is a wrapper for those other operations.
""" """
pass raise NotImplementedError
def execute(self): def execute(self):
if not super(MultiCommand, self).execute(): if not super(MultiCommand, self).execute():
......
...@@ -14,10 +14,9 @@ ...@@ -14,10 +14,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.PrettyPrinterFilter import ( from topydo.lib.prettyprinters.Colors import PrettyPrinterColorFilter
PrettyPrinterColorFilter, from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
PrettyPrinterNumbers
)
class Printer(object): class Printer(object):
""" """
...@@ -25,6 +24,7 @@ class Printer(object): ...@@ -25,6 +24,7 @@ class Printer(object):
Subclasses must at least implement the print_todo method. Subclasses must at least implement the print_todo method.
""" """
def print_todo(self, p_todo): def print_todo(self, p_todo):
raise NotImplementedError raise NotImplementedError
...@@ -35,6 +35,7 @@ class Printer(object): ...@@ -35,6 +35,7 @@ class Printer(object):
""" """
return "\n".join([self.print_todo(todo) for todo in p_todos]) return "\n".join([self.print_todo(todo) for todo in p_todos])
class PrettyPrinter(Printer): class PrettyPrinter(Printer):
""" """
Prints todo items on a single line, decorated by the filters passed by Prints todo items on a single line, decorated by the filters passed by
...@@ -44,6 +45,7 @@ class PrettyPrinter(Printer): ...@@ -44,6 +45,7 @@ class PrettyPrinter(Printer):
add colors, indentation, etc. These filters are found in the add colors, indentation, etc. These filters are found in the
PrettyPrinterFilter module. PrettyPrinterFilter module.
""" """
def __init__(self): def __init__(self):
""" """
Constructor. Constructor.
...@@ -68,9 +70,9 @@ class PrettyPrinter(Printer): ...@@ -68,9 +70,9 @@ class PrettyPrinter(Printer):
return todo_str return todo_str
def pretty_printer_factory(p_todolist, p_additional_filters=None): def pretty_printer_factory(p_todolist, p_additional_filters=None):
""" Returns a pretty printer suitable for the ls and dep subcommands. """ """ Returns a pretty printer suitable for the ls and dep subcommands. """
p_additional_filters = p_additional_filters or [] p_additional_filters = p_additional_filters or []
printer = PrettyPrinter() printer = PrettyPrinter()
......
...@@ -14,19 +14,14 @@ ...@@ -14,19 +14,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" 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): class PrettyPrinterFilter(object):
""" """
Base class for a pretty printer filter. 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, _): def filter(self, p_todo_str, _):
...@@ -35,85 +30,3 @@ class PrettyPrinterFilter(object): ...@@ -35,85 +30,3 @@ class PrettyPrinterFilter(object):
""" """
raise NotImplementedError 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. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -22,9 +22,11 @@ from topydo.lib.Config import config ...@@ -22,9 +22,11 @@ from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
class NoRecurrenceException(Exception): class NoRecurrenceException(Exception):
pass pass
def advance_recurring_todo(p_todo, p_offset=None, p_strict=False): 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 Given a Todo item, return a new instance of a Todo item with the dates
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,9 +16,10 @@ ...@@ -16,9 +16,10 @@
""" This module deals with relative dates (2d, 5y, Monday, today, etc.) """ """ This module deals with relative dates (2d, 5y, Monday, today, etc.) """
from datetime import date, timedelta
import calendar import calendar
import re import re
from datetime import date, timedelta
def _add_months(p_sourcedate, p_months): def _add_months(p_sourcedate, p_months):
""" """
...@@ -35,6 +36,7 @@ def _add_months(p_sourcedate, p_months): ...@@ -35,6 +36,7 @@ def _add_months(p_sourcedate, p_months):
return date(year, month, day) return date(year, month, day)
def _convert_pattern(p_length, p_periodunit, p_offset=None): def _convert_pattern(p_length, p_periodunit, p_offset=None):
""" """
Converts a pattern in the form [0-9][dwmy] and returns a date from the 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): ...@@ -56,12 +58,12 @@ def _convert_pattern(p_length, p_periodunit, p_offset=None):
return result return result
def _convert_weekday_pattern(p_weekday): def _convert_weekday_pattern(p_weekday):
""" """
Converts a weekday name to an absolute date. Converts a weekday name to an absolute date.
When today's day of the week is entered, it will return today and not next When today's day of the week is entered, it will return next week's date.
week's.
""" """
day_value = { day_value = {
'mo': 0, 'mo': 0,
...@@ -78,9 +80,10 @@ def _convert_weekday_pattern(p_weekday): ...@@ -78,9 +80,10 @@ def _convert_weekday_pattern(p_weekday):
day = date.today().weekday() day = date.today().weekday()
shift = (target_day - day) % 7 shift = 7 - (day - target_day) % 7
return date.today() + timedelta(shift) return date.today() + timedelta(shift)
def relative_date_to_date(p_date, p_offset=None): def relative_date_to_date(p_date, p_offset=None):
""" """
Transforms a relative date into a date object. Transforms a relative date into a date object.
...@@ -88,10 +91,9 @@ def relative_date_to_date(p_date, p_offset=None): ...@@ -88,10 +91,9 @@ def relative_date_to_date(p_date, p_offset=None):
The following formats are understood: The following formats are understood:
* [0-9][dwmy] * [0-9][dwmy]
* 'today' or 'tomorrow' * 'yesterday', 'today' or 'tomorrow'
* days of the week (in full or abbreviated) * days of the week (in full or abbreviated)
""" """
result = None result = None
p_date = p_date.lower() p_date = p_date.lower()
p_offset = p_offset or date.today() p_offset = p_offset or date.today()
...@@ -124,4 +126,7 @@ def relative_date_to_date(p_date, p_offset=None): ...@@ -124,4 +126,7 @@ def relative_date_to_date(p_date, p_offset=None):
elif re.match('tom(orrow)?$', p_date): elif re.match('tom(orrow)?$', p_date):
result = _convert_pattern('1', 'd') result = _convert_pattern('1', 'd')
elif re.match('yes(terday)?$', p_date):
result = _convert_pattern('-1', 'd')
return result return result
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -16,15 +16,17 @@ ...@@ -16,15 +16,17 @@
""" This module provides functionality to sort lists with todo items. """ """ This module provides functionality to sort lists with todo items. """
from datetime import date
import re 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): def is_priority_field(p_field):
""" Returns True when the field name denotes the priority. """ """ Returns True when the field name denotes the priority. """
return p_field.startswith('prio') return p_field.startswith('prio')
def get_field_function(p_field): def get_field_function(p_field):
""" """
Given a property (string) of a todo, return a function that attempts to Given a property (string) of a todo, return a function that attempts to
...@@ -36,29 +38,34 @@ def get_field_function(p_field): ...@@ -36,29 +38,34 @@ def get_field_function(p_field):
if is_priority_field(p_field): if is_priority_field(p_field):
# assign dummy priority when a todo has no priority # assign dummy priority when a todo has no priority
result = lambda a: a.priority() or 'ZZ' 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': elif p_field == 'creationdate' or p_field == 'creation':
# when a task has no creation date, push it to the end by assigning it # when a task has no creation date, push it to the end by assigning it
# the maximum possible date. # the maximum possible date.
result = (lambda a: a.creation_date() if a.creation_date() \ result = (lambda a: a.creation_date() if a.creation_date()
else date.max) else date.max)
elif p_field == 'done' or p_field == 'completed' or p_field == 'completion': elif p_field == 'done' or p_field == 'completed' or p_field == 'completion':
result = (lambda a: a.completion_date() if a.completion_date() \ result = (lambda a: a.completion_date() if a.completion_date()
else date.max) else date.max)
elif p_field == 'importance': elif p_field == 'importance':
result = importance result = importance
elif p_field == 'importance-avg' or p_field == 'importance-average': elif p_field == 'importance-avg' or p_field == 'importance-average':
result = average_importance 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': elif p_field == 'text':
result = lambda a: a.text() result = lambda a: a.text()
else: else:
# try to find the corresponding tag # try to find the corresponding tag
# when a tag is not present, push it to the end of the list by giving # when a tag is not present, push it to the end of the list by giving
# it an artificially higher value # it an artificially higher value
result = (lambda a: "0" + a.tag_value(p_field) if a.has_tag(p_field) \ result = (lambda a: "0" + a.tag_value(p_field) if a.has_tag(p_field)
else "1") else "1")
return result return result
class Sorter(object): class Sorter(object):
""" """
This class sorts a todo list. This class sorts a todo list.
...@@ -83,6 +90,7 @@ class Sorter(object): ...@@ -83,6 +90,7 @@ class Sorter(object):
specific search is done first. This relies on the fact that sorting is specific search is done first. This relies on the fact that sorting is
stable. stable.
""" """
def __init__(self, p_sortstring="desc:priority"): def __init__(self, p_sortstring="desc:priority"):
self.sortstring = p_sortstring self.sortstring = p_sortstring
self.functions = [] self.functions = []
...@@ -97,7 +105,6 @@ class Sorter(object): ...@@ -97,7 +105,6 @@ class Sorter(object):
sort operation is done first, relying on the stability of the sorted() sort operation is done first, relying on the stability of the sorted()
function. function.
""" """
sorted_todos = p_todos sorted_todos = p_todos
for function, order in reversed(self.functions): for function, order in reversed(self.functions):
sorted_todos = sorted(sorted_todos, key=function, sorted_todos = sorted(sorted_todos, key=function,
...@@ -113,8 +120,8 @@ class Sorter(object): ...@@ -113,8 +120,8 @@ class Sorter(object):
fields = self.sortstring.lower().split(',') fields = self.sortstring.lower().split(',')
for field in fields: for field in fields:
parsed_field = re.match( \ parsed_field = re.match(
r'(?P<order>(asc|desc)(ending)?:)?(?P<field>\S+)', \ r'(?P<order>(asc|desc)(ending)?:)?(?P<field>\S+)',
field) field)
if not parsed_field: if not parsed_field:
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -24,6 +24,7 @@ from topydo.lib.Config import config ...@@ -24,6 +24,7 @@ from topydo.lib.Config import config
from topydo.lib.TodoBase import TodoBase from topydo.lib.TodoBase import TodoBase
from topydo.lib.Utils import date_string_to_date from topydo.lib.Utils import date_string_to_date
class Todo(TodoBase): class Todo(TodoBase):
""" """
This class adds common functionality with respect to dates to the Todo This class adds common functionality with respect to dates to the Todo
...@@ -84,9 +85,10 @@ class Todo(TodoBase): ...@@ -84,9 +85,10 @@ class Todo(TodoBase):
def length(self): def length(self):
""" """
Returns the length (in days) of the task, by considering the start date 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() due = self.due_date()
if start and due and start < due: if start and due and start < due:
...@@ -94,4 +96,3 @@ class Todo(TodoBase): ...@@ -94,4 +96,3 @@ class Todo(TodoBase):
return diff.days return diff.days
else: else:
return 0 return 0
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -18,13 +18,13 @@ ...@@ -18,13 +18,13 @@
This module contains the class that represents a single todo item. This module contains the class that represents a single todo item.
""" """
from datetime import date
import re import re
from six import u from datetime import date
from topydo.lib.TodoParser import parse_line from topydo.lib.TodoParser import parse_line
from topydo.lib.Utils import is_valid_priority from topydo.lib.Utils import is_valid_priority
class TodoBase(object): class TodoBase(object):
""" """
This class represents a single todo item in a todo.txt file. It maintains This class represents a single todo item in a todo.txt file. It maintains
...@@ -56,7 +56,6 @@ class TodoBase(object): ...@@ -56,7 +56,6 @@ class TodoBase(object):
Returns a list of all tag values associated with p_key. Returns Returns a list of all tag values associated with p_key. Returns
empty list if p_key does not exist. empty list if p_key does not exist.
""" """
tags = self.fields['tags'] tags = self.fields['tags']
matches = [tag[1] for tag in tags if tag[0] == p_key] matches = [tag[1] for tag in tags if tag[0] == p_key]
return matches if len(matches) else [] return matches if len(matches) else []
...@@ -67,9 +66,8 @@ class TodoBase(object): ...@@ -67,9 +66,8 @@ class TodoBase(object):
value is passed, it will only return true when there exists a tag with value is passed, it will only return true when there exists a tag with
the given key-value combination. the given key-value combination.
""" """
result = [t for t in self.tag_values(p_key)
result = [t for t in self.tag_values(p_key) \ if p_value == "" or t == p_value]
if p_value == "" or t == p_value]
return len(result) > 0 return len(result) > 0
def add_tag(self, p_key, p_value): def add_tag(self, p_key, p_value):
...@@ -90,7 +88,6 @@ class TodoBase(object): ...@@ -90,7 +88,6 @@ class TodoBase(object):
When p_old_value is set, all tags having this value will be set to the When p_old_value is set, all tags having this value will be set to the
new value. new value.
""" """
if p_value == "": if p_value == "":
self.remove_tag(p_key, p_old_value) self.remove_tag(p_key, p_old_value)
return return
...@@ -99,8 +96,8 @@ class TodoBase(object): ...@@ -99,8 +96,8 @@ class TodoBase(object):
if not p_force_add and value: if not p_force_add and value:
# remove old value from the tags # remove old value from the tags
self.fields['tags'] = [t for t in self.fields['tags'] \ self.fields['tags'] = [t for t in self.fields['tags']
if not (t[0] == p_key and t[1] == value)] if not (t[0] == p_key and t[1] == value)]
self.src = re.sub( self.src = re.sub(
r'\b' + p_key + ':' + value + r'\b', r'\b' + p_key + ':' + value + r'\b',
...@@ -122,8 +119,9 @@ class TodoBase(object): ...@@ -122,8 +119,9 @@ class TodoBase(object):
# Build a new list that excludes the specified tag, match by value when # Build a new list that excludes the specified tag, match by value when
# p_value is given. # p_value is given.
self.fields['tags'] = [t for t in self.fields['tags'] \ self.fields['tags'] = [t for t in self.fields['tags']
if not (t[0] == p_key and (p_value == "" or t[1] == p_value))] if not (t[0] == p_key and (p_value == "" or
t[1] == p_value))]
# when value == "", match any value having key p_key # when value == "", match any value having key p_key
value = p_value if p_value != "" else r'\S+' value = p_value if p_value != "" else r'\S+'
...@@ -143,13 +141,11 @@ class TodoBase(object): ...@@ -143,13 +141,11 @@ class TodoBase(object):
Priority remains unchanged when an invalid priority is given, or when Priority remains unchanged when an invalid priority is given, or when
the task was completed. the task was completed.
""" """
if not self.is_completed() and (p_priority is None or
if not self.is_completed() and \ is_valid_priority(p_priority)):
(p_priority == None or is_valid_priority(p_priority)):
self.fields['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) self.src = re.sub(r'^(\([A-Z]\) )?', priority_str, self.src)
def priority(self): def priority(self):
...@@ -204,8 +200,9 @@ class TodoBase(object): ...@@ -204,8 +200,9 @@ class TodoBase(object):
self.fields['completed'] = True self.fields['completed'] = True
self.fields['completionDate'] = p_completion_date self.fields['completionDate'] = p_completion_date
self.src = re.sub(r'^(\([A-Z]\) )?', \ self.src = re.sub(r'^(\([A-Z]\) )?',
'x ' + p_completion_date.isoformat() + ' ', self.src) 'x ' + p_completion_date.isoformat() + ' ',
self.src)
def set_creation_date(self, p_date=date.today()): def set_creation_date(self, p_date=date.today()):
""" """
...@@ -213,16 +210,15 @@ class TodoBase(object): ...@@ -213,16 +210,15 @@ class TodoBase(object):
""" """
self.fields['creationDate'] = p_date 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 # http://bugs.python.org/issue1519638 non-existent matches trigger
# exceptions, hence the lambda # exceptions, hence the lambda
self.src = re.sub( self.src = re.sub(
r'^(x \d{4}-\d{2}-\d{2} |\([A-Z]\) )?(\d{4}-\d{2}-\d{2} )?(.*)$', r'^(x \d{4}-\d{2}-\d{2} |\([A-Z]\) )?(\d{4}-\d{2}-\d{2} )?(.*)$',
lambda m: \ lambda m:
u("{}{} {}").format(m.group(1) or '', p_date.isoformat(), m.group(3)), u"{}{} {}".format(m.group(1) or '', p_date.isoformat(),
self.src) m.group(3)), self.src)
def creation_date(self): def creation_date(self):
""" Returns the creation date of a todo. """ """ Returns the creation date of a todo. """
return self.fields['creationDate'] return self.fields['creationDate']
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -20,6 +20,7 @@ This module deals with todo.txt files. ...@@ -20,6 +20,7 @@ This module deals with todo.txt files.
import codecs import codecs
class TodoFile(object): class TodoFile(object):
""" """
This class represents a todo.txt file, which can be read from or written This class represents a todo.txt file, which can be read from or written
...@@ -48,7 +49,6 @@ class TodoFile(object): ...@@ -48,7 +49,6 @@ class TodoFile(object):
p_todos can be a list of todo items, or a string that is just written p_todos can be a list of todo items, or a string that is just written
to the file. to the file.
""" """
todofile = codecs.open(self.path, 'w', encoding="utf-8") todofile = codecs.open(self.path, 'w', encoding="utf-8")
if p_todos is list: if p_todos is list:
......
...@@ -22,6 +22,7 @@ from topydo.lib.Config import config ...@@ -22,6 +22,7 @@ from topydo.lib.Config import config
from topydo.lib.Graph import DirectedGraph from topydo.lib.Graph import DirectedGraph
from topydo.lib.TodoListBase import TodoListBase from topydo.lib.TodoListBase import TodoListBase
class TodoList(TodoListBase): class TodoList(TodoListBase):
""" """
Provides operations for a todo list, such as adding items, removing them, Provides operations for a todo list, such as adding items, removing them,
...@@ -37,7 +38,7 @@ class TodoList(TodoListBase): ...@@ -37,7 +38,7 @@ class TodoList(TodoListBase):
The string will be parsed. The string will be parsed.
""" """
# initialize these first because the constructor calls add_list # initialize these first because the constructor calls add_list
self._tododict = {} # hash(todo) to todo lookup self._tododict = {} # hash(todo) to todo lookup
self._depgraph = DirectedGraph() self._depgraph = DirectedGraph()
super(TodoList, self).__init__(p_todostrings) super(TodoList, self).__init__(p_todostrings)
...@@ -57,7 +58,6 @@ class TodoList(TodoListBase): ...@@ -57,7 +58,6 @@ class TodoList(TodoListBase):
Makes sure that the dependency graph is consistent according to the Makes sure that the dependency graph is consistent according to the
given todo. given todo.
""" """
dep_id = p_todo.tag_value('id') dep_id = p_todo.tag_value('id')
# maintain dependency graph # maintain dependency graph
if dep_id: if dep_id:
...@@ -66,7 +66,7 @@ class TodoList(TodoListBase): ...@@ -66,7 +66,7 @@ class TodoList(TodoListBase):
# connect all tasks we have in memory so far that refer to this # connect all tasks we have in memory so far that refer to this
# task # task
for dep in \ 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) self._depgraph.add_edge(hash(p_todo), hash(dep), dep_id)
...@@ -114,8 +114,7 @@ class TodoList(TodoListBase): ...@@ -114,8 +114,7 @@ class TodoList(TodoListBase):
""" """
def id_exists(p_id): def id_exists(p_id):
""" """
Returns True if there exists a todo with the given parent Returns True if there exists a todo with the given parent ID.
ID.
""" """
for todo in self._todos: for todo in self._todos:
if todo.has_tag('id', str(p_id)): if todo.has_tag('id', str(p_id)):
...@@ -148,7 +147,7 @@ class TodoList(TodoListBase): ...@@ -148,7 +147,7 @@ class TodoList(TodoListBase):
self.append(p_to_todo, "@{}".format(context)) self.append(p_to_todo, "@{}".format(context))
if p_from_todo != p_to_todo and not self._depgraph.has_edge( 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 dep_id = None
if p_from_todo.has_tag('id'): if p_from_todo.has_tag('id'):
...@@ -204,19 +203,39 @@ class TodoList(TodoListBase): ...@@ -204,19 +203,39 @@ class TodoList(TodoListBase):
graph and removing unused dependency ids from the graph (in that graph and removing unused dependency ids from the graph (in that
order). order).
""" """
def clean_by_tag(tag_name): def remove_tag(p_todo, p_tag, p_value):
""" Generic function to handle 'p' and 'id' tags. """ """
for todo in [todo for todo in self._todos Removes a tag from a todo item.
if todo.has_tag(tag_name)]: """
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): if not self._depgraph.has_edge_id(value):
todo.remove_tag(tag_name, value) remove_tag(todo, 'id', value)
self.dirty = True
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() self._depgraph.transitively_reduce()
clean_by_tag('p') clean_parent_relations()
clean_by_tag('id') clean_orphan_relations()
def _update_parent_cache(self): def _update_parent_cache(self):
""" """
...@@ -225,6 +244,5 @@ class TodoList(TodoListBase): ...@@ -225,6 +244,5 @@ class TodoList(TodoListBase):
This is used for calculating the average importance, that requires This is used for calculating the average importance, that requires
access to a todo's parents. access to a todo's parents.
""" """
for todo in self._todos: for todo in self._todos:
todo.attributes['parents'] = self.parents(todo) todo.attributes['parents'] = self.parents(todo)
...@@ -18,20 +18,21 @@ ...@@ -18,20 +18,21 @@
A list of todo items. A list of todo items.
""" """
from datetime import date
import re 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 import Filter
from topydo.lib.Config import config
from topydo.lib.HashListValues import hash_list_values from topydo.lib.HashListValues import hash_list_values
from topydo.lib.PrettyPrinter import PrettyPrinter from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from topydo.lib.View import View from topydo.lib.View import View
class InvalidTodoException(Exception): class InvalidTodoException(Exception):
pass pass
class TodoListBase(object): class TodoListBase(object):
""" """
Provides operations for a todo list, such as adding items, removing them, Provides operations for a todo list, such as adding items, removing them,
...@@ -75,7 +76,7 @@ class TodoListBase(object): ...@@ -75,7 +76,7 @@ class TodoListBase(object):
try: try:
result = self._id_todo_map[p_identifier] result = self._id_todo_map[p_identifier]
except KeyError: except KeyError:
pass # we'll try something else pass # we'll try something else
return result return result
...@@ -83,10 +84,9 @@ class TodoListBase(object): ...@@ -83,10 +84,9 @@ class TodoListBase(object):
""" """
Attempts to find the todo on the given line number. 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. will be None.
""" """
result = None result = None
if config().identifiers() != 'text': if config().identifiers() != 'text':
...@@ -126,12 +126,14 @@ class TodoListBase(object): ...@@ -126,12 +126,14 @@ class TodoListBase(object):
if not result: if not result:
# convert integer to text so we pass on a valid regex # 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 return result
def add(self, p_src): 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]) todos = self.add_list([p_src])
return todos[0] if len(todos) else None return todos[0] if len(todos) else None
...@@ -169,6 +171,12 @@ class TodoListBase(object): ...@@ -169,6 +171,12 @@ class TodoListBase(object):
self._todos = [] self._todos = []
self.dirty = True 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): def count(self):
""" Returns the number of todos on this list. """ """ Returns the number of todos on this list. """
return len(self._todos) return len(self._todos)
...@@ -246,8 +254,8 @@ class TodoListBase(object): ...@@ -246,8 +254,8 @@ class TodoListBase(object):
def _update_todo_ids(self): def _update_todo_ids(self):
# the idea is to have a hash that is independent of the position of the # 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 # todo. Use the text (without tags) of the todo to keep the id as
# as possible (not influenced by priorities or due dates, etc.) # stable as possible (not influenced by priorities or due dates, etc.)
self._todo_id_map = {} self._todo_id_map = {}
self._id_todo_map = {} self._id_todo_map = {}
......
# Topydo - A todo.txt client written in Python. # 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 # 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 # 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>.+)') ...@@ -37,13 +37,14 @@ _TAG_MATCH = re.compile('(?P<key>[^:]+):(?P<value>.+)')
_PROJECT_MATCH = re.compile(r'\+(\S*\w)') _PROJECT_MATCH = re.compile(r'\+(\S*\w)')
_CONTEXT_MATCH = re.compile(r'@(\S*\w)') _CONTEXT_MATCH = re.compile(r'@(\S*\w)')
def parse_line(p_string): def parse_line(p_string):
""" """
Parses a single line as can be encountered in a todo.txt file. Parses a single line as can be encountered in a todo.txt file.
First checks whether the standard elements are present, such as priority, First checks whether the standard elements are present, such as priority,
creation date, completeness check and the completion date. 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. tags.
Returns an dictionary with the default values as shown below. Returns an dictionary with the default values as shown below.
...@@ -102,4 +103,3 @@ def parse_line(p_string): ...@@ -102,4 +103,3 @@ def parse_line(p_string):
result['text'] = result['text'][:-1] result['text'] = result['text'][:-1]
return result return result
# Topydo - A todo.txt client written in Python. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -18,9 +18,18 @@ ...@@ -18,9 +18,18 @@
Various utility functions. Various utility functions.
""" """
from datetime import date
import re 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): def date_string_to_date(p_date):
""" """
Given a date in YYYY-MM-DD, returns a Python date object. Returns None 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): ...@@ -32,19 +41,39 @@ def date_string_to_date(p_date):
parsed_date = re.match(r'(\d{4})-(\d{2})-(\d{2})', p_date) parsed_date = re.match(r'(\d{4})-(\d{2})-(\d{2})', p_date)
if parsed_date: if parsed_date:
result = date( result = date(
int(parsed_date.group(1)), # year int(parsed_date.group(1)), # year
int(parsed_date.group(2)), # month int(parsed_date.group(2)), # month
int(parsed_date.group(3)) # day int(parsed_date.group(3)) # day
) )
else: else:
raise ValueError raise ValueError
return result return result
def is_valid_priority(p_priority): 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): def escape_ansi(p_string):
return escape_ansi.pattern.sub('', p_string) return escape_ansi.pattern.sub('', p_string)
escape_ansi.pattern = re.compile(r'\x1b[^m]*m') 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 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>. 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. This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.""" There is NO WARRANTY, to the extent permitted by law."""
...@@ -16,12 +16,14 @@ ...@@ -16,12 +16,14 @@
""" A view is a list of todos, sorted and filtered. """ """ A view is a list of todos, sorted and filtered. """
class View(object): class View(object):
""" """
A view is instantiated by a todo list, usually obtained from a todo.txt A view is instantiated by a todo list, usually obtained from a todo.txt
file. Also a sorter and a list of filters should be given that is applied file. Also a sorter and a list of filters should be given that is applied
to the list. to the list.
""" """
def __init__(self, p_sorter, p_filters, p_todolist): def __init__(self, p_sorter, p_filters, p_todolist):
self.todolist = p_todolist self.todolist = p_todolist
self._sorter = p_sorter self._sorter = p_sorter
......
# 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. # 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 # 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 # it under the terms of the GNU General Public License as published by
...@@ -14,28 +14,19 @@ ...@@ -14,28 +14,19 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest """ Provides a pretty printer filter that inserts todo numbers. """
from topydo.lib.Config import config from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter
from test.TopydoTest import TopydoTest
class ConfigTest(TopydoTest):
def test_config1(self):
self.assertEqual(config("test/data/config1").default_command(), 'do')
def test_config2(self): class PrettyPrinterNumbers(PrettyPrinterFilter):
self.assertNotEqual(config("").default_command(), 'do') """ Prepends the todo's number, retrieved from the todolist. """
def test_config3(self): def __init__(self, p_todolist):
self.assertTrue(config("test/data/config2").ignore_weekends()) super(PrettyPrinterNumbers, self).__init__()
self.todolist = p_todolist
def test_config4(self): def filter(self, p_todo_str, p_todo):
""" Test that value in file is overridden by parameter. """ """ Prepends the number to the todo string. """
overrides = { return "|{:>3}| {}".format(self.todolist.number(p_todo), p_todo_str)
('topydo', 'default_command'): 'edit'
}
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