Commit 705ebf57 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'prompt'

parents 072d7c89 0c711a24
from setuptools import setup
from setuptools import setup, find_packages
setup(
name = "topydo",
packages = ["topydo", "topydo.lib", "topydo.cli"],
packages = find_packages(exclude=["test"]),
version = "0.3",
description = "A command-line todo list application using the todo.txt format.",
author = "Bram Schoenmakers",
author_email = "me@bramschoenmakers.nl",
url = "https://github.com/bram85/topydo",
install_requires = [
'six',
'six >= 1.9.0',
],
extras_require = {
'ical': ['icalendar'],
'prompt-toolkit': ['prompt-toolkit >= 0.37'],
'edit-cmd-tests': ['mock'],
},
entry_points= {
'console_scripts': ['topydo = topydo.cli.Main:main'],
'console_scripts': ['topydo = topydo.cli.UILoader:main'],
},
classifiers = [
"Development Status :: 4 - Beta",
......
......@@ -16,10 +16,11 @@
from datetime import date
import unittest
from six import u
from topydo.lib import AddCommand
from topydo.lib import ListCommand
from test.CommandTest import CommandTest
from topydo.commands import AddCommand
from topydo.commands import ListCommand
from test.CommandTest import CommandTest, utf8
from topydo.lib.Config import config
from topydo.lib import TodoList
......@@ -234,6 +235,13 @@ class AddCommandTest(CommandTest):
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n")
def test_add_unicode(self):
command = AddCommand.AddCommand([u("Special \u25c4")], self.todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, utf8(u("| 1| {} Special \u25c4\n").format(self.today)))
self.assertEqual(self.errors, "")
def test_help(self):
command = AddCommand.AddCommand(["help"], self.todolist, self.out, self.error)
command.execute()
......
......@@ -16,7 +16,7 @@
import unittest
from topydo.lib.AppendCommand import AppendCommand
from topydo.commands.AppendCommand import AppendCommand
from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList
......
......@@ -16,7 +16,7 @@
import unittest
from topydo.lib.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
......
......@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from six import PY2
from topydo.lib.Utils import escape_ansi
from test.TopydoTest import TopydoTest
......@@ -33,5 +34,13 @@ class CommandTest(TopydoTest):
if p_error:
self.errors += escape_ansi(p_error + "\n")
# utility for several commands
def utf8(p_string):
""" Converts a Unicode string to UTF-8 in case of Python 2. """
if PY2:
p_string = p_string.encode('utf-8')
return p_string
if __name__ == '__main__':
unittest.main()
......@@ -15,10 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from six import u
from test.CommandTest import CommandTest
from topydo.lib.Config import config
from topydo.lib.DeleteCommand import DeleteCommand
from topydo.commands.DeleteCommand import DeleteCommand
from topydo.lib.TodoList import TodoList
from topydo.lib.TodoListBase import InvalidTodoException
......@@ -150,6 +151,15 @@ class DeleteCommandTest(CommandTest):
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: A.\n")
def test_multi_del5(self):
""" Throw an error with invalid argument containing special characters. """
command = DeleteCommand([u("Fo\u00d3B\u0105r"), "Bar"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
def test_empty(self):
command = DeleteCommand([], self.todolist, self.out, self.error)
command.execute()
......
......@@ -16,8 +16,8 @@
import unittest
from topydo.commands.DepCommand import DepCommand
from test.CommandTest import CommandTest
from topydo.lib.DepCommand import DepCommand
from topydo.lib.TodoList import TodoList
class DepCommandTest(CommandTest):
......
......@@ -15,9 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from six import u
from topydo.commands.DepriCommand import DepriCommand
from test.CommandTest import CommandTest
from topydo.lib.DepriCommand import DepriCommand
from topydo.lib.TodoList import TodoList
class DepriCommandTest(CommandTest):
......@@ -93,6 +94,15 @@ class DepriCommandTest(CommandTest):
self.assertFalse(self.output)
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: FooBar.\n")
def test_invalid4(self):
""" Throw an error with invalid argument containing special characters. """
command = DepriCommand([u("Fo\u00d3B\u0105r"), "Bar"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertFalse(self.output)
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
def test_empty(self):
command = DepriCommand([], self.todolist, self.out, self.error)
command.execute()
......
......@@ -16,9 +16,10 @@
from datetime import date, timedelta
import unittest
from six import u
from topydo.commands.DoCommand import DoCommand
from test.CommandTest import CommandTest
from topydo.lib.DoCommand import DoCommand
from topydo.lib.TodoList import TodoList
def _yes_prompt(self):
......@@ -324,6 +325,14 @@ class DoCommandTest(CommandTest):
self.assertEqual(self.errors, "Invalid todo number given: 99.\nInvalid todo number given: 10.\n")
def test_multi_do6(self):
""" Throw an error with invalid argument containing special characters. """
command = DoCommand([u("Fo\u00d3B\u0105r"), "Bar"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
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)
......
......@@ -16,9 +16,10 @@
import unittest
import mock
from six import u
from test.CommandTest import CommandTest
from topydo.lib.EditCommand import EditCommand
from topydo.commands.EditCommand import EditCommand
from test.CommandTest import CommandTest, utf8
from topydo.lib.TodoList import TodoList
from topydo.lib.Todo import Todo
......@@ -29,12 +30,13 @@ class EditCommandTest(CommandTest):
"Foo id:1",
"Bar p:1 @test",
"Baz @test",
u("Fo\u00f3B\u0105\u017a"),
]
self.todolist = TodoList(todos)
@mock.patch('topydo.lib.EditCommand.EditCommand._open_in_editor')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit1(self, mock_open_in_editor):
""" Preserve dependencies after editing. """
mock_open_in_editor.return_value = 0
......@@ -44,10 +46,10 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), "Bar p:1 @test\nBaz @test\nFoo id:1")
self.assertEqual(str(self.todolist), utf8(u("Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1")))
@mock.patch('topydo.lib.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.lib.EditCommand.EditCommand._open_in_editor')
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit2(self, mock_open_in_editor, mock_todos_from_temp):
""" Edit some todo. """
mock_open_in_editor.return_value = 0
......@@ -58,7 +60,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), "Foo id:1\nBaz @test\nLazy Cat")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat")))
def test_edit3(self):
""" Throw an error after invalid todo number given as argument. """
......@@ -70,14 +72,14 @@ class EditCommandTest(CommandTest):
def test_edit4(self):
""" Throw an error with pointing invalid argument. """
command = EditCommand(["Bar", "4"], self.todolist, self.out, self.error, None)
command = EditCommand(["Bar", "5"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Invalid todo number given: 4.\n")
self.assertEqual(self.errors, "Invalid todo number given: 5.\n")
@mock.patch('topydo.lib.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.lib.EditCommand.EditCommand._open_in_editor')
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit5(self, mock_open_in_editor, mock_todos_from_temp):
""" Don't let to delete todos acidentally while editing. """
mock_open_in_editor.return_value = 0
......@@ -88,10 +90,33 @@ class EditCommandTest(CommandTest):
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Number of edited todos is not equal to number of supplied todo IDs.\n")
self.assertEqual(str(self.todolist), "Foo id:1\nBar p:1 @test\nBaz @test")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a")))
@mock.patch('topydo.lib.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.lib.EditCommand.EditCommand._open_in_editor')
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()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit7(self, mock_open_in_editor, mock_todos_from_temp):
""" Edit todo with special characters. """
mock_open_in_editor.return_value = 0
mock_todos_from_temp.return_value = [Todo('Lazy Cat')]
command = EditCommand([u("Fo\u00f3B\u0105\u017a")], self.todolist, self.out, self.error, None)
command.execute()
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat")))
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit_expr(self, mock_open_in_editor, mock_todos_from_temp):
""" Edit todos matching expression. """
mock_open_in_editor.return_value = 0
......@@ -102,7 +127,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), "Foo id:1\nLazy Cat\nLazy Dog")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog")))
if __name__ == '__main__':
unittest.main()
......@@ -19,8 +19,9 @@ import re
import sys
import unittest
from topydo.lib.Config import config
from topydo.commands.IcalCommand import IcalCommand
from test.CommandTest import CommandTest
from topydo.lib.IcalCommand import IcalCommand
from test.TestFacilities import load_file_to_todolist
IS_PYTHON_32 = (sys.version_info.major, sys.version_info.minor) == (3, 2)
......
......@@ -14,11 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from six import u
import unittest
from topydo.lib.Config import config
from test.CommandTest import CommandTest
from topydo.lib.ListCommand import ListCommand
from topydo.commands.ListCommand import ListCommand
from test.CommandTest import CommandTest, utf8
from test.TestFacilities import load_file_to_todolist
class ListCommandTest(CommandTest):
......@@ -187,5 +188,21 @@ class ListCommandTest(CommandTest):
self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n\n" + command.help() + "\n")
class ListCommandUnicodeTest(CommandTest):
def setUp(self):
super(ListCommandUnicodeTest, self).setUp()
self.todolist = load_file_to_todolist("test/data/ListCommandUnicodeTest.txt")
def test_list_unicode1(self):
""" Unicode filters """
command = ListCommand([u("\u25c4")], self.todolist, self.out, self.error)
command.execute()
self.assertFalse(self.todolist.is_dirty())
expected = utf8(u("| 1| (C) And some sp\u00e9cial tag:\u25c4\n"))
self.assertEqual(self.output, expected)
if __name__ == '__main__':
unittest.main()
......@@ -16,9 +16,9 @@
import unittest
from topydo.commands.ListContextCommand import ListContextCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
from topydo.lib.ListContextCommand import ListContextCommand
class ListContextCommandTest(CommandTest):
def test_contexts1(self):
......
......@@ -16,9 +16,9 @@
import unittest
from topydo.commands.ListProjectCommand import ListProjectCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
from topydo.lib.ListProjectCommand import ListProjectCommand
class ListProjectCommandTest(CommandTest):
def test_projects1(self):
......
......@@ -16,9 +16,10 @@
from datetime import date, timedelta
import unittest
from six import u
from topydo.commands.PostponeCommand import PostponeCommand
from test.CommandTest import CommandTest
from topydo.lib.PostponeCommand import PostponeCommand
from topydo.lib.TodoList import TodoList
class PostponeCommandTest(CommandTest):
......@@ -223,6 +224,15 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, "")
self.assertEqual(self.errors, "Invalid todo number given: Zoo.\nInvalid todo number given: 99.\nInvalid todo number given: 123.\n")
def test_postpone20(self):
""" Throw an error with invalid argument containing special characters. """
command = PostponeCommand([u("Fo\u00d3B\u0105r"), "Bar", "1d"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
def test_help(self):
command = PostponeCommand(["help"], self.todolist, self.out, self.error)
command.execute()
......
......@@ -15,9 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from six import u
from topydo.commands.PriorityCommand import PriorityCommand
from test.CommandTest import CommandTest
from topydo.lib.PriorityCommand import PriorityCommand
from topydo.lib.TodoList import TodoList
class PriorityCommandTest(CommandTest):
......@@ -118,6 +119,15 @@ class PriorityCommandTest(CommandTest):
self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n")
def test_invalid7(self):
""" Throw an error with invalid argument containing special characters. """
command = PriorityCommand([u("Fo\u00d3B\u0105r"), "Bar", "C"], self.todolist, self.out, self.error, None)
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.output, "")
self.assertEqual(self.errors, u("Invalid todo number given: Fo\u00d3B\u0105r.\n"))
def test_empty(self):
command = PriorityCommand([], self.todolist, self.out, self.error)
command.execute()
......
......@@ -16,9 +16,9 @@
import unittest
from test.CommandTest import CommandTest
from topydo.lib.Config import config
from topydo.lib.SortCommand import SortCommand
from topydo.commands.SortCommand import SortCommand
from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist
class SortCommandTest(CommandTest):
......
......@@ -16,8 +16,8 @@
import unittest
from topydo.commands.TagCommand import TagCommand
from test.CommandTest import CommandTest
from topydo.lib.TagCommand import TagCommand
from topydo.lib.TodoList import TodoList
class TagCommandTest(CommandTest):
......
(C) And some spécial tag:◄
......@@ -21,21 +21,22 @@ instance based on an argument list.
from topydo.lib.Config import config
from topydo.lib.AddCommand import AddCommand
from topydo.lib.AppendCommand import AppendCommand
from topydo.lib.DeleteCommand import DeleteCommand
from topydo.lib.DepCommand import DepCommand
from topydo.lib.DepriCommand import DepriCommand
from topydo.lib.DoCommand import DoCommand
from topydo.lib.EditCommand import EditCommand
from topydo.lib.IcalCommand import IcalCommand
from topydo.lib.ListCommand import ListCommand
from topydo.lib.ListContextCommand import ListContextCommand
from topydo.lib.ListProjectCommand import ListProjectCommand
from topydo.lib.PostponeCommand import PostponeCommand
from topydo.lib.PriorityCommand import PriorityCommand
from topydo.lib.SortCommand import SortCommand
from topydo.lib.TagCommand import TagCommand
from topydo.commands.AddCommand import AddCommand
from topydo.commands.AppendCommand import AppendCommand
from topydo.commands.DeleteCommand import DeleteCommand
from topydo.commands.DepCommand import DepCommand
from topydo.commands.DepriCommand import DepriCommand
from topydo.commands.DoCommand import DoCommand
from topydo.commands.EditCommand import EditCommand
from topydo.commands.ExitCommand import ExitCommand
from topydo.commands.IcalCommand import IcalCommand
from topydo.commands.ListCommand import ListCommand
from topydo.commands.ListContextCommand import ListContextCommand
from topydo.commands.ListProjectCommand import ListProjectCommand
from topydo.commands.PostponeCommand import PostponeCommand
from topydo.commands.PriorityCommand import PriorityCommand
from topydo.commands.SortCommand import SortCommand
from topydo.commands.TagCommand import TagCommand
_SUBCOMMAND_MAP = {
'add': AddCommand,
......@@ -46,6 +47,7 @@ _SUBCOMMAND_MAP = {
'depri': DepriCommand,
'do': DoCommand,
'edit': EditCommand,
'exit': ExitCommand, # used for the prompt
'ical': IcalCommand,
'ls': ListCommand,
'lscon': ListContextCommand,
......@@ -58,6 +60,7 @@ _SUBCOMMAND_MAP = {
'listprojects': ListProjectCommand,
'postpone': PostponeCommand,
'pri': PriorityCommand,
'quit': ExitCommand,
'rm': DeleteCommand,
'sort': SortCommand,
'tag': TagCommand,
......
# 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/>.
""" Entry file for the Python todo.txt CLI. """
import sys
from topydo.cli.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
# make sure to bail out if the configuration is invalid.
try:
config()
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):
"""
Class that represents the (original) Command Line Interface of Topydo.
"""
def __init__(self):
super(CLIApplication, self).__init__()
def run(self):
""" Main entry function. """
args = self._process_flags()
self.todofile = TodoFile.TodoFile(self.path)
self.todolist = TodoList.TodoList(self.todofile.read())
(subcommand, args) = get_subcommand(args)
if subcommand == None:
self._usage()
if self._execute(subcommand, args) == False:
sys.exit(1)
def main():
""" Main entry point of the CLI. """
CLIApplication().run()
if __name__ == '__main__':
main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -14,12 +14,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/>.
""" Entry file for the Python todo.txt CLI. """
"""
Contains a base class for a CLI implementation of topydo and functions for the
I/O on the command-line.
"""
import getopt
import sys
from six import PY2
from six.moves import input
MAIN_OPTS = "c:d:ht:v"
def usage():
""" Prints the command-line usage of topydo. """
......@@ -55,8 +61,6 @@ Available commands:
Run `topydo help <subcommand>` for command-specific help.
""")
sys.exit(0)
def write(p_file, p_string):
"""
Write p_string to file p_file, trailed by a newline character.
......@@ -92,19 +96,19 @@ except ConfigError as config_error:
error(str(config_error))
sys.exit(1)
from topydo.lib.Commands import get_subcommand
from topydo.lib.ArchiveCommand import ArchiveCommand
from topydo.lib.SortCommand import SortCommand
from topydo.commands.ArchiveCommand import ArchiveCommand
from topydo.commands.SortCommand import SortCommand
from topydo.lib import TodoFile
from topydo.lib import TodoList
from topydo.lib import TodoListBase
from topydo.lib.Utils import escape_ansi
class CLIApplication(object):
class CLIApplicationBase(object):
"""
Class that represents the Command Line Interface of Topydo.
Base class for a Command Line Interfaces (CLI) for topydo. Examples are the
original CLI and the Prompt interface.
Handles input/output of the various subcommand.
Handles input/output of the various subcommands.
"""
def __init__(self):
self.todolist = TodoList.TodoList([])
......@@ -113,9 +117,20 @@ class CLIApplication(object):
self.path = self.config.todotxt()
self.archive_path = self.config.archive()
self.todofile = None
def _usage(self):
usage()
sys.exit(0)
def _process_flags(self):
args = sys.argv[1:]
if PY2:
args = [arg.decode('utf-8') for arg in args]
try:
opts, args = getopt.getopt(sys.argv[1:], "c:d:ht:v")
opts, args = getopt.getopt(args, MAIN_OPTS)
except getopt.GetoptError as e:
error(str(e))
sys.exit(1)
......@@ -133,7 +148,7 @@ class CLIApplication(object):
elif opt == "-v":
version()
else:
usage()
self._usage()
self.path = alt_path if alt_path else self.config.todotxt()
self.archive_path = alt_archive \
......@@ -164,6 +179,12 @@ class CLIApplication(object):
else:
pass # TODO
def _input(self):
"""
Returns a function that retrieves user input.
"""
return input
def _execute(self, p_command, p_args):
"""
Execute a subcommand with arguments. p_command is a class (not an
......@@ -174,36 +195,23 @@ class CLIApplication(object):
self.todolist,
lambda o: write(sys.stdout, o),
error,
input)
return False if command.execute() == False else True
def run(self):
""" Main entry function. """
args = self._process_flags()
self._input())
todofile = TodoFile.TodoFile(self.path)
self.todolist = TodoList.TodoList(todofile.read())
if command.execute() != False:
self._post_execute()
return True
(subcommand, args) = get_subcommand(args)
if subcommand == None:
usage()
if self._execute(subcommand, args) == False:
sys.exit(1)
return False
def _post_execute(self):
if self.todolist.is_dirty():
self._archive()
if config().keep_sorted():
self._execute(SortCommand, [])
todofile.write(str(self.todolist))
self.todofile.write(str(self.todolist))
def main():
""" Main entry point of the CLI. """
CLIApplication().run()
def run(self):
raise NotImplementedError
if __name__ == '__main__':
main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 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/>.
""" Entry file for the topydo Prompt interface (CLI). """
import sys
from topydo.cli.CLIApplicationBase import CLIApplicationBase, error, usage
from topydo.cli.TopydoCompleter import TopydoCompleter
from prompt_toolkit.shortcuts import get_input
from prompt_toolkit.history import History
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
# make sure to bail out if the configuration is invalid.
try:
config()
except ConfigError as config_error:
error(str(config_error))
sys.exit(1)
from topydo.Commands import get_subcommand
from topydo.commands.SortCommand import SortCommand
from topydo.lib import TodoFile
from topydo.lib import TodoList
class PromptApplication(CLIApplicationBase):
"""
This class implements a variant of topydo's CLI showing a shell and
offering auto-completion thanks to the prompt toolkit.
"""
def __init__(self):
super(PromptApplication, self).__init__()
def run(self):
""" Main entry function. """
args = self._process_flags()
self.todofile = TodoFile.TodoFile(self.path)
self.todolist = TodoList.TodoList(self.todofile.read())
completer = TopydoCompleter(self.todolist)
history = History()
while True:
try:
user_input = get_input(u'topydo> ', history=history, completer=completer).split()
except (EOFError, KeyboardInterrupt):
sys.exit(0)
(subcommand, args) = get_subcommand(user_input)
try:
if self._execute(subcommand, args) != False:
if self.todolist.is_dirty():
self._archive()
if config().keep_sorted():
self._execute(SortCommand, [])
self.todofile.write(str(self.todolist))
except TypeError:
usage()
def main():
""" Main entry point of the CLI. """
PromptApplication().run()
if __name__ == '__main__':
main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 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 datetime
import re
from prompt_toolkit.completion import Completer, Completion
from topydo.lib.Config import config
from topydo.Commands import _SUBCOMMAND_MAP
from topydo.lib.RelativeDate import relative_date_to_date
def _date_suggestions():
"""
Returns a list of relative date that is presented to the user as auto
complete suggestions.
"""
# don't use strftime, prevent locales to kick in
days_of_week = {
0: "Monday",
1: "Tuesday",
2: "Wednesday",
3: "Thursday",
4: "Friday",
5: "Saturday",
6: "Sunday"
}
dates = [
'today',
'tomorrow',
]
# show days of week up to next week
dow = datetime.date.today().weekday()
for i in range(dow + 2 % 7, dow + 7):
dates.append(days_of_week[i % 7])
# and some more relative days starting from next week
dates += ["1w", "2w", "1m", "2m", "3m", "1y"]
return dates
class TopydoCompleter(Completer):
def __init__(self, p_todolist):
self.todolist = p_todolist
def _subcommands(self, p_word_before_cursor):
subcommands = [sc for sc in sorted(_SUBCOMMAND_MAP.keys()) if sc.startswith(p_word_before_cursor)]
for command in subcommands:
yield Completion(command, -len(p_word_before_cursor))
def _projects(self, p_word_before_cursor):
projects = [p for p in self.todolist.projects() if p.startswith(p_word_before_cursor[1:])]
for project in projects:
yield Completion("+" + project, -len(p_word_before_cursor))
def _contexts(self, p_word_before_cursor):
contexts = [c for c in self.todolist.contexts() if c.startswith(p_word_before_cursor[1:])]
for context in contexts:
yield Completion("@" + context, -len(p_word_before_cursor))
def _dates(self, p_word_before_cursor):
to_absolute = lambda s: relative_date_to_date(s).isoformat()
start_value_pos = p_word_before_cursor.find(':') + 1
value = p_word_before_cursor[start_value_pos:]
for reldate in _date_suggestions():
if not reldate.startswith(value):
continue
yield Completion(reldate, -len(value), display_meta=to_absolute(reldate))
def get_completions(self, p_document, p_complete_event):
# include all characters except whitespaces (for + and @)
word_before_cursor = p_document.get_word_before_cursor(True)
is_first_word = not re.match(r'\s*\S+\s', p_document.current_line_before_cursor)
if is_first_word:
return self._subcommands(word_before_cursor)
elif word_before_cursor.startswith('+'):
return self._projects(word_before_cursor)
elif word_before_cursor.startswith('@'):
return self._contexts(word_before_cursor)
elif word_before_cursor.startswith(config().tag_due() + ':'):
return self._dates(word_before_cursor)
elif word_before_cursor.startswith(config().tag_start() + ':'):
return self._dates(word_before_cursor)
return []
# 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/>.
""" Entry file for the Python todo.txt CLI. """
import sys
import getopt
from topydo.cli.CLIApplicationBase import MAIN_OPTS
from topydo.cli.CLI import CLIApplication
from topydo.cli.Prompt import PromptApplication
def main():
""" Main entry point of the CLI. """
try:
args = sys.argv[1:]
try:
opts, args = getopt.getopt(args, MAIN_OPTS)
except getopt.GetoptError as e:
error(str(e))
sys.exit(1)
if args[0] == 'prompt':
PromptApplication().run()
else:
CLIApplication().run()
except IndexError:
CLIApplication().run()
if __name__ == '__main__':
main()
......@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.DCommand import DCommand
from topydo.commands.DCommand import DCommand
class DeleteCommand(DCommand):
def __init__(self, p_args, p_todolist,
......
......@@ -16,7 +16,7 @@
from datetime import date
from topydo.lib.DCommand import DCommand
from topydo.commands.DCommand import DCommand
from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.Recurrence import advance_recurring_todo, strict_advance_recurring_todo, NoRecurrenceException
......
......@@ -18,7 +18,9 @@ import os
from subprocess import call, check_call, CalledProcessError
import tempfile
from topydo.lib.ListCommand import ListCommand
from six import text_type, u
from topydo.commands.ListCommand import ListCommand
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.Config import config
from topydo.lib.Todo import Todo
......@@ -54,7 +56,7 @@ class EditCommand(MultiCommand, ListCommand):
def _todos_to_temp(self):
f = tempfile.NamedTemporaryFile()
for todo in self.todos:
f.write((str(todo) + "\n").encode('utf-8'))
f.write((text_type(todo) + "\n").encode('utf-8'))
f.seek(0)
return f
......@@ -81,7 +83,7 @@ class EditCommand(MultiCommand, ListCommand):
if len(self.invalid_numbers) > 1 or len(self.invalid_numbers) > 0 and len(self.todos) > 0:
for number in self.invalid_numbers:
errors.append("Invalid todo number given: {}.".format(number))
errors.append(u("Invalid todo number given: {}.").format(number))
elif len(self.invalid_numbers) == 1 and len(self.todos) == 0:
errors.append("Invalid todo number given.")
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 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 sys
from topydo.lib.Command import Command
class ExitCommand(Command):
"""
A command that exits topydo. Used for the 'exit' and 'quit' subcommands on
the prompt CLI.
"""
def __init__(self, p_args, p_todolist, p_output, p_error, p_input):
super(ExitCommand, self).__init__(p_args, p_todolist, p_output, p_error,
p_input)
def execute(self):
if not super(ExitCommand, self).execute():
return False
sys.exit(0)
......@@ -19,7 +19,7 @@ Implements a subcommand that outputs an iCalendar file.
"""
from topydo.lib.IcalPrinter import IcalPrinter
from topydo.lib.ListCommand import ListCommand
from topydo.commands.ListCommand import ListCommand
class IcalCommand(ListCommand):
def __init__(self, p_args, p_todolist,
......
......@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
from six import text_type
from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date
......@@ -63,7 +64,7 @@ class GrepFilter(Filter):
super(GrepFilter, self).__init__()
# convert to string in case we receive integers
self.expression = str(p_expression)
self.expression = text_type(p_expression)
if p_case_sensitive != None:
self.case_sensitive = p_case_sensitive
......
......@@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from six import u
from topydo.lib.Command import Command
from topydo.lib.TodoListBase import InvalidTodoException
......@@ -52,7 +54,7 @@ class MultiCommand(Command):
if len(self.invalid_numbers) > 1 or len(self.invalid_numbers) > 0 and len(self.todos) > 0:
for number in self.invalid_numbers:
errors.append("Invalid todo number given: {}.".format(number))
errors.append(u("Invalid todo number given: {}.").format(number))
elif len(self.invalid_numbers) == 1 and len(self.todos) == 0:
errors.append("Invalid todo number given.")
elif len(self.todos) == 0 and len(self.invalid_numbers) == 0:
......
......@@ -20,10 +20,12 @@ This module contains the class that represents a single todo item.
from datetime import date
import re
from six import python_2_unicode_compatible, u
from topydo.lib.TodoParser import parse_line
from topydo.lib.Utils import is_valid_priority
@python_2_unicode_compatible
class TodoBase(object):
"""
This class represents a single todo item in a todo.txt file. It maintains
......@@ -211,7 +213,7 @@ class TodoBase(object):
self.src = re.sub(
r'^(x \d{4}-\d{2}-\d{2} |\([A-Z]\) )?(\d{4}-\d{2}-\d{2} )?(.*)$',
lambda m: \
"{}{} {}".format(m.group(1) or '', p_date.isoformat(), m.group(3)),
u("{}{} {}").format(m.group(1) or '', p_date.isoformat(), m.group(3)),
self.src)
def creation_date(self):
......
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