Commit 82ab00a6 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'ical'

parents 23985524 57a6ab53
...@@ -8,6 +8,9 @@ setup( ...@@ -8,6 +8,9 @@ setup(
author = "Bram Schoenmakers", author = "Bram Schoenmakers",
author_email = "me@bramschoenmakers.nl", author_email = "me@bramschoenmakers.nl",
url = "https://github.com/bram85/topydo", url = "https://github.com/bram85/topydo",
extras_require = {
'ical': ['icalendar'],
},
entry_points= { entry_points= {
'console_scripts': ['topydo = topydo.cli.Main:main'], 'console_scripts': ['topydo = topydo.cli.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 re
import unittest
from topydo.lib.Config import config
import CommandTest
from topydo.lib.IcalCommand import IcalCommand
import TestFacilities
class IcalCommandTest(CommandTest.CommandTest):
def setUp(self):
super(IcalCommandTest, self).setUp()
self.todolist = TestFacilities.load_file_to_todolist("test/data/ListCommandTest.txt")
def test_ical(self):
def replace_ical_tags(p_text):
# replace identifiers with dots, since they're random.
result = re.sub(r'\bical:....\b', 'ical:....', p_text)
result = re.sub(r'\bUID:....\b', 'UID:....', result)
return result
command = IcalCommand([""], self.todolist, self.out, self.error)
command.execute()
self.assertTrue(self.todolist.is_dirty())
icaltext = ""
with open('test/data/ListCommandTest.ics', 'r') as ical:
icaltext = "".join(ical.readlines())
self.assertEquals(replace_ical_tags(self.output), replace_ical_tags(icaltext))
self.assertEquals(self.errors, "")
def test_help(self):
command = IcalCommand(["help"], self.todolist, self.out, self.error)
command.execute()
self.assertEquals(self.output, "")
self.assertEquals(self.errors, command.usage() + "\n\n" + command.help() + "\n")
if __name__ == '__main__':
unittest.main()
...@@ -26,10 +26,6 @@ class ListCommandTest(CommandTest.CommandTest): ...@@ -26,10 +26,6 @@ class ListCommandTest(CommandTest.CommandTest):
super(ListCommandTest, self).setUp() super(ListCommandTest, self).setUp()
self.todolist = TestFacilities.load_file_to_todolist("test/data/ListCommandTest.txt") self.todolist = TestFacilities.load_file_to_todolist("test/data/ListCommandTest.txt")
def tearDown(self):
# restore to the default configuration in case a custom one was set
config("")
def test_list1(self): def test_list1(self):
command = ListCommand([""], self.todolist, self.out, self.error) command = ListCommand([""], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -26,10 +26,6 @@ class SortCommandTest(CommandTest.CommandTest): ...@@ -26,10 +26,6 @@ class SortCommandTest(CommandTest.CommandTest):
super(SortCommandTest, self).setUp() super(SortCommandTest, self).setUp()
self.todolist = TestFacilities.load_file_to_todolist("test/data/SorterTest1.txt") self.todolist = TestFacilities.load_file_to_todolist("test/data/SorterTest1.txt")
def tearDown(self):
# restore to the default configuration in case a custom one was set
config("")
def test_sort1(self): def test_sort1(self):
""" Alphabetically sorted """ """ Alphabetically sorted """
command = SortCommand(["text"], self.todolist, self.out, self.error) command = SortCommand(["text"], self.todolist, self.out, self.error)
......
...@@ -36,10 +36,6 @@ class TodoListTester(TopydoTest): ...@@ -36,10 +36,6 @@ class TodoListTester(TopydoTest):
self.text = ''.join(lines) self.text = ''.join(lines)
self.todolist = TodoList(lines) self.todolist = TodoList(lines)
def tearDown(self):
# restore to the default configuration in case a custom one was set
config("")
def test_contexts(self): def test_contexts(self):
self.assertEquals(set(['Context1', 'Context2']), \ self.assertEquals(set(['Context1', 'Context2']), \
self.todolist.contexts()) self.todolist.contexts())
......
...@@ -19,9 +19,9 @@ import unittest ...@@ -19,9 +19,9 @@ import unittest
from topydo.lib.Config import config from topydo.lib.Config import config
class TopydoTest(unittest.TestCase): class TopydoTest(unittest.TestCase):
def setUp(self): def tearDown(self):
""" """
Make sure that every test case starts with a clean configuration. Make sure that every test case leaves a clean configuration.
""" """
config("") config("")
This diff was suppressed by a .gitattributes entry.
...@@ -41,6 +41,7 @@ Available commands: ...@@ -41,6 +41,7 @@ Available commands:
* dep * dep
* do * do
* edit * edit
* ical
* ls * ls
* listcon (lscon) * listcon (lscon)
* listprojects (lsprj) * listprojects (lsprj)
...@@ -97,6 +98,7 @@ from topydo.lib.DepCommand import DepCommand ...@@ -97,6 +98,7 @@ from topydo.lib.DepCommand import DepCommand
from topydo.lib.DepriCommand import DepriCommand from topydo.lib.DepriCommand import DepriCommand
from topydo.lib.DoCommand import DoCommand from topydo.lib.DoCommand import DoCommand
from topydo.lib.EditCommand import EditCommand from topydo.lib.EditCommand import EditCommand
from topydo.lib.IcalCommand import IcalCommand
from topydo.lib.ListCommand import ListCommand from topydo.lib.ListCommand import ListCommand
from topydo.lib.ListContextCommand import ListContextCommand from topydo.lib.ListContextCommand import ListContextCommand
from topydo.lib.ListProjectCommand import ListProjectCommand from topydo.lib.ListProjectCommand import ListProjectCommand
...@@ -118,6 +120,7 @@ SUBCOMMAND_MAP = { ...@@ -118,6 +120,7 @@ SUBCOMMAND_MAP = {
'depri': DepriCommand, 'depri': DepriCommand,
'do': DoCommand, 'do': DoCommand,
'edit': EditCommand, 'edit': EditCommand,
'ical': IcalCommand,
'ls': ListCommand, 'ls': ListCommand,
'lscon': ListContextCommand, 'lscon': ListContextCommand,
'listcon': ListContextCommand, 'listcon': ListContextCommand,
......
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -21,7 +21,7 @@ import re ...@@ -21,7 +21,7 @@ import re
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.Command import Command from topydo.lib.Command import Command
from topydo.lib.PrettyPrinter import pretty_print from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.RelativeDate import relative_date_to_date from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
...@@ -93,7 +93,8 @@ class AddCommand(Command): ...@@ -93,7 +93,8 @@ class AddCommand(Command):
self.todo = self.todolist.add(self.text) self.todo = self.todolist.add(self.text)
self._postprocess_input_todo() self._postprocess_input_todo()
self.out(pretty_print(self.todo, [self.todolist.pp_number()])) self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.out(self.printer.print_todo(self.todo))
else: else:
self.error(self.usage()) self.error(self.usage())
......
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.PrettyPrinter import pretty_print from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
class AppendCommand(Command): class AppendCommand(Command):
...@@ -37,7 +37,9 @@ class AppendCommand(Command): ...@@ -37,7 +37,9 @@ class AppendCommand(Command):
if text: if text:
todo = self.todolist.todo(number) todo = self.todolist.todo(number)
self.todolist.append(todo, text) self.todolist.append(todo, text)
self.out(pretty_print(todo, [self.todolist.pp_number()]))
self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.out(self.printer.print_todo(todo))
else: else:
self.error(self.usage()) self.error(self.usage())
except InvalidCommandArgument: except InvalidCommandArgument:
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
import getopt import getopt
from topydo.lib.PrettyPrinter import PrettyPrinter
class InvalidCommandArgument(Exception): class InvalidCommandArgument(Exception):
pass pass
...@@ -49,6 +51,9 @@ class Command(object): ...@@ -49,6 +51,9 @@ class Command(object):
self.error = p_err self.error = p_err
self.prompt = p_prompt self.prompt = p_prompt
# make pretty printer available
self.printer = PrettyPrinter()
def execute(self): def execute(self):
""" """
Execute the command. Intercepts the help subsubcommand to show the help Execute the command. Intercepts the help subsubcommand to show the help
......
...@@ -16,8 +16,9 @@ ...@@ -16,8 +16,9 @@
import re import re
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command
from topydo.lib.PrettyPrinter import pretty_print, pretty_print_list from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
class DCommand(Command): class DCommand(Command):
...@@ -72,8 +73,9 @@ class DCommand(Command): ...@@ -72,8 +73,9 @@ class DCommand(Command):
) )
def _print_list(self, p_todos): def _print_list(self, p_todos):
filters = [self.todolist.pp_number()] printer = PrettyPrinter()
self.out("\n".join(pretty_print_list(p_todos, filters))) printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.out(printer.print_list(p_todos))
def prompt_text(self): def prompt_text(self):
return "Yes or no? [y/N] " return "Yes or no? [y/N] "
...@@ -93,7 +95,7 @@ class DCommand(Command): ...@@ -93,7 +95,7 @@ class DCommand(Command):
if not self.force and re.match('^y(es)?$', confirmation, re.I): if not self.force and re.match('^y(es)?$', confirmation, re.I):
for child in children: for child in children:
self.execute_specific_core(child) self.execute_specific_core(child)
self.out(self.prefix() + pretty_print(child)) self.out(self.prefix() + self.printer.print_todo(child))
def _print_unlocked_todos(self, p_old, p_new): def _print_unlocked_todos(self, p_old, p_new):
delta = [todo for todo in p_new if todo not in p_old] delta = [todo for todo in p_new if todo not in p_old]
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.DCommand import DCommand from topydo.lib.DCommand import DCommand
from topydo.lib.PrettyPrinter import pretty_print
class DeleteCommand(DCommand): class DeleteCommand(DCommand):
def __init__(self, p_args, p_todolist, def __init__(self, p_args, p_todolist,
...@@ -35,7 +34,7 @@ class DeleteCommand(DCommand): ...@@ -35,7 +34,7 @@ class DeleteCommand(DCommand):
self.todolist.delete(p_todo) self.todolist.delete(p_todo)
def execute_specific(self, p_todo): def execute_specific(self, p_todo):
self.out(self.prefix() + pretty_print(p_todo)) self.out(self.prefix() + self.printer.print_todo(p_todo))
self.execute_specific_core(p_todo) self.execute_specific_core(p_todo)
def usage(self): def usage(self):
......
...@@ -97,7 +97,8 @@ class DepCommand(Command): ...@@ -97,7 +97,8 @@ class DepCommand(Command):
if todos: if todos:
sorter = Sorter(config().sort_string()) sorter = Sorter(config().sort_string())
instance_filter = Filter.InstanceFilter(todos) instance_filter = Filter.InstanceFilter(todos)
view = View(sorter, [instance_filter], self.todolist) view = View(sorter, [instance_filter], self.todolist,
self.printer)
self.out(view.pretty_print()) self.out(view.pretty_print())
except InvalidTodoException: except InvalidTodoException:
self.error("Invalid todo number given.") self.error("Invalid todo number given.")
......
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.PrettyPrinter import pretty_print
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
class DepriCommand(Command): class DepriCommand(Command):
...@@ -37,7 +36,7 @@ class DepriCommand(Command): ...@@ -37,7 +36,7 @@ class DepriCommand(Command):
if todo.priority() != None: if todo.priority() != None:
self.todolist.set_priority(todo, None) self.todolist.set_priority(todo, None)
self.out("Priority removed.") self.out("Priority removed.")
self.out(pretty_print(todo)) self.out(self.printer.print_todo(todo))
except InvalidCommandArgument: except InvalidCommandArgument:
self.error(self.usage()) self.error(self.usage())
except (InvalidTodoException): except (InvalidTodoException):
......
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
from datetime import date from datetime import date
from topydo.lib.DCommand import DCommand from topydo.lib.DCommand import DCommand
from topydo.lib.PrettyPrinter import pretty_print from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.Recurrence import advance_recurring_todo, strict_advance_recurring_todo from topydo.lib.Recurrence import advance_recurring_todo, strict_advance_recurring_todo
from topydo.lib.Utils import date_string_to_date from topydo.lib.Utils import date_string_to_date
...@@ -56,7 +57,10 @@ class DoCommand(DCommand): ...@@ -56,7 +57,10 @@ class DoCommand(DCommand):
self.completion_date) self.completion_date)
self.todolist.add_todo(new_todo) self.todolist.add_todo(new_todo)
self.out(pretty_print(new_todo, [self.todolist.pp_number()]))
printer = PrettyPrinter()
printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.out(printer.print_todo(new_todo))
def prompt_text(self): def prompt_text(self):
return "Also mark subtasks as done? [y/N] " return "Also mark subtasks as done? [y/N] "
...@@ -77,7 +81,9 @@ class DoCommand(DCommand): ...@@ -77,7 +81,9 @@ class DoCommand(DCommand):
""" Actions specific to this command. """ """ Actions specific to this command. """
self._handle_recurrence(p_todo) self._handle_recurrence(p_todo)
self.execute_specific_core(p_todo) self.execute_specific_core(p_todo)
self.out(self.prefix() + pretty_print(p_todo))
printer = PrettyPrinter()
self.out(self.prefix() + printer.print_todo(p_todo))
def execute_specific_core(self, p_todo): def execute_specific_core(self, p_todo):
""" """
......
# 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/>.
"""
Implements a subcommand that outputs an iCalendar file.
"""
from topydo.lib.IcalPrinter import IcalPrinter
from topydo.lib.ListCommand import ListCommand
class IcalCommand(ListCommand):
def __init__(self, p_args, p_todolist,
p_out=lambda a: None,
p_err=lambda a: None,
p_prompt=lambda a: None):
super(IcalCommand, self).__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.printer = IcalPrinter(p_todolist)
def _print(self):
self.out(str(self._view()))
def execute(self):
try:
import icalendar as _
except ImportError:
self.error("icalendar package is not installed.")
return False
return super(IcalCommand, self).execute()
def usage(self):
return """Synopsis: ical [-x] [expression]"""
def help(self):
return """\
Similar to the 'ls' subcommand, except that the todos are printed in iCalendar
format (RFC 2445) that can be imported by other calendar applications.
By default prints the active todo items, possibly filtered by the given
expression.
For the supported options, please refer to the help text of 'ls'
(topydo help ls).
While specifying the sort order is supported (-s flag), like in 'ls', this is
not meaningful in the context of an iCalendar file.
Note: be aware that this is not necessarily a read-only operation. This
subcommand may add ical tags to the printed todo items containing a unique ID.
Completed todo items may be archived.
Note: topydo does not support reading iCal files, this is merely a dump.
Changes made with other iCalendar enabled applications will not be processed.
Suggested usage is to use the output as a read-only calendar.
"""
# 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/>.
"""
Provides a printer that transforms a list of Todo items to an iCalendar
file according to RFC 2445.
"""
try:
import icalendar as ical
ICAL_PRESENT = True
except ImportError:
ICAL_PRESENT = False
from datetime import datetime, time
import random
import string
from topydo.lib.PrettyPrinter import Printer
def _convert_priority(p_priority):
"""
Converts todo.txt priority to an iCalendar priority (RFC 2445).
Priority A gets priority 1, priority B gets priority 5 and priority C-F get
priorities 6-9. This scheme makes sure that clients that use "high",
"medium" and "low" show the correct priority.
"""
result = 0
prio_map = {
'A': 1,
'B': 5,
'C': 6,
'D': 7,
'E': 8,
'F': 9,
}
try:
result = prio_map[p_priority]
except KeyError:
if p_priority:
# todos with no priority have priority None, and result of this
# function will be 0. For all other letters, return 9 (lowest
# priority in RFC 2445).
result = 9
return result
class IcalPrinter(Printer):
"""
A printer that converts a list of Todo items to a string in iCalendar
format (RFC 2445).
https://www.rfc-editor.org/rfc/rfc2445.txt
"""
def __init__(self, p_todolist):
super(IcalPrinter, self).__init__()
self.todolist = p_todolist
def print_todo(self, p_todo):
return self._convert_todo(p_todo).to_ical() if ICAL_PRESENT else ""
def print_list(self, p_todos):
result = ""
if ICAL_PRESENT:
cal = ical.Calendar()
cal.add('prodid', '-//bramschoenmakers.nl//topydo//')
cal.add('version', '2.0')
for todo in p_todos:
cal.add_component(self._convert_todo(todo))
result = cal.to_ical()
return result
def _convert_todo(self, p_todo):
""" Converts a Todo instance (Topydo) to an icalendar Todo instance. """
def _get_uid(p_todo):
"""
Gets a unique ID from a todo item, stored by the ical tag. If the
tag is not present, a random value is assigned to it and returned.
"""
def generate_uid(p_length=4):
"""
Generates a random string of the given length, used as
identifier.
"""
return ''.join(
random.choice(string.ascii_letters + string.digits)
for i in xrange(p_length))
uid = p_todo.tag_value('ical')
if not uid:
uid = generate_uid()
p_todo.set_tag('ical', uid)
self.todolist.set_dirty()
return uid
result = ical.Todo()
# this should be called first, it may set the ical: tag and therefore
# change the source() output.
result['uid'] = _get_uid(p_todo)
result['summary'] = ical.vText(p_todo.text())
result['description'] = ical.vText(p_todo.source())
result.add('priority', _convert_priority(p_todo.priority()))
start = p_todo.start_date()
if start:
result.add('dtstart', start)
due = p_todo.due_date()
if due:
result.add('due', due)
created = p_todo.creation_date()
if created:
result.add('created', created)
completed = p_todo.completion_date()
if completed:
completed = datetime.combine(completed, time(0, 0))
result.add('completed', completed)
return result
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -19,8 +19,9 @@ import re ...@@ -19,8 +19,9 @@ import re
from topydo.lib.Command import Command from topydo.lib.Command import Command
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import Filter from topydo.lib import Filter
from topydo.lib.PrettyPrinter import pp_indent from topydo.lib.PrettyPrinterFilter import PrettyPrinterIndentFilter
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from topydo.lib.View import View
class ListCommand(Command): class ListCommand(Command):
def __init__(self, p_args, p_todolist, def __init__(self, p_args, p_todolist,
...@@ -74,17 +75,23 @@ class ListCommand(Command): ...@@ -74,17 +75,23 @@ class ListCommand(Command):
return filters return filters
def _view(self):
sorter = Sorter(self.sort_expression)
filters = self._filters()
return View(sorter, filters, self.todolist, self.printer)
def _print(self):
""" Prints the todos. """
indent = config().list_indent()
self.out(self._view().pretty_print([PrettyPrinterIndentFilter(indent)]))
def execute(self): def execute(self):
if not super(ListCommand, self).execute(): if not super(ListCommand, self).execute():
return False return False
self._process_flags() self._process_flags()
self._print()
sorter = Sorter(self.sort_expression)
filters = self._filters()
pp_filters = [pp_indent(config().list_indent())]
self.out(self.todolist.view(sorter, filters).pretty_print(pp_filters))
def usage(self): def usage(self):
return """Synopsis: ls [-x] [-s <sort_expression>] [expression]""" return """Synopsis: ls [-x] [-s <sort_expression>] [expression]"""
......
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -18,7 +18,7 @@ from datetime import date, timedelta ...@@ -18,7 +18,7 @@ from datetime import date, timedelta
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.PrettyPrinter import pretty_print from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.RelativeDate import relative_date_to_date from topydo.lib.RelativeDate import relative_date_to_date
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import date_string_to_date from topydo.lib.Utils import date_string_to_date
...@@ -74,7 +74,8 @@ class PostponeCommand(Command): ...@@ -74,7 +74,8 @@ class PostponeCommand(Command):
todo.set_tag(config().tag_due(), new_due.isoformat()) todo.set_tag(config().tag_due(), new_due.isoformat())
self.todolist.set_dirty() self.todolist.set_dirty()
self.out(pretty_print(todo, [self.todolist.pp_number()])) self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.out(self.printer.print_todo(todo))
else: else:
self.error("Invalid date pattern given.") self.error("Invalid date pattern given.")
......
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -14,75 +14,52 @@ ...@@ -14,75 +14,52 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Provides a function to pretty print a list of todo items. """ class Printer(object):
import re
from topydo.lib.Config import config
PRIORITY_COLORS = {
'A': '\033[36m', # cyan
'B': '\033[33m', # yellow
'C': '\033[34m' # blue
}
PROJECT_COLOR = '\033[31m' # red
NEUTRAL_COLOR = '\033[0m'
def pp_color(p_todo_str, p_todo):
""" """
Adds colors to the todo string by inserting ANSI codes. An abstract class that turns todo items into strings.
Should be passed as a filter in the filter list of pretty_print() Subclasses must at least implement the print_todo method.
""" """
def print_todo(self, p_todo):
if config().colors(): """ Base implementation. Simply returns the string conversion. """
color = NEUTRAL_COLOR return str(p_todo)
try:
color = PRIORITY_COLORS[p_todo.priority()] def print_list(self, p_todos):
except KeyError: """
pass Given a list of todo items, pretty print it and return a list of
formatted strings.
p_todo_str = color + p_todo_str + NEUTRAL_COLOR """
return "\n".join([self.print_todo(todo) for todo in p_todos])
if config().highlight_projects_contexts():
p_todo_str = re.sub( class PrettyPrinter(Printer):
r'\B(\+|@)(\S*\w)',
PROJECT_COLOR + r'\g<0>' + color,
p_todo_str)
p_todo_str += NEUTRAL_COLOR
return p_todo_str
def pp_indent(p_indent=0):
return lambda s, t: ' ' * p_indent + s
def pretty_print(p_todo, p_filters=None):
""" """
Given a todo item, pretty print it and return a list of formatted strings. Prints todo items on a single line, decorated by the filters passed by
the caller.
p_filters is a list of functions that transform the output string, each
function accepting two arguments:
* the todo's text that has to be modified; The caller can adjust the output by passing on a set of filters, that may
* the todo object itself which allows for obtaining relevant information. add colors, indentation, etc. These filters are found in the
PrettyPrinterFilter module.
Example is pp_color in this fle.
""" """
p_filters = p_filters or [] def __init__(self):
"""
Constructor.
"""
super(PrettyPrinter, self).__init__()
self.filters = []
todo_str = str(p_todo) def add_filter(self, p_filter):
"""
Adds a filter to be applied when calling print_todo.
for f in p_filters: p_filter is an instance of a PrettyPrinterFilter.
todo_str = f(todo_str, p_todo) """
self.filters.append(p_filter)
return todo_str def print_todo(self, p_todo):
""" Given a todo item, pretty print it. """
todo_str = str(p_todo)
def pretty_print_list(p_todos, p_filters=None): for ppf in self.filters:
""" todo_str = ppf.filter(todo_str, p_todo)
Given a list of todo items, pretty print it and return a list of
formatted strings. return todo_str
"""
p_filters = p_filters or []
return [pretty_print(todo, p_filters) for todo in p_todos]
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Provides filters used for pretty printing. """
import re
from topydo.lib.Config import config
class PrettyPrinterFilter(object):
"""
Base class for a pretty printer filter.
Subclasses must reimplement the filter method.
"""
def filter(self, p_todo_str, _):
""" Default implementation returns an unmodified todo string. """
return p_todo_str
PRIORITY_COLORS = {
'A': '\033[36m', # cyan
'B': '\033[33m', # yellow
'C': '\033[34m' # blue
}
PROJECT_COLOR = '\033[31m' # red
NEUTRAL_COLOR = '\033[0m'
class PrettyPrinterColorFilter(PrettyPrinterFilter):
"""
Adds colors to the todo string by inserting ANSI codes.
Should be passed as a filter in the filter list of pretty_print()
"""
def filter(self, p_todo_str, p_todo):
""" Applies the colors. """
if config().colors():
color = NEUTRAL_COLOR
try:
color = PRIORITY_COLORS[p_todo.priority()]
except KeyError:
pass
p_todo_str = color + p_todo_str + NEUTRAL_COLOR
if config().highlight_projects_contexts():
p_todo_str = re.sub(
r'\B(\+|@)(\S*\w)',
PROJECT_COLOR + r'\g<0>' + color,
p_todo_str)
p_todo_str += NEUTRAL_COLOR
return p_todo_str
class PrettyPrinterIndentFilter(PrettyPrinterFilter):
""" Adds indentation to the todo item. """
def __init__(self, p_indent=0):
super(PrettyPrinterIndentFilter, self).__init__()
self.indent = p_indent
def filter(self, p_todo_str, _):
""" Applies the indentation. """
return ' ' * self.indent + p_todo_str
class PrettyPrinterNumbers(PrettyPrinterFilter):
""" Prepends the todo's number, retrieved from the todolist. """
def __init__(self, p_todolist):
super(PrettyPrinterNumbers, self).__init__()
self.todolist = p_todolist
def filter(self, p_todo_str, p_todo):
""" Prepends the number to the todo string. """
return "|{:>3}| {}".format(self.todolist.number(p_todo), p_todo_str)
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.PrettyPrinter import pretty_print
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.Utils import is_valid_priority from topydo.lib.Utils import is_valid_priority
...@@ -48,7 +47,7 @@ class PriorityCommand(Command): ...@@ -48,7 +47,7 @@ class PriorityCommand(Command):
elif not old_priority: elif not old_priority:
self.out("Priority set to {}.".format(priority)) self.out("Priority set to {}.".format(priority))
self.out(pretty_print(todo)) self.out(self.printer.print_todo(todo))
else: else:
self.error("Invalid priority given.") self.error("Invalid priority given.")
except InvalidCommandArgument: except InvalidCommandArgument:
......
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.PrettyPrinterFilter import PrettyPrinterNumbers
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.PrettyPrinter import pretty_print
class TagCommand(Command): class TagCommand(Command):
def __init__(self, p_args, p_todolist, def __init__(self, p_args, p_todolist,
...@@ -60,7 +60,8 @@ class TagCommand(Command): ...@@ -60,7 +60,8 @@ class TagCommand(Command):
self.value = "" self.value = ""
def _print(self): def _print(self):
self.out(pretty_print(self.todo, [self.todolist.pp_number()])) self.printer.add_filter(PrettyPrinterNumbers(self.todolist))
self.out(self.printer.print_todo(self.todo))
def _choose(self): def _choose(self):
""" """
......
...@@ -24,7 +24,7 @@ import re ...@@ -24,7 +24,7 @@ import re
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import Filter from topydo.lib import Filter
from topydo.lib.HashListValues import hash_list_values from topydo.lib.HashListValues import hash_list_values
from topydo.lib.PrettyPrinter import pretty_print_list from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from topydo.lib.View import View from topydo.lib.View import View
...@@ -242,14 +242,6 @@ class TodoListBase(object): ...@@ -242,14 +242,6 @@ class TodoListBase(object):
except (ValueError, KeyError): except (ValueError, KeyError):
raise InvalidTodoException raise InvalidTodoException
def pp_number(self):
"""
A filter for the pretty printer to append the todo number to the
printed todo.
"""
return lambda p_todo_str, p_todo: \
"|{:>3}| {}".format(self.number(p_todo), p_todo_str)
def _update_todo_ids(self): def _update_todo_ids(self):
# the idea is to have a hash that is independent of the position of the # the idea is to have a hash that is independent of the position of the
# todo. Use the text (without tags) of the todo to keep the id as stable # todo. Use the text (without tags) of the todo to keep the id as stable
...@@ -264,5 +256,6 @@ class TodoListBase(object): ...@@ -264,5 +256,6 @@ class TodoListBase(object):
self._id_todo_map[uid] = todo self._id_todo_map[uid] = todo
def __str__(self): def __str__(self):
return '\n'.join(pretty_print_list(self._todos)) printer = PrettyPrinter()
return printer.print_list(self._todos)
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 Bram Schoenmakers <me@bramschoenmakers.nl> # Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -16,19 +16,29 @@ ...@@ -16,19 +16,29 @@
""" A view is a list of todos, sorted and filtered. """ """ A view is a list of todos, sorted and filtered. """
from topydo.lib.PrettyPrinter import pretty_print_list, pp_color from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterColorFilter,
PrettyPrinterNumbers
)
from topydo.lib.PrettyPrinter import PrettyPrinter
class View(object): class View(object):
""" """
A view is instantiated by a todo list, usually obtained from a todo.txt A view is instantiated by a todo list, usually obtained from a todo.txt
file. Also a sorter and a list of filters should be given that is applied file. Also a sorter and a list of filters should be given that is applied
to the list. to the list.
A printer can be passed, but it won't be used when pretty_print() is
called, since it will instantiate its own pretty printer instance.
""" """
def __init__(self, p_sorter, p_filters, p_todolist): def __init__(self, p_sorter, p_filters, p_todolist,
p_printer=PrettyPrinter()):
self._todolist = p_todolist self._todolist = p_todolist
self._viewdata = [] self._viewdata = []
self._sorter = p_sorter self._sorter = p_sorter
self._filters = p_filters self._filters = p_filters
self._printer = p_printer
self.update() self.update()
...@@ -45,8 +55,16 @@ class View(object): ...@@ -45,8 +55,16 @@ class View(object):
def pretty_print(self, p_pp_filters=None): def pretty_print(self, p_pp_filters=None):
""" Pretty prints the view. """ """ Pretty prints the view. """
p_pp_filters = p_pp_filters or [] p_pp_filters = p_pp_filters or []
pp_filters = [self._todolist.pp_number(), pp_color] + p_pp_filters
return '\n'.join(pretty_print_list(self._viewdata, pp_filters)) # since we're using filters, always use PrettyPrinter
printer = PrettyPrinter()
printer.add_filter(PrettyPrinterNumbers(self._todolist))
printer.add_filter(PrettyPrinterColorFilter())
for ppf in p_pp_filters:
printer.add_filter(ppf)
return printer.print_list(self._viewdata)
def __str__(self): def __str__(self):
return '\n'.join(pretty_print_list(self._viewdata)) return self._printer.print_list(self._viewdata)
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