Commit c6e098e3 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'style-fixes'

parents 486aa7be 8ca31100
[settings]
skip=topydo/ui/CLIApplicationBase.py
known_first_party=topydo
......@@ -22,9 +22,11 @@ install:
- "pip install .[test]"
- "pip install pylint"
- "pip install codecov"
- "pip install isort"
script:
- "green -vvr $GREEN_OPTS"
- "python -m pylint --errors-only topydo test"
- "isort -c -rc ."
# Cache Dependencies
after_script:
- codecov
......
from setuptools import setup, find_packages
import codecs
import os
import re
import codecs
import sys
here = os.path.abspath(os.path.dirname(__file__))
from setuptools import find_packages, setup
_HERE = os.path.abspath(os.path.dirname(__file__))
def read(*parts):
# intentionally *not* adding an encoding option to open
return codecs.open(os.path.join(here, *parts), 'r').read()
return codecs.open(os.path.join(_HERE, *parts), 'r').read()
def find_version(*file_paths):
......@@ -24,17 +24,17 @@ WATCHDOG = 'watchdog >= 0.8.3'
ICALENDAR = 'icalendar'
setup(
name = "topydo",
packages = find_packages(exclude=["test"]),
version = find_version('topydo', 'lib', 'Version.py'),
description = "A powerful todo.txt application for the console",
author = "Bram Schoenmakers",
author_email = "bram@topydo.org",
url = "https://www.topydo.org",
install_requires = [
name="topydo",
packages=find_packages(exclude=["test"]),
version=find_version('topydo', 'lib', 'Version.py'),
description="A powerful todo.txt application for the console",
author="Bram Schoenmakers",
author_email="bram@topydo.org",
url="https://www.topydo.org",
install_requires=[
'arrow >= 0.7.0',
],
extras_require = {
extras_require={
':sys_platform=="win32"': ['colorama>=0.2.5'],
':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'],
'columns': ['urwid >= 1.3.0', WATCHDOG],
......@@ -43,10 +43,10 @@ setup(
'test': ['coverage>=4.3', 'freezegun', 'green', ICALENDAR, 'pylint>=1.7.1'],
'test:python_version=="3.2"': ['mock'],
},
entry_points= {
'console_scripts': ['topydo = topydo.ui.UILoader:main'],
entry_points={
'console_scripts': ['topydo=topydo.ui.UILoader:main'],
},
classifiers = [
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
......@@ -60,7 +60,7 @@ setup(
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Utilities",
],
long_description = """\
long_description="""\
topydo is a powerful and customizable todo.txt application for the console, inspired by the todo.txt CLI by Gina Trapani.
Highlights of the additional features it provides:
......@@ -74,5 +74,5 @@ Highlights of the additional features it provides:
* Some conveniences when adding new items (e.g. adding creation date and use relative dates)
""",
test_suite = "test",
test_suite="test",
)
......@@ -15,9 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from test.topydo_testcase import TopydoTest
from topydo.lib.Utils import escape_ansi
from .topydo_testcase import TopydoTest
class CommandTest(TopydoTest):
def __init__(self, *args, **kwargs):
......
......@@ -18,11 +18,12 @@ 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
from .command_testcase import CommandTest
# We're searching for 'mock'
# pylint: disable=no-name-in-module
try:
......
......@@ -17,10 +17,11 @@
import unittest
from datetime import date
from test.command_testcase import CommandTest
from topydo.commands.AppendCommand import AppendCommand
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
class AppendCommandTest(CommandTest):
def setUp(self):
......
......@@ -16,11 +16,12 @@
import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ArchiveCommand import ArchiveCommand
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
from .facilities import load_file_to_todolist
class ArchiveCommandTest(CommandTest):
def test_archive(self):
......
......@@ -18,11 +18,11 @@
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Color import Color
from topydo.lib.Config import config
from topydo.lib.Todo import Todo
from .topydo_testcase import TopydoTest
NEUTRAL_COLOR = '\033[0m'
......
......@@ -15,11 +15,11 @@
# 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
from .topydo_testcase import TopydoTest
class ConfigTest(TopydoTest):
def test_config01(self):
......@@ -167,7 +167,7 @@ class ConfigTest(TopydoTest):
def test_config28(self):
""" test duplicates. """
keymap, keystates = config("test/data/ConfigTest7.conf").column_keymap()
keymap, _ = config("test/data/ConfigTest7.conf").column_keymap()
self.assertEqual(keymap['k'], 'bar')
self.assertEqual(keymap['z'], 'foobar')
......
......@@ -16,12 +16,13 @@
import unittest
from test.command_testcase import CommandTest
from topydo.commands.DeleteCommand import DeleteCommand
from topydo.lib.Config import config
from topydo.lib.TodoList import TodoList
from topydo.lib.TodoListBase import InvalidTodoException
from .command_testcase import CommandTest
def _yes_prompt(self):
return "y"
......
......@@ -16,10 +16,11 @@
import unittest
from test.command_testcase import CommandTest
from topydo.commands.DepCommand import DepCommand
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
class DepCommandTest(CommandTest):
def setUp(self):
......
......@@ -16,10 +16,11 @@
import unittest
from test.command_testcase import CommandTest
from topydo.commands.DepriCommand import DepriCommand
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
class DepriCommandTest(CommandTest):
def setUp(self):
......
......@@ -17,10 +17,11 @@
import unittest
from datetime import date, timedelta
from test.command_testcase import CommandTest
from topydo.commands.DoCommand import DoCommand
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
def _yes_prompt(self):
return "y"
......@@ -100,34 +101,32 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "")
def test_do_subtasks_force1(self):
prompt_shown = False
def prompt(p_prompt):
global prompt_shown
prompt_shown = True
prompt.prompt_shown = True
prompt.prompt_shown = False
command = DoCommand(["-f", "1"], self.todolist, self.out, self.error,
prompt)
command.execute()
command.execute_post_archive_actions()
self.assertFalse(prompt_shown)
self.assertFalse(prompt.prompt_shown)
self.assertEqual(self.errors, "")
self.assertFalse(self.todolist.todo(2).is_completed())
def test_do_subtasks_force2(self):
prompt_shown = False
def prompt(p_prompt):
global prompt_shown
prompt_shown = True
prompt.prompt_shown = True
prompt.prompt_shown = False
command = DoCommand(["--force", "1"], self.todolist, self.out,
self.error, prompt)
command.execute()
command.execute_post_archive_actions()
self.assertFalse(prompt_shown)
self.assertFalse(prompt.prompt_shown)
self.assertEqual(self.errors, "")
self.assertFalse(self.todolist.todo(2).is_completed())
......
......@@ -17,12 +17,13 @@
import os
import unittest
from test.command_testcase import CommandTest
from topydo.commands.EditCommand import EditCommand
from topydo.lib.Config import config
from topydo.lib.Todo import Todo
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
# We're searching for 'mock'
# pylint: disable=no-name-in-module
try:
......
......@@ -19,12 +19,12 @@
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.Todo import Todo
from .facilities import load_file, load_file_to_todolist, todolist_to_string
from .topydo_testcase import TopydoTest
class FilterTest(TopydoTest):
def test_filter03(self):
......
......@@ -16,18 +16,20 @@
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.TagCommand import TagCommand
from topydo.lib.Config import config, ConfigError
from topydo.lib.Config import ConfigError, config
from .topydo_testcase import TopydoTest
class GetSubcommandTest(TopydoTest):
def test_normal_cmd(self):
args = ["add"]
real_cmd, final_args = get_subcommand(args)
real_cmd, _ = get_subcommand(args)
self.assertTrue(issubclass(real_cmd, AddCommand))
def test_cmd_help(self):
......@@ -116,7 +118,7 @@ class GetSubcommandTest(TopydoTest):
config("test/data/aliases.conf")
args = ["baz"]
real_cmd, final_args = get_subcommand(args)
real_cmd, _ = get_subcommand(args)
self.assertEqual(real_cmd, None)
def test_alias_quotation(self):
......
......@@ -16,9 +16,10 @@
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Graph import DirectedGraph
from .topydo_testcase import TopydoTest
class GraphTest(TopydoTest):
def setUp(self):
......
......@@ -15,14 +15,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from datetime import date, timedelta
from datetime import date
from freezegun import freeze_time
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
from topydo.lib.Importance import importance
from topydo.lib.Todo import Todo
from .topydo_testcase import TopydoTest
@freeze_time("2015, 11, 06")
class ImportanceTest(TopydoTest):
......
......@@ -16,10 +16,11 @@
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.printers.Json import JsonPrinter
from topydo.lib.Todo import Todo
from .topydo_testcase import TopydoTest
class JsonPrinterTest(TopydoTest):
"""
......
......@@ -15,19 +15,21 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import codecs
import re
import os
import re
import sys
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
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
from .facilities 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
......
......@@ -16,10 +16,11 @@
import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListContextCommand import ListContextCommand
from .command_testcase import CommandTest
from .facilities import load_file_to_todolist
class ListContextCommandTest(CommandTest):
def test_contexts1(self):
......
......@@ -15,16 +15,17 @@
# 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
from topydo.lib.TodoListBase import TodoListBase
from .command_testcase import CommandTest
from .facilities 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
......
......@@ -16,10 +16,11 @@
import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.ListProjectCommand import ListProjectCommand
from .command_testcase import CommandTest
from .facilities import load_file_to_todolist
class ListProjectCommandTest(CommandTest):
def test_projects1(self):
......
......@@ -17,10 +17,11 @@
import unittest
from datetime import date, timedelta
from test.command_testcase import CommandTest
from topydo.commands.PostponeCommand import PostponeCommand
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
class PostponeCommandTest(CommandTest):
def setUp(self):
......
......@@ -16,10 +16,11 @@
import unittest
from test.command_testcase import CommandTest
from topydo.commands.PriorityCommand import PriorityCommand
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
class PriorityCommandTest(CommandTest):
def setUp(self):
......
......@@ -14,15 +14,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from freezegun import freeze_time
import unittest
from test.topydo_testcase import TopydoTest
from freezegun import freeze_time
from topydo.lib.Config import config
from topydo.lib.ProgressColor import progress_color
from topydo.lib.Todo import Todo
from topydo.lib.TodoList import TodoList
from .topydo_testcase import TopydoTest
def set_256_colors():
config(p_overrides={('topydo', 'colors'): '256'})
......
......@@ -17,11 +17,12 @@
import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
from topydo.lib.Recurrence import NoRecurrenceException, advance_recurring_todo
from topydo.lib.Todo import Todo
from .topydo_testcase import TopydoTest
class RecurrenceTest(TopydoTest):
def setUp(self):
......
......@@ -16,11 +16,13 @@
import unittest
from datetime import date
from freezegun import freeze_time
from test.topydo_testcase import TopydoTest
from topydo.lib.RelativeDate import relative_date_to_date
from .topydo_testcase import TopydoTest
@freeze_time('2015, 11, 06')
class RelativeDateTester(TopydoTest):
......
......@@ -17,12 +17,12 @@
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 freezegun import freeze_time
from topydo.commands.AddCommand import AddCommand
from topydo.commands.ArchiveCommand import ArchiveCommand
from topydo.commands.DeleteCommand import DeleteCommand
......@@ -33,6 +33,35 @@ from topydo.lib.Config import config
from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
# 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 BackupSimulator(object):
def __init__(self, p_todolist, p_archive, p_timestamp, p_label):
self.backup = ChangeSet(p_todolist, p_archive, p_label)
self.backup.timestamp = p_timestamp
def save(self, p_todolist):
self.backup.save(p_todolist)
def command_executer(p_cmd, p_args, p_todolist, p_archive=None, *params):
command = p_cmd(p_args, p_todolist, *params)
command.execute()
if p_archive:
archive_command = ArchiveCommand(p_todolist, p_archive)
archive_command.execute()
@freeze_time('2015, 11, 06')
class RevertCommandTest(CommandTest):
def setUp(self):
super().setUp()
......@@ -57,14 +86,8 @@ class RevertCommandTest(CommandTest):
self.archive = TodoList([])
def test_revert01(self):
backup = ChangeSet(p_label=['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()
backup = BackupSimulator(self.todolist, self.archive, '1', ['do 1'])
command_executer(DoCommand, ["1"], self.todolist, self.archive, self.out, self.error, None)
self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist)
......@@ -77,26 +100,22 @@ class RevertCommandTest(CommandTest):
result = TodoList(self.archive_file.read()).print_todos()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: do 1\n"))
self.assertTrue(self.output.endswith("Reverted to state before: 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()
backup = BackupSimulator(self.todolist, self.archive, '1', ['do 1'])
command_executer(DoCommand, ["1"], self.todolist, self.archive, self.out, self.error, None)
self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['do Bar'])
# Use add_todolist and add_archive to also cover them
backup = ChangeSet(p_label=['do Bar'])
backup.add_todolist(self.todolist)
backup.add_archive(self.archive)
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()
command_executer(DoCommand, ["Bar"], self.todolist, self.archive, self.out, self.error, None)
self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist)
......@@ -109,7 +128,7 @@ class RevertCommandTest(CommandTest):
result = TodoList(self.archive_file.read()).print_todos()
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: do Bar\n"))
self.assertTrue(self.output.endswith("Reverted to state before: do Bar\n"))
self.assertEqual(result, "x {} Foo".format(self.today))
self.assertEqual(self.todolist.print_todos(), "Bar\nBaz")
......@@ -120,51 +139,39 @@ class RevertCommandTest(CommandTest):
self.assertEqual(self.errors, "No backup was found for the current state of {}\n".format(config().todotxt()))
def test_revert04(self):
@mock.patch('topydo.lib.Config._Config.archive')
def test_revert04(self, mock_archive):
""" 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()
mock_archive.return_value = '' # test for empty archive setting
backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '4', ['add Four'])
command_executer(AddCommand, ["Four"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '5', ['add Five'])
command_executer(AddCommand, ["Five"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '6', ['add Six'])
command_executer(AddCommand, ["Six"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '7', ['add Seven'])
command_executer(AddCommand, ["Seven"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
result = len(ChangeSet().backup_dict.keys())
......@@ -182,94 +189,76 @@ class RevertCommandTest(CommandTest):
self.assertEqual(len(changesets), 4)
self.assertEqual(result, [])
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Seven\n"))
self.assertTrue(self.output.endswith("Reverted to state before: 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 = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '5', ['add Four'])
command_executer(AddCommand, ["Four"], self.todolist, None, self.out, self.error, None)
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.assertTrue(self.output.endswith("Reverted to state before: 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.assertTrue(self.output.endswith("Reverted to state before: 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.assertTrue(self.output.endswith("Reverted to state before: 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.assertTrue(self.output.endswith("Reverted to state before: 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.assertTrue(self.output.endswith("Reverted to state before: 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 = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
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 = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = ChangeSet()
......@@ -288,8 +277,7 @@ class RevertCommandTest(CommandTest):
""" Test backup when no archive file is set """
backup = ChangeSet(self.todolist, None, ['add One'])
backup.timestamp = '1'
command1 = AddCommand(["One"], self.todolist, self.out, self.error, None)
command1.execute()
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
changesets = list(backup.backup_dict.keys())
......@@ -298,6 +286,99 @@ class RevertCommandTest(CommandTest):
self.assertEqual(len(changesets), 1)
self.assertEqual(self.errors, "")
def test_revert_ls(self):
backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '5', ['add Four'])
command_executer(AddCommand, ["Four"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
command_executer(RevertCommand, ['ls'], self.todolist, None, self.out, self.error, None)
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith(""" 1| 1970-01-01 00:00:05 | add Four
2| 1970-01-01 00:00:04 | delete Three
3| 1970-01-01 00:00:03 | add Three
4| 1970-01-01 00:00:02 | add Two
5| 1970-01-01 00:00:01 | add One\n"""))
def test_revert_08(self):
backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '5', ['add Four'])
command_executer(AddCommand, ["Four"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
command_executer(RevertCommand, ['3'], self.todolist, None, self.out, self.error, None)
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Reverted to state before: add Three\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n2015-11-06 One\n2015-11-06 Two")
def test_revert_invalid(self):
""" Test invalid input for revert. """
command_executer(RevertCommand, ["foo"], self.todolist, None, self.out, self.error, None)
command_executer(RevertCommand, ["ls", "foo"], self.todolist, None, self.out, self.error, None)
command_executer(RevertCommand, ["1", "foo"], self.todolist, None, self.out, self.error, None)
usage_text = RevertCommand([], self.todolist).usage() + '\n'
self.assertEqual(self.errors, usage_text*3)
def test_revert_out_of_range(self):
command_executer(RevertCommand, ["158"], self.todolist, None, self.out, self.error, None)
self.assertEqual(self.errors, "Specified index is out range\n")
def test_revert_no_todolist(self):
""" Test attempt of revert with todolist missing """
backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
command_executer(RevertCommand, ['1'], None, None, self.out, self.error, None)
result = len(ChangeSet().backup_dict.keys())
self.assertEqual(result, 4)
def test_backup_config01(self):
config(p_overrides={('topydo', 'backup_count'): '1'})
......
......@@ -16,11 +16,12 @@
import unittest
from test.command_testcase import CommandTest
from test.facilities import load_file_to_todolist
from topydo.commands.SortCommand import SortCommand
from topydo.lib.Config import config
from .command_testcase import CommandTest
from .facilities import load_file_to_todolist
class SortCommandTest(CommandTest):
def setUp(self):
......
......@@ -14,15 +14,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from freezegun import freeze_time
import unittest
from test.facilities import (load_file, load_file_to_todolist, print_view,
todolist_to_string)
from test.topydo_testcase import TopydoTest
from freezegun import freeze_time
from topydo.lib.Config import config
from topydo.lib.Sorter import Sorter
from .facilities import (load_file, load_file_to_todolist, print_view,
todolist_to_string)
from .topydo_testcase import TopydoTest
@freeze_time("2016, 04, 25")
class SorterTest(TopydoTest):
......
......@@ -14,13 +14,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from freezegun import freeze_time
import unittest
from test.command_testcase import CommandTest
from freezegun import freeze_time
from topydo.commands.TagCommand import TagCommand
from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest
class TagCommandTest(CommandTest):
def setUp(self):
......
......@@ -17,9 +17,10 @@
import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.Todo import Todo
from .topydo_testcase import TopydoTest
def today_date():
today = date.today()
......
......@@ -20,9 +20,10 @@ import re
import unittest
from datetime import date, timedelta
from test.topydo_testcase import TopydoTest
from topydo.lib.TodoBase import TodoBase
from .topydo_testcase import TopydoTest
class TodoBaseTester(TopydoTest):
def test_parse_tag(self):
......
......@@ -16,8 +16,8 @@
import unittest
from test.facilities import load_file
from test.topydo_testcase import TopydoTest
from .facilities import load_file
from .topydo_testcase import TopydoTest
class TodoFileTest(TopydoTest):
......
......@@ -19,13 +19,14 @@
import re
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Config import config
from topydo.lib.Todo import Todo
from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList
from topydo.lib.TodoListBase import InvalidTodoException, TodoListBase
from .topydo_testcase import TopydoTest
class TodoListTester(TopydoTest):
def setUp(self):
......
......@@ -16,9 +16,10 @@
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Utils import translate_key_to_config
from .topydo_testcase import TopydoTest
class UtilsTest(TopydoTest):
def test_key_to_cfg(self):
......
......@@ -16,13 +16,14 @@
import unittest
from test.facilities import load_file, print_view, todolist_to_string
from test.topydo_testcase import TopydoTest
from topydo.lib import Filter
from topydo.lib.Sorter import Sorter
from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList
from .facilities import load_file, print_view, todolist_to_string
from .topydo_testcase import TopydoTest
class ViewTest(TopydoTest):
def test_view(self):
......
......@@ -21,7 +21,7 @@ instance based on an argument list.
import sys
from topydo.lib.Config import config, ConfigError
from topydo.lib.Config import ConfigError, config
SUBCOMMAND_MAP = {
'add': 'AddCommand',
......
......@@ -22,9 +22,9 @@ from datetime import date
from os.path import expanduser
from sys import stdin
from topydo.lib.WriteCommand import WriteCommand
from topydo.lib.Config import config
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.WriteCommand import WriteCommand
class AddCommand(WriteCommand):
......
......@@ -14,12 +14,13 @@
# 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.Config import config
from topydo.lib.Command import InvalidCommandArgument
from topydo.lib.WriteCommand import WriteCommand
from topydo.lib.Config import config
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.TodoParser import parse_line
from topydo.lib.WriteCommand import WriteCommand
class AppendCommand(WriteCommand):
def __init__(self, p_args, p_todolist, #pragma: no branch
......
......@@ -35,6 +35,9 @@ class DoCommand(DCommand):
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.condition = lambda todo: not todo.is_completed()
self.condition_failed_text = "Todo has already been completed."
def get_flags(self):
""" Additional flags. """
opts, long_opts = super().get_flags()
......@@ -75,15 +78,6 @@ class DoCommand(DCommand):
def prefix(self):
return "Completed: "
def condition(self, p_todo):
"""
An additional condition whether execute_specific should be executed.
"""
return not p_todo.is_completed()
def condition_failed_text(self):
return "Todo has already been completed."
def execute_specific(self, p_todo):
""" Actions specific to this command. """
self._handle_recurrence(p_todo)
......
......@@ -14,10 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import codecs
import tempfile
import os
import shlex
import tempfile
from subprocess import CalledProcessError, check_call
from topydo.lib.Config import config
......@@ -25,6 +25,7 @@ from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.Todo import Todo
def _get_file_mtime(p_file):
return os.stat(p_file.name).st_mtime
......@@ -44,11 +45,11 @@ class EditCommand(MultiCommand):
def get_flags(self):
return ("dE:", [])
def process_flag(self, p_opt, p_value):
if p_opt == '-d':
def process_flag(self, p_option, p_value):
if p_option == '-d':
self.edit_archive = True
self.multi_mode = False
elif p_opt == '-E':
elif p_option == '-E':
self.editor = shlex.split(p_value)
def _process_flags(self):
......@@ -71,9 +72,10 @@ class EditCommand(MultiCommand):
return f
def _todos_from_temp(self, p_temp_file):
f = codecs.open(p_temp_file.name, encoding='utf-8')
todos = f.read().splitlines()
@staticmethod
def _todos_from_temp(p_temp_file):
with codecs.open(p_temp_file.name, encoding='utf-8') as temp:
todos = temp.read().splitlines()
todo_objs = []
for todo in todos:
......@@ -112,7 +114,7 @@ class EditCommand(MultiCommand):
orig_mtime = _get_file_mtime(temp_todos)
if not self._open_in_editor(temp_todos.name):
new_todos = self._todos_from_temp(temp_todos)
new_todos = EditCommand._todos_from_temp(temp_todos)
if _is_edited(orig_mtime, temp_todos):
for todo in self.todos:
......
......@@ -14,18 +14,18 @@
# 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 re
import sys
import os
from topydo.lib.Config import config
from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.Filter import HiddenTagFilter, InstanceFilter
from topydo.lib.ListFormat import ListFormatError
from topydo.lib.printers.PrettyPrinter import pretty_printer_factory
from topydo.lib.prettyprinters.Format import PrettyPrinterFormatFilter
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.printers.PrettyPrinter import pretty_printer_factory
from topydo.lib.Sorter import Sorter
from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import get_terminal_size
from topydo.lib.View import View
......@@ -90,7 +90,7 @@ class ListCommand(ExpressionCommand):
elif opt == '-N':
# 2 lines are assumed to be taken up by printing the next prompt
# display at least one item
self.limit = self._N_lines()
self.limit = ListCommand._N_lines()
elif opt == '-n':
try:
self.limit = int(value)
......@@ -164,7 +164,8 @@ class ListCommand(ExpressionCommand):
return View(sorter, filters, self.todolist)
def _N_lines(self):
@staticmethod
def _N_lines():
''' Determine how many lines to print, such that the number of items
displayed will fit on the terminal (i.e one 'screen-ful' of items)
......@@ -182,7 +183,7 @@ class ListCommand(ExpressionCommand):
if "win32" in sys.platform:
lines_in_prompt += 1 # Windows will typically print a free line after
# the program output
a = re.findall('\$_', os.getenv('PROMPT', ''))
a = re.findall(r'\$_', os.getenv('PROMPT', ''))
lines_in_prompt += len(a)
else:
a = re.findall('\\n', os.getenv('PS1', ''))
......
......@@ -37,8 +37,8 @@ class PostponeCommand(MultiCommand):
def get_flags(self):
return("s", [])
def process_flag(self, p_opt, p_value):
if p_opt == '-s':
def process_flag(self, p_option, p_value):
if p_option == '-s':
self.move_start_date = True
def _execute_multi_specific(self):
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <bram@topydo.org>
# Copyright (C) 2014 - 2017 Bram Schoenmakers <bram@topydo.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,44 +14,119 @@
# 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
import arrow
from topydo.lib import TodoFile, TodoList
from topydo.lib.ChangeSet import ChangeSet
from topydo.lib import TodoFile
from topydo.lib import TodoList
from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config
class RevertCommand(Command):
def __init__(self, p_args, p_todolist, #pragma: no branch
def __init__(self, p_args, p_todolist, # pragma: no branch
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super().__init__(p_args, p_todolist, p_out, p_err,
p_prompt)
super().__init__(p_args, p_todolist, p_out, p_err, p_prompt)
self._backup = None
self._archive_file = None
self._archive = None
def execute(self):
if not super().execute():
return False
archive_file = TodoFile.TodoFile(config().archive())
archive = TodoList.TodoList(archive_file.read())
self._backup = ChangeSet()
archive_path = config().archive()
if archive_path:
self._archive_file = TodoFile.TodoFile(config().archive())
self._archive = TodoList.TodoList(self._archive_file.read())
if len(self.args) > 1:
self.error(self.usage())
else:
try:
arg = self.argument(0)
self._handle_args(arg)
except InvalidCommandArgument:
try:
self._revert_last()
except (ValueError, KeyError):
self.error('No backup was found for the current state of '
+ config().todotxt())
self._backup.close()
def _revert(self, p_timestamp=None):
self._backup.read_backup(self.todolist, p_timestamp)
self._backup.apply(self.todolist, self._archive)
last_change = ChangeSet()
if self._archive:
self._archive_file.write(self._archive.print_todos())
self.out("Reverted to state before: " + self._backup.label)
def _revert_last(self):
self._revert()
self._backup.delete()
def _revert_to_specific(self, p_position):
timestamps = [timestamp for timestamp, _ in self._backup]
position = int(p_position) - 1 # numbering in UI starts with 1
try:
last_change.get_backup(self.todolist)
last_change.apply(self.todolist, archive)
archive_file.write(archive.print_todos())
last_change.delete()
timestamp = timestamps[position]
self._revert(timestamp)
for timestamp in timestamps[:position + 1]:
self._backup.read_backup(p_timestamp=timestamp)
self._backup.delete()
except IndexError:
self.error('Specified index is out range')
self.out("Successfully reverted: " + last_change.label)
except (ValueError, KeyError):
self.error('No backup was found for the current state of ' + config().todotxt())
def _handle_args(self, p_arg):
try:
if p_arg == 'ls':
self._handle_ls()
elif p_arg.isdigit():
self._revert_to_specific(p_arg)
else:
raise InvalidCommandArgument
except InvalidCommandArgument:
self.error(self.usage())
last_change.close()
def _handle_ls(self):
num = 1
for timestamp, change in self._backup:
label = change[2]
time = arrow.get(timestamp).format('YYYY-MM-DD HH:mm:ss')
self.out('{0: >3}| {1} | {2}'.format(str(num), time, label))
num += 1
def usage(self):
return """Synopsis: revert"""
return """Synopsis:
revert [ls]
revert [NUMBER]"""
def help(self):
return """Reverts the last command."""
return """\
Reverts last commands.
* ls : Lists all backups ordered and numbered chronologically (starting
with 1 for latest backup).
* [NUMBER] : revert to specific point in history specified by NUMBER.
Output example for `revert ls`:
1 | 1970-01-01 00:00:02 | add Baz
2 | 1970-01-01 00:00:01 | add Bar
3 | 1970-01-01 00:00:00 | add Foo
In such example executing `revert 2` will revert todo and archive files to the
state before execution of `add Bar`.
* `revert` without any further arguments will revert to the latest backup
available, provided that this backup matches current state of the todo file.
Topydo will refuse to revert, if any changes to todo file were made by
external application after the latest backup. To force a `revert` action use
it with a NUMBER.\
"""
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <bram@topydo.org>
# Copyright (C) 2014 - 2017 Bram Schoenmakers <bram@topydo.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -19,7 +19,6 @@
import json
import time
import zlib
from copy import deepcopy
from hashlib import sha1
from os import path
......@@ -27,6 +26,7 @@ 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()
......@@ -43,11 +43,11 @@ def get_backup_path():
class ChangeSet(object):
""" Class for operations related with backup management. """
def __init__(self, p_todolist=None, p_archive=None, p_label=[]):
def __init__(self, p_todolist=None, p_archive=None, p_label=None):
self.todolist = deepcopy(p_todolist)
self.archive = deepcopy(p_archive)
self.timestamp = str(int(time.time()))
self.label = ' '.join(p_label)
self.timestamp = str(time.time())
self.label = ' '.join(p_label if p_label else [])
try:
self.json_file = open(get_backup_path(), 'r+b')
......@@ -56,6 +56,11 @@ class ChangeSet(object):
self._read()
def __iter__(self):
items = {key: self.backup_dict[key]
for key in self.backup_dict if key != 'index'}.items()
return iter(sorted(items, reverse=True))
def _read(self):
"""
Reads backup file from json_file property and sets backup_dict property
......@@ -158,15 +163,18 @@ class ChangeSet(object):
for changeset in index[backup_limit:]:
self.delete(changeset[0], p_write=False)
def get_backup(self, p_todolist):
def read_backup(self, p_todolist=None, p_timestamp=None):
"""
Retrieves a backup for p_todolist from backup file and sets todolist,
archive and label attributes to appropriate data from it.
Retrieves a backup for p_timestamp or p_todolist (if p_timestamp is not
specified) from backup file and sets timestamp, todolist, archive and
label attributes to appropriate data from it.
"""
if not p_timestamp:
change_hash = hash_todolist(p_todolist)
index = self._get_index()
self.timestamp = index[[change[1] for change in index].index(change_hash)][0]
else:
self.timestamp = p_timestamp
d = self.backup_dict[self.timestamp]
......@@ -176,10 +184,10 @@ class ChangeSet(object):
def apply(self, p_todolist, p_archive):
""" Applies backup on supplied p_todolist. """
if self.todolist:
if self.todolist and p_todolist:
p_todolist.replace(self.todolist.todos())
if self.archive:
if self.archive and p_archive:
p_archive.replace(self.archive.todos())
def close(self):
......
......@@ -15,17 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import configparser
from functools import lru_cache
import os
import re
import shlex
from collections import OrderedDict
from functools import lru_cache
from itertools import accumulate
from string import ascii_lowercase
from topydo.lib.Color import Color
def home_config_path(p_filename):
return os.path.join(os.path.expanduser('~'), p_filename)
......
......@@ -17,8 +17,8 @@
import re
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.printers.PrettyPrinter import PrettyPrinter
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.printers.PrettyPrinter import PrettyPrinter
class DCommand(MultiCommand):
......@@ -36,13 +36,14 @@ class DCommand(MultiCommand):
self.force = False
self._delta = []
self.condition = lambda _: True
self.condition_failed_text = ""
def get_flags(self):
return ("f", ["force"])
def process_flag(self, p_opt, p_value):
if p_opt == "-f" or p_opt == "--force":
def process_flag(self, p_option, p_value):
if p_option == "-f" or p_option == "--force":
self.force = True
def _uncompleted_children(self, p_todo):
......@@ -93,15 +94,6 @@ class DCommand(MultiCommand):
return [todo for todo in self.todolist.todos()
if not self._uncompleted_children(todo) and todo.is_active()]
def condition(self, _):
"""
An additional condition whether execute_specific should be executed.
"""
return True
def condition_failed_text(self):
raise NotImplementedError
def execute_specific(self, _):
raise NotImplementedError
......@@ -120,7 +112,7 @@ class DCommand(MultiCommand):
self._process_subtasks(todo)
self.execute_specific(todo)
else:
self.error(self.condition_failed_text())
self.error(self.condition_failed_text)
current_active = self._active_todos()
self._delta = [todo for todo in current_active
......
......@@ -14,8 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
from topydo.lib import Filter
from topydo.lib.Command import Command
from topydo.lib.Config import config
......
......@@ -16,12 +16,13 @@
""" Utilities for formatting output with "list_format" option."""
import arrow
import re
import arrow
from topydo.lib.Config import config
from topydo.lib.ProgressColor import progress_color
from topydo.lib.Utils import get_terminal_size, escape_ansi, humanize_date
from topydo.lib.Utils import escape_ansi, get_terminal_size, humanize_date
MAIN_PATTERN = (r'^({{(?P<before>.+?)}})?'
r'(?P<placeholder>{ph}|\[{ph}\])'
......@@ -205,7 +206,7 @@ class ListFormatParser(object):
'T': lambda t: humanize_date(t.start_date()) if t.start_date() else '',
# unique text ID
'u': lambda t: self.todolist.uid(t),
'u': self.todolist.uid if self.todolist else lambda _ : '',
# unique text ID, padded with spaces
'U': lambda t: _filler(self.todolist.uid(t),
......
......@@ -35,6 +35,7 @@ class MultiCommand(ExpressionCommand):
self.is_expression = False
self.multi_mode = True
# pylint: disable=no-self-use
def get_flags(self):
""" Default implementation of getting specific flags. """
return ("", [])
......
......@@ -20,7 +20,6 @@ from topydo.lib.Color import Color
from topydo.lib.Config import config
from topydo.lib.Recurrence import relative_date_to_date
# when a todo item has not enough information to determine the length, assume
# this length
ASSUMED_TODO_LENGTH = 14 # days
......
......@@ -16,16 +16,15 @@
""" This module provides functionality to sort lists with todo items. """
from collections import OrderedDict, namedtuple
from itertools import groupby
import re
from collections import OrderedDict, namedtuple
from datetime import date
from itertools import groupby
from topydo.lib.Config import config
from topydo.lib.Importance import average_importance, importance
from topydo.lib.Utils import date_string_to_date, humanize_date
Field = namedtuple('Field', ['sort', 'group', 'label'])
FIELDS = {
......@@ -148,57 +147,7 @@ class Sorter(object):
"""
def __init__(self, p_sortstring="desc:priority", p_groupstring=""):
self.groupfunctions = self._parse(p_groupstring, p_group=True) if p_groupstring else []
self.pregroupfunctions = self._parse(p_groupstring, p_group=False) if p_groupstring else []
self.sortfunctions = self._parse(p_sortstring, p_group=False)
def sort(self, p_todos):
"""
Sorts the list of todos given as a parameter, returns a new sorted
list.
The list is traversed in reverse order, such that the most specific
sort operation is done first, relying on the stability of the sorted()
function.
"""
return _apply_sort_functions(p_todos, self.sortfunctions)
def group(self, p_todos):
"""
Groups the todos according to the given group string.
"""
# preorder todos for the group sort
p_todos = _apply_sort_functions(p_todos, self.pregroupfunctions)
# initialize result with a single group
result = OrderedDict([((), p_todos)])
for (function, label), _ in self.groupfunctions:
oldresult = result
result = OrderedDict()
for oldkey, oldgroup in oldresult.items():
for key, _group in groupby(oldgroup, function):
newgroup = list(_group)
if not isinstance(key, list):
key = [key]
for subkey in key:
subkey = "{}: {}".format(label, subkey)
newkey = oldkey + (subkey,)
if newkey in result:
result[newkey] = result[newkey] + newgroup
else:
result[newkey] = newgroup
# sort all groups
for key, _group in result.items():
result[key] = self.sort(_group)
return result
def _parse(self, p_string, p_group):
def parse(p_string, p_group):
"""
Parses a sort/group string and returns a list of functions and the
desired order.
......@@ -270,3 +219,52 @@ class Sorter(object):
return result
self.groupfunctions = parse(p_groupstring, p_group=True) if p_groupstring else []
self.pregroupfunctions = parse(p_groupstring, p_group=False) if p_groupstring else []
self.sortfunctions = parse(p_sortstring, p_group=False)
def sort(self, p_todos):
"""
Sorts the list of todos given as a parameter, returns a new sorted
list.
The list is traversed in reverse order, such that the most specific
sort operation is done first, relying on the stability of the sorted()
function.
"""
return _apply_sort_functions(p_todos, self.sortfunctions)
def group(self, p_todos):
"""
Groups the todos according to the given group string.
"""
# preorder todos for the group sort
p_todos = _apply_sort_functions(p_todos, self.pregroupfunctions)
# initialize result with a single group
result = OrderedDict([((), p_todos)])
for (function, label), _ in self.groupfunctions:
oldresult = result
result = OrderedDict()
for oldkey, oldgroup in oldresult.items():
for key, _group in groupby(oldgroup, function):
newgroup = list(_group)
if not isinstance(key, list):
key = [key]
for subkey in key:
subkey = "{}: {}".format(label, subkey)
newkey = oldkey + (subkey,)
if newkey in result:
result[newkey] = result[newkey] + newgroup
else:
result[newkey] = newgroup
# sort all groups
for key, _group in result.items():
result[key] = self.sort(_group)
return result
......@@ -20,8 +20,10 @@ changes.
"""
import os.path
from watchdog.events import (FileCreatedEvent, FileModifiedEvent,
FileSystemEventHandler)
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, FileModifiedEvent, FileCreatedEvent
from topydo.lib.TodoFile import TodoFile
......
......@@ -116,7 +116,7 @@ class TodoList(TodoListBase):
super().add_todos(p_todos)
for todo in self._todos:
todo.parents = types.MethodType(lambda i: self.parents(i), todo)
todo.parents = types.MethodType(self.parents, todo)
# only do administration when the dependency info is initialized,
# otherwise we postpone it until it's really needed (through the
......
......@@ -18,6 +18,7 @@
import collections
class TopydoString(collections.UserString):
"""
Represents a string that also contains color information. A combination of
......@@ -50,4 +51,3 @@ class TopydoString(collections.UserString):
def set_color(self, p_pos, p_color):
""" Start using a color at the given position. """
self.colors[p_pos] = p_color
......@@ -18,12 +18,12 @@
Various utility functions.
"""
import arrow
import re
from collections import namedtuple
from datetime import date
import arrow
def date_string_to_date(p_date):
"""
......@@ -81,16 +81,14 @@ def get_terminal_size(p_getter=None):
except ImportError:
from backports.shutil_get_terminal_size import get_terminal_size as _get_terminal_size # pylint: disable=import-error
sz = _get_terminal_size()
size = _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?)
"""
# 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)
size = terminal_size(80, 24)
return sz
return size
get_terminal_size.getter = inner
......@@ -114,6 +112,5 @@ def translate_key_to_config(p_key):
def humanize_date(p_datetime):
""" Returns a relative date string from a datetime object. """
now = arrow.now()
date = now.replace(day=p_datetime.day, month=p_datetime.month, year=p_datetime.year)
return date.humanize(now).replace('just now', 'today')
_date = now.replace(day=p_datetime.day, month=p_datetime.month, year=p_datetime.year)
return _date.humanize(now).replace('just now', 'today')
......@@ -56,4 +56,3 @@ class PrettyPrinterColorFilter(PrettyPrinterFilter):
p_todo_str.append('', AbstractColor.NEUTRAL)
return p_todo_str
......@@ -19,8 +19,8 @@ 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
from topydo.lib.PrettyPrinterFilter import PrettyPrinterFilter
class PrettyPrinterFormatFilter(PrettyPrinterFilter):
......
......@@ -29,4 +29,3 @@ class PrettyPrinterNumbers(PrettyPrinterFilter):
def filter(self, p_todo_str, p_todo):
""" Prepends the number to the todo string. """
return "|{:>3}| {}".format(self.todolist.number(p_todo), p_todo_str)
......@@ -177,7 +177,8 @@ class CLIApplicationBase(object):
self._post_archive_action = None
self.backup = None
def _usage(self):
@staticmethod
def _usage():
usage()
sys.exit(0)
......@@ -208,7 +209,7 @@ class CLIApplicationBase(object):
elif opt in ("-v", "--version"):
version()
else:
self._usage()
CLIApplicationBase._usage()
if alt_config_path:
config(alt_config_path, overrides)
......@@ -237,21 +238,17 @@ class CLIApplicationBase(object):
if archive.dirty:
archive_file.write(archive.print_todos())
def _help(self, args):
if args is None:
pass # TODO
else:
pass # TODO
def is_read_only(self, p_command):
@staticmethod
def is_read_only(p_command):
""" Returns True when the given command class is read-only. """
read_only_commands = tuple(cmd for cmd
in ('revert', ) + READ_ONLY_COMMANDS)
return p_command.name() in read_only_commands
def _backup(self, p_command, p_args=[], p_label=None):
if config().backup_count() > 0 and p_command and not self.is_read_only(p_command):
call = [p_command.name()]+ p_args
def _backup(self, p_command, p_args=None, p_label=None):
if config().backup_count() > 0 and p_command and not CLIApplicationBase.is_read_only(p_command):
p_args = p_args if p_args else []
call = [p_command.name()] + p_args
from topydo.lib.ChangeSet import ChangeSet
label = p_label if p_label else call
......
......@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
from functools import lru_cache
from topydo.Commands import SUBCOMMAND_MAP
......
......@@ -20,7 +20,7 @@ import getopt
import sys
from topydo.ui.cli.CLI import CLIApplication
from topydo.ui.CLIApplicationBase import MAIN_OPTS, MAIN_LONG_OPTS, error
from topydo.ui.CLIApplicationBase import MAIN_LONG_OPTS, MAIN_OPTS, error
_WINDOWS = "win32" in sys.platform
......
......@@ -18,9 +18,10 @@
import sys
from topydo.Commands import get_subcommand
from topydo.lib import TodoFile, TodoList
from topydo.lib.Config import ConfigError, config
from topydo.ui.CLIApplicationBase import CLIApplicationBase, error
from topydo.lib import TodoFile
from topydo.lib.Config import config, ConfigError
# First thing is to poke the configuration and check whether it's sane
# The modules below may already read in configuration upon import, so
......@@ -31,8 +32,6 @@ except ConfigError as config_error:
error(str(config_error))
sys.exit(1)
from topydo.Commands import get_subcommand
from topydo.lib import TodoList
class CLIApplication(CLIApplicationBase):
......@@ -57,7 +56,7 @@ class CLIApplication(CLIApplicationBase):
sys.exit(1)
if subcommand is None:
self._usage()
CLIApplicationBase._usage()
if self._execute(subcommand, args) == False:
sys.exit(1)
......
......@@ -14,10 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from configparser import RawConfigParser, NoOptionError
from configparser import NoOptionError, RawConfigParser
from os.path import expanduser
from topydo.lib.Config import home_config_path, config
from topydo.lib.Config import config, home_config_path
def columns(p_alt_layout_path=None):
......
......@@ -14,10 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urwid
from os.path import commonprefix
import urwid
from topydo.ui.columns.CompletionBoxWidget import CompletionBoxWidget
......
......@@ -19,30 +19,32 @@ import getopt
import shlex
import sys
import time
import urwid
from collections import namedtuple
from string import ascii_uppercase
import urwid
from topydo.Commands import get_subcommand
from topydo.lib.Config import config, ConfigError
from topydo.ui.columns.ColumnCompleter import ColumnCompleter
from topydo.lib import TodoList
from topydo.lib.Config import ConfigError, config
from topydo.lib.Filter import (DependencyFilter, HiddenTagFilter,
RelevanceFilter, get_filter_list)
from topydo.lib.Sorter import Sorter
from topydo.lib.Filter import get_filter_list, RelevanceFilter, DependencyFilter, HiddenTagFilter
from topydo.lib.TodoFileWatched import TodoFileWatched
from topydo.lib.Utils import get_terminal_size
from topydo.lib.View import View
from topydo.lib.TodoFileWatched import TodoFileWatched
from topydo.lib import TodoList
from topydo.ui.CLIApplicationBase import CLIApplicationBase, error, GENERIC_HELP
from topydo.ui.CLIApplicationBase import (GENERIC_HELP, CLIApplicationBase,
error)
from topydo.ui.columns.ColumnCompleter import ColumnCompleter
from topydo.ui.columns.ColumnLayout import columns
from topydo.ui.columns.CommandLineWidget import CommandLineWidget
from topydo.ui.columns.ConsoleWidget import ConsoleWidget
from topydo.ui.columns.KeystateWidget import KeystateWidget
from topydo.ui.columns.TodoWidget import TodoWidget
from topydo.ui.columns.TodoListWidget import TodoListWidget
from topydo.ui.columns.TodoWidget import TodoWidget
from topydo.ui.columns.Transaction import Transaction
from topydo.ui.columns.Utils import PaletteItem, to_urwid_color
from topydo.ui.columns.ViewWidget import ViewWidget
from topydo.ui.columns.ColumnLayout import columns
class UIView(View):
......@@ -196,24 +198,7 @@ class UIApplication(CLIApplicationBase):
self._screen = urwid.raw_display.Screen()
if config().colors():
self._screen.register_palette(self._create_color_palette())
else:
self._screen.register_palette(self._create_mono_palette())
self._screen.set_terminal_properties(256)
self.mainloop = urwid.MainLoop(
self.mainwindow,
screen=self._screen,
unhandled_input=self._handle_input,
pop_ups=True
)
self.column_mode = _APPEND_COLUMN
self._set_alarm_for_next_midnight_update()
def _create_color_palette(self):
def create_color_palette():
project_color = to_urwid_color(config().project_color())
context_color = to_urwid_color(config().context_color())
metadata_color = to_urwid_color(config().metadata_color())
......@@ -249,7 +234,7 @@ class UIApplication(CLIApplicationBase):
return palette
def _create_mono_palette(self):
def create_mono_palette():
palette = [
(PaletteItem.DEFAULT_FOCUS, 'black', 'light gray'),
(PaletteItem.PROJECT_FOCUS, PaletteItem.DEFAULT_FOCUS),
......@@ -266,6 +251,23 @@ class UIApplication(CLIApplicationBase):
return palette
if config().colors():
self._screen.register_palette(create_color_palette())
else:
self._screen.register_palette(create_mono_palette())
self._screen.set_terminal_properties(256)
self.mainloop = urwid.MainLoop(
self.mainwindow,
screen=self._screen,
unhandled_input=self._handle_input,
pop_ups=True
)
self.column_mode = _APPEND_COLUMN
self._set_alarm_for_next_midnight_update()
def _set_alarm_for_next_midnight_update(self):
def callback(p_loop, p_data):
TodoWidget.wipe_cache()
......
......@@ -207,6 +207,7 @@ class TodoListWidget(urwid.LineBox):
p_row,
p_focus)
# pylint: disable=no-self-use
def selectable(self):
return True
......
......@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import urwid
from topydo.lib.Config import config
......@@ -91,7 +92,6 @@ class TodoWidget(urwid.WidgetWrap):
priority_widget = urwid.Text(priority_text)
self.text_widget = urwid.Text(txt_markup)
progress = to_urwid_color(progress_color(p_todo)) if config().colors() else PaletteItem.DEFAULT
self.progress_bar = urwid.AttrMap(
urwid.SolidFill(' '),
{},
......@@ -117,13 +117,15 @@ class TodoWidget(urwid.WidgetWrap):
super().__init__(self.widget)
def keypress(self, p_size, p_key):
# pylint: disable=no-self-use
def keypress(self, _, p_key):
"""
Override keypress to prevent the wrapped Columns widget to
receive any key.
"""
return p_key
# pylint: disable=no-self-use
def selectable(self):
# make sure that ListBox will highlight this widget
return True
......@@ -196,4 +198,3 @@ class TodoWidget(urwid.WidgetWrap):
def wipe_cache(p_class):
""" Wipes the cache """
p_class.cache = {}
......@@ -18,6 +18,7 @@ import urwid
from topydo.lib.Config import config
class ViewWidget(urwid.LineBox):
def __init__(self, p_todolist):
self._todolist = p_todolist
......
......@@ -16,16 +16,18 @@
""" Entry file for the topydo Prompt interface (CLI). """
import os.path
import shlex
import sys
from topydo.ui.CLIApplicationBase import CLIApplicationBase, error, GENERIC_HELP
from topydo.ui.prompt.PromptCompleter import PromptCompleter
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.history import InMemoryHistory
from prompt_toolkit.shortcuts import prompt
from topydo.lib.Config import config, ConfigError
from topydo.Commands import get_subcommand
from topydo.lib.Config import ConfigError, config
from topydo.lib.TodoFileWatched import TodoFileWatched
from topydo.ui.CLIApplicationBase import (GENERIC_HELP, CLIApplicationBase,
error)
from topydo.ui.prompt.PromptCompleter import PromptCompleter
# First thing is to poke the configuration and check whether it's sane
# The modules below may already read in configuration upon import, so
......@@ -36,9 +38,6 @@ except ConfigError as config_error:
error(str(config_error))
sys.exit(1)
from topydo.Commands import get_subcommand
from topydo.lib.TodoFileWatched import TodoFileWatched
from topydo.lib import TodoList
class PromptApplication(CLIApplicationBase):
......
......@@ -22,9 +22,10 @@ by the prompt toolkit.
import re
from prompt_toolkit.completion import Completer, Completion
from topydo.ui.CompleterBase import CompleterBase, date_suggestions
from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date
from topydo.ui.CompleterBase import CompleterBase, date_suggestions
def _dates(p_word_before_cursor):
......@@ -52,15 +53,14 @@ class PromptCompleter(CompleterBase, Completer):
for candidate in candidates:
yield Completion(candidate, -len(p_word))
def get_completions(self, p_document, _):
def get_completions(self, p_word, p_is_first_word=False):
# include all characters except whitespaces (for + and @)
word_before_cursor = p_document.get_word_before_cursor(True)
word_before_cursor = p_word.get_word_before_cursor(True)
is_first_word = not re.match(r'\s*\S+\s',
p_document.current_line_before_cursor)
p_word.current_line_before_cursor)
if word_before_cursor.startswith(config().tag_due() + ':'):
return _dates(word_before_cursor)
elif word_before_cursor.startswith(config().tag_start() + ':'):
return _dates(word_before_cursor)
else:
return self._completion_generator(word_before_cursor, is_first_word)
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