Commit 09532f61 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Refactored the View class and Unicode support cleanups.

This started as a refactoring of the View class: to get rid of the
pretty_print() function and anything releated to printers. So the View
class now solely focusses on sorting and viewing a bunch of todos. It's
up to the users of the View class to retrieve the todos in the view and
pass them on to a Printer instance.

The View class was quite involved with the proper Unicode support that
was added lately. There were still encoded string passed through this
class rather than Unicode strings. Getting rid of this affected
basically all other places in the codebase.

The result is that there are less string conversions; printed todo items
stay Unicode as long as possible, until they're actually written out to
standard output or to a file. I got rid of one text_type() call from the
six library, all @python_2_unicode_compatible decorators are gone and
the tests no longer need the utf8() function because everything is
Unicode now.

Remove utf8 function.
parent 34a5d7a4
...@@ -29,7 +29,7 @@ from io import StringIO ...@@ -29,7 +29,7 @@ from io import StringIO
from topydo.commands import AddCommand from topydo.commands import AddCommand
from topydo.commands import ListCommand from topydo.commands import ListCommand
from test.CommandTest import CommandTest, utf8 from test.CommandTest import CommandTest
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import TodoList from topydo.lib import TodoList
...@@ -127,7 +127,7 @@ class AddCommandTest(CommandTest): ...@@ -127,7 +127,7 @@ class AddCommandTest(CommandTest):
self.assertFalse(self.todolist.todo(1).has_tag("after")) self.assertFalse(self.todolist.todo(1).has_tag("after"))
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo") self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo")
self.assertEqual(self.output, "| 1| " + str(self.todolist.todo(1)) + "\n") self.assertEqual(self.output, "| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep5(self): def test_add_dep5(self):
...@@ -137,7 +137,7 @@ class AddCommandTest(CommandTest): ...@@ -137,7 +137,7 @@ class AddCommandTest(CommandTest):
self.assertFalse(self.todolist.todo(1).has_tag("after")) self.assertFalse(self.todolist.todo(1).has_tag("after"))
self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo") self.assertEqual(self.todolist.todo(1).source(), self.today + " Foo")
self.assertEqual(self.output, "| 1| " + str(self.todolist.todo(1)) + "\n") self.assertEqual(self.output, "| 1| " + self.todolist.todo(1).source() + "\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_dep6(self): def test_add_dep6(self):
...@@ -248,7 +248,7 @@ class AddCommandTest(CommandTest): ...@@ -248,7 +248,7 @@ class AddCommandTest(CommandTest):
command = AddCommand.AddCommand([u("Special \u25c4")], self.todolist, self.out, self.error) command = AddCommand.AddCommand([u("Special \u25c4")], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output, utf8(u("| 1| {} Special \u25c4\n").format(self.today))) self.assertEqual(self.output, u("| 1| {} Special \u25c4\n").format(self.today))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
@mock.patch("topydo.commands.AddCommand.stdin", StringIO(u("Fo\u00f3 due:tod id:1\nB\u0105r before:1"))) @mock.patch("topydo.commands.AddCommand.stdin", StringIO(u("Fo\u00f3 due:tod id:1\nB\u0105r before:1")))
...@@ -256,14 +256,14 @@ class AddCommandTest(CommandTest): ...@@ -256,14 +256,14 @@ class AddCommandTest(CommandTest):
command = AddCommand.AddCommand(["-f", "-"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["-f", "-"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output, utf8(u("| 1| {tod} Fo\u00f3 due:{tod} id:1\n| 2| {tod} B\u0105r p:1\n".format(tod=self.today)))) self.assertEqual(self.output, u("| 1| {tod} Fo\u00f3 due:{tod} id:1\n| 2| {tod} B\u0105r p:1\n".format(tod=self.today)))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_from_file(self): def test_add_from_file(self):
command = AddCommand.AddCommand(["-f", "test/data/AddCommandTest-from_file.txt"], self.todolist, self.out, self.error) command = AddCommand.AddCommand(["-f", "test/data/AddCommandTest-from_file.txt"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(self.output, utf8(u("| 1| {tod} Foo @fo\u00f3b\u0105r due:{tod} id:1\n| 2| {tod} Bar +baz t:{tod} p:1\n".format(tod=self.today)))) self.assertEqual(self.output, u("| 1| {tod} Foo @fo\u00f3b\u0105r due:{tod} id:1\n| 2| {tod} Bar +baz t:{tod} p:1\n".format(tod=self.today)))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_help(self): def test_help(self):
......
...@@ -31,8 +31,8 @@ class ArchiveCommandTest(CommandTest): ...@@ -31,8 +31,8 @@ class ArchiveCommandTest(CommandTest):
self.assertTrue(todolist.is_dirty()) self.assertTrue(todolist.is_dirty())
self.assertTrue(archive.is_dirty()) self.assertTrue(archive.is_dirty())
self.assertEqual(str(todolist), "x Not complete\n(C) Active") self.assertEqual(todolist.print_todos(), "x Not complete\n(C) Active")
self.assertEqual(str(archive), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete") self.assertEqual(archive.print_todos(), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
......
...@@ -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/>.
import unittest import unittest
from six import PY2
from topydo.lib.Utils import escape_ansi from topydo.lib.Utils import escape_ansi
from test.TopydoTest import TopydoTest from test.TopydoTest import TopydoTest
...@@ -34,13 +33,5 @@ class CommandTest(TopydoTest): ...@@ -34,13 +33,5 @@ class CommandTest(TopydoTest):
if p_error: if p_error:
self.errors += escape_ansi(p_error + "\n") 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__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -116,7 +116,7 @@ class DeleteCommandTest(CommandTest): ...@@ -116,7 +116,7 @@ class DeleteCommandTest(CommandTest):
command = DeleteCommand(["8to"], self.todolist, self.out, self.error) command = DeleteCommand(["8to"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(str(self.todolist), "Foo") self.assertEqual(self.todolist.print_todos(), "Foo")
self.assertRaises(InvalidTodoException, self.todolist.todo, 'b0n') self.assertRaises(InvalidTodoException, self.todolist.todo, 'b0n')
def test_multi_del1(self): def test_multi_del1(self):
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
import unittest import unittest
# We're searching for 'mock' # We're searching for 'mock'
# pylint: disable=no-name-in-module # pylint: disable=no-name-in-module
try: try:
from unittest import mock from unittest import mock
except ImportError: except ImportError:
...@@ -27,7 +27,7 @@ from six import u ...@@ -27,7 +27,7 @@ from six import u
import os import os
from topydo.commands.EditCommand import EditCommand from topydo.commands.EditCommand import EditCommand
from test.CommandTest import CommandTest, utf8 from test.CommandTest import CommandTest
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from topydo.lib.Config import config from topydo.lib.Config import config
...@@ -44,7 +44,6 @@ class EditCommandTest(CommandTest): ...@@ -44,7 +44,6 @@ class EditCommandTest(CommandTest):
self.todolist = TodoList(todos) self.todolist = TodoList(todos)
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
def test_edit1(self, mock_open_in_editor): def test_edit1(self, mock_open_in_editor):
""" Preserve dependencies after editing. """ """ Preserve dependencies after editing. """
...@@ -55,7 +54,7 @@ class EditCommandTest(CommandTest): ...@@ -55,7 +54,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), utf8(u("Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1"))) self.assertEqual(self.todolist.print_todos(), u("Bar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a\nFoo id:1"))
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp') @mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
...@@ -69,7 +68,7 @@ class EditCommandTest(CommandTest): ...@@ -69,7 +68,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat"))) self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nBaz @test\nFo\u00f3B\u0105\u017a\nLazy Cat"))
def test_edit3(self): def test_edit3(self):
""" Throw an error after invalid todo number given as argument. """ """ Throw an error after invalid todo number given as argument. """
...@@ -99,7 +98,7 @@ class EditCommandTest(CommandTest): ...@@ -99,7 +98,7 @@ class EditCommandTest(CommandTest):
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
self.assertEqual(self.errors, "Number of edited todos is not equal to number of supplied todo IDs.\n") self.assertEqual(self.errors, "Number of edited todos is not equal to number of supplied todo IDs.\n")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a"))) self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nBar p:1 @test\nBaz @test\nFo\u00f3B\u0105\u017a"))
def test_edit6(self): def test_edit6(self):
""" Throw an error with invalid argument containing special characters. """ """ Throw an error with invalid argument containing special characters. """
...@@ -121,7 +120,7 @@ class EditCommandTest(CommandTest): ...@@ -121,7 +120,7 @@ class EditCommandTest(CommandTest):
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat"))) self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nBar p:1 @test\nBaz @test\nLazy Cat"))
@mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp') @mock.patch('topydo.commands.EditCommand.EditCommand._todos_from_temp')
@mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor') @mock.patch('topydo.commands.EditCommand.EditCommand._open_in_editor')
...@@ -133,12 +132,12 @@ class EditCommandTest(CommandTest): ...@@ -133,12 +132,12 @@ class EditCommandTest(CommandTest):
command = EditCommand(["-e", "@test"], self.todolist, self.out, self.error, None) command = EditCommand(["-e", "@test"], self.todolist, self.out, self.error, None)
command.execute() command.execute()
expected = utf8(u("| 3| Lazy Cat\n| 4| Lazy Dog\n")) expected = u("| 3| Lazy Cat\n| 4| Lazy Dog\n")
self.assertTrue(self.todolist.is_dirty()) self.assertTrue(self.todolist.is_dirty())
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(self.output, expected) self.assertEqual(self.output, expected)
self.assertEqual(str(self.todolist), utf8(u("Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog"))) self.assertEqual(self.todolist.print_todos(), u("Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog"))
@mock.patch('topydo.commands.EditCommand.call') @mock.patch('topydo.commands.EditCommand.call')
def test_edit_archive(self, mock_call): def test_edit_archive(self, mock_call):
......
...@@ -330,7 +330,7 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -330,7 +330,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo1) self.assertEqual(result[0].source(), self.todo1)
def test_filter2(self): def test_filter2(self):
otf = Filter.OrdinalTagFilter('due:=today') otf = Filter.OrdinalTagFilter('due:=today')
...@@ -338,7 +338,7 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -338,7 +338,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo1) self.assertEqual(result[0].source(), self.todo1)
def test_filter3(self): def test_filter3(self):
otf = Filter.OrdinalTagFilter('due:>today') otf = Filter.OrdinalTagFilter('due:>today')
...@@ -346,7 +346,7 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -346,7 +346,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo2) self.assertEqual(result[0].source(), self.todo2)
def test_filter4(self): def test_filter4(self):
otf = Filter.OrdinalTagFilter('due:<1w') otf = Filter.OrdinalTagFilter('due:<1w')
...@@ -354,8 +354,8 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -354,8 +354,8 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 2) self.assertEqual(len(result), 2)
self.assertEqual(str(result[0]), self.todo1) self.assertEqual(result[0].source(), self.todo1)
self.assertEqual(str(result[1]), self.todo2) self.assertEqual(result[1].source(), self.todo2)
def test_filter5(self): def test_filter5(self):
otf = Filter.OrdinalTagFilter('due:!today') otf = Filter.OrdinalTagFilter('due:!today')
...@@ -363,7 +363,7 @@ class OrdinalTagFilterTest(TopydoTest): ...@@ -363,7 +363,7 @@ class OrdinalTagFilterTest(TopydoTest):
result = otf.filter(self.todos) result = otf.filter(self.todos)
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(str(result[0]), self.todo2) self.assertEqual(result[0].source(), self.todo2)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
......
...@@ -22,7 +22,7 @@ import unittest ...@@ -22,7 +22,7 @@ import unittest
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.commands.ListCommand import ListCommand from topydo.commands.ListCommand import ListCommand
from test.CommandTest import CommandTest, utf8 from test.CommandTest import CommandTest
from test.TestFacilities import load_file_to_todolist from test.TestFacilities import load_file_to_todolist
class ListCommandTest(CommandTest): class ListCommandTest(CommandTest):
...@@ -219,7 +219,7 @@ class ListCommandUnicodeTest(CommandTest): ...@@ -219,7 +219,7 @@ class ListCommandUnicodeTest(CommandTest):
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
expected = utf8(u("| 1| (C) And some sp\u00e9cial tag:\u25c4\n")) expected = u("| 1| (C) And some sp\u00e9cial tag:\u25c4\n")
self.assertEqual(self.output, expected) self.assertEqual(self.output, expected)
...@@ -252,7 +252,7 @@ class ListCommandJsonTest(CommandTest): ...@@ -252,7 +252,7 @@ class ListCommandJsonTest(CommandTest):
with codecs.open('test/data/ListCommandUnicodeTest.json', 'r', encoding='utf-8') as json: with codecs.open('test/data/ListCommandUnicodeTest.json', 'r', encoding='utf-8') as json:
jsontext = json.read() jsontext = json.read()
self.assertEqual(self.output, utf8(jsontext)) self.assertEqual(self.output, jsontext)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def replace_ical_tags(p_text): def replace_ical_tags(p_text):
...@@ -308,7 +308,7 @@ class ListCommandIcalTest(CommandTest): ...@@ -308,7 +308,7 @@ class ListCommandIcalTest(CommandTest):
with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r', encoding='utf-8') as ical: with codecs.open('test/data/ListCommandUnicodeTest.ics', 'r', encoding='utf-8') as ical:
icaltext = ical.read() icaltext = ical.read()
self.assertEqual(replace_ical_tags(self.output), utf8(replace_ical_tags(icaltext))) self.assertEqual(replace_ical_tags(self.output), replace_ical_tags(icaltext))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -31,13 +31,13 @@ class SortCommandTest(CommandTest): ...@@ -31,13 +31,13 @@ class SortCommandTest(CommandTest):
command = SortCommand(["text"], self.todolist, self.out, self.error) command = SortCommand(["text"], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(str(self.todolist), "First\n(A) Foo\n2014-06-14 Last") self.assertEqual(self.todolist.print_todos(), "First\n(A) Foo\n2014-06-14 Last")
def test_sort2(self): def test_sort2(self):
command = SortCommand([], self.todolist, self.out, self.error) command = SortCommand([], self.todolist, self.out, self.error)
command.execute() command.execute()
self.assertEqual(str(self.todolist), "(A) Foo\n2014-06-14 Last\nFirst") self.assertEqual(self.todolist.print_todos(), "(A) Foo\n2014-06-14 Last\nFirst")
def test_sort3(self): def test_sort3(self):
""" Check that order does not influence the UID of a todo. """ """ Check that order does not influence the UID of a todo. """
......
...@@ -19,7 +19,7 @@ import unittest ...@@ -19,7 +19,7 @@ import unittest
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from test.TestFacilities import load_file, todolist_to_string, load_file_to_todolist from test.TestFacilities import load_file, todolist_to_string, load_file_to_todolist, print_view
from test.TopydoTest import TopydoTest from test.TopydoTest import TopydoTest
class SorterTest(TopydoTest): class SorterTest(TopydoTest):
...@@ -128,7 +128,7 @@ class SorterTest(TopydoTest): ...@@ -128,7 +128,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, []) view = todolist.view(sorter, [])
result = load_file('test/data/SorterTest10-result.txt') result = load_file('test/data/SorterTest10-result.txt')
self.assertEqual(str(view), todolist_to_string(result)) self.assertEqual(print_view(view), todolist_to_string(result))
def test_sort15(self): def test_sort15(self):
""" """
...@@ -141,7 +141,7 @@ class SorterTest(TopydoTest): ...@@ -141,7 +141,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, []) view = todolist.view(sorter, [])
result = load_file('test/data/SorterTest11-result.txt') result = load_file('test/data/SorterTest11-result.txt')
self.assertEqual(str(view), todolist_to_string(result)) self.assertEqual(print_view(view), todolist_to_string(result))
def test_sort16(self): def test_sort16(self):
""" """
...@@ -153,7 +153,7 @@ class SorterTest(TopydoTest): ...@@ -153,7 +153,7 @@ class SorterTest(TopydoTest):
view = todolist.view(sorter, []) view = todolist.view(sorter, [])
result = load_file('test/data/SorterTest12-result.txt') result = load_file('test/data/SorterTest12-result.txt')
self.assertEqual(str(view), todolist_to_string(result)) self.assertEqual(print_view(view), todolist_to_string(result))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.Todo import Todo from topydo.lib.Todo import Todo
from topydo.lib.TodoFile import TodoFile from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
...@@ -42,4 +43,8 @@ def load_file_to_todolist(p_filename): ...@@ -42,4 +43,8 @@ def load_file_to_todolist(p_filename):
def todolist_to_string(p_list): def todolist_to_string(p_list):
""" Converts a todo list to a single string. """ """ Converts a todo list to a single string. """
return '\n'.join([str(t) for t in p_list]) return '\n'.join([t.source() for t in p_list])
def print_view(p_view):
printer = PrettyPrinter()
return printer.print_list(p_view.todos)
...@@ -133,11 +133,6 @@ class TodoListTester(TopydoTest): ...@@ -133,11 +133,6 @@ class TodoListTester(TopydoTest):
self.assertRaises(InvalidTodoException, self.todolist.todo, count + 100) self.assertRaises(InvalidTodoException, self.todolist.todo, count + 100)
self.assertFalse(self.todolist.is_dirty()) self.assertFalse(self.todolist.is_dirty())
def test_string(self):
# readlines() always ends a string with \n, but join() in str(todolist)
# doesn't necessarily.
self.assertEqual(str(self.todolist) + '\n', self.text)
def test_count(self): def test_count(self):
""" Test that empty lines are not counted. """ """ Test that empty lines are not counted. """
self.assertEqual(self.todolist.count(), 5) self.assertEqual(self.todolist.count(), 5)
......
...@@ -18,7 +18,7 @@ import unittest ...@@ -18,7 +18,7 @@ import unittest
from topydo.lib import Filter from topydo.lib import Filter
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from test.TestFacilities import load_file, todolist_to_string from test.TestFacilities import load_file, todolist_to_string, print_view
from topydo.lib.TodoFile import TodoFile from topydo.lib.TodoFile import TodoFile
from topydo.lib.TodoList import TodoList from topydo.lib.TodoList import TodoList
from test.TopydoTest import TopydoTest from test.TopydoTest import TopydoTest
...@@ -34,7 +34,7 @@ class ViewTest(TopydoTest): ...@@ -34,7 +34,7 @@ class ViewTest(TopydoTest):
todofilter = Filter.GrepFilter('+Project') todofilter = Filter.GrepFilter('+Project')
view = todolist.view(sorter, [todofilter]) view = todolist.view(sorter, [todofilter])
self.assertEqual(str(view), todolist_to_string(ref)) self.assertEqual(print_view(view), todolist_to_string(ref))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
......
...@@ -166,7 +166,7 @@ class CLIApplicationBase(object): ...@@ -166,7 +166,7 @@ class CLIApplicationBase(object):
command.execute() command.execute()
if archive.is_dirty(): if archive.is_dirty():
archive_file.write(str(archive)) archive_file.write(archive.print_todos())
def _help(self, args): def _help(self, args):
if args == None: if args == None:
...@@ -205,7 +205,7 @@ class CLIApplicationBase(object): ...@@ -205,7 +205,7 @@ class CLIApplicationBase(object):
if config().keep_sorted(): if config().keep_sorted():
self._execute(SortCommand, []) self._execute(SortCommand, [])
self.todofile.write(str(self.todolist)) self.todofile.write(self.todolist.print_todos())
def run(self): def run(self):
raise NotImplementedError raise NotImplementedError
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
from topydo.lib.Command import Command, InvalidCommandArgument from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib import Filter from topydo.lib import Filter
from topydo.lib.PrettyPrinter import pretty_printer_factory
from topydo.lib.Sorter import Sorter from topydo.lib.Sorter import Sorter
from topydo.lib.TodoListBase import InvalidTodoException from topydo.lib.TodoListBase import InvalidTodoException
from topydo.lib.View import View from topydo.lib.View import View
...@@ -34,6 +35,8 @@ class DepCommand(Command): ...@@ -34,6 +35,8 @@ class DepCommand(Command):
except InvalidCommandArgument: except InvalidCommandArgument:
self.subsubcommand = None self.subsubcommand = None
self.printer = pretty_printer_factory(self.todolist)
def _handle_add(self): def _handle_add(self):
(from_todo, to_todo) = self._get_todos() (from_todo, to_todo) = self._get_todos()
...@@ -97,9 +100,8 @@ class DepCommand(Command): ...@@ -97,9 +100,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(self.printer.print_list(view.todos))
self.out(view.pretty_print())
except InvalidTodoException: except InvalidTodoException:
self.error("Invalid todo number given.") self.error("Invalid todo number given.")
except InvalidCommandArgument: except InvalidCommandArgument:
......
...@@ -18,7 +18,7 @@ import os ...@@ -18,7 +18,7 @@ import os
from subprocess import call, check_call, CalledProcessError from subprocess import call, check_call, CalledProcessError
import tempfile import tempfile
from six import text_type, u from six import u
from topydo.lib.ExpressionCommand import ExpressionCommand from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.MultiCommand import MultiCommand from topydo.lib.MultiCommand import MultiCommand
...@@ -58,7 +58,7 @@ class EditCommand(MultiCommand, ExpressionCommand): ...@@ -58,7 +58,7 @@ class EditCommand(MultiCommand, ExpressionCommand):
def _todos_to_temp(self): def _todos_to_temp(self):
f = tempfile.NamedTemporaryFile() f = tempfile.NamedTemporaryFile()
for todo in self.todos: for todo in self.todos:
f.write((text_type(todo) + "\n").encode('utf-8')) f.write((todo.source() + "\n").encode('utf-8'))
f.seek(0) f.seek(0)
return f return f
...@@ -118,7 +118,7 @@ class EditCommand(MultiCommand, ExpressionCommand): ...@@ -118,7 +118,7 @@ class EditCommand(MultiCommand, ExpressionCommand):
return call([editor, archive]) == 0 return call([editor, archive]) == 0
if self.is_expression: if self.is_expression:
self.todos = self._view().todos() self.todos = self._view().todos
else: else:
self.get_todos(self.args) self.get_todos(self.args)
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
from topydo.lib.ExpressionCommand import ExpressionCommand from topydo.lib.ExpressionCommand import ExpressionCommand
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.PrettyPrinter import pretty_printer_factory
from topydo.lib.PrettyPrinterFilter import ( from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterIndentFilter, PrettyPrinterIndentFilter,
PrettyPrinterHideTagFilter PrettyPrinterHideTagFilter
...@@ -81,10 +82,8 @@ class ListCommand(ExpressionCommand): ...@@ -81,10 +82,8 @@ class ListCommand(ExpressionCommand):
sent to the output. sent to the output.
""" """
def _print_text(): if self.printer == None:
""" # create a standard printer with some filters
Outputs a pretty-printed text format of the todo list.
"""
indent = config().list_indent() indent = config().list_indent()
hidden_tags = config().hidden_tags() hidden_tags = config().hidden_tags()
...@@ -92,14 +91,9 @@ class ListCommand(ExpressionCommand): ...@@ -92,14 +91,9 @@ class ListCommand(ExpressionCommand):
filters.append(PrettyPrinterIndentFilter(indent)) filters.append(PrettyPrinterIndentFilter(indent))
filters.append(PrettyPrinterHideTagFilter(hidden_tags)) filters.append(PrettyPrinterHideTagFilter(hidden_tags))
self.out(self._view().pretty_print(filters)) self.printer = pretty_printer_factory(self.todolist, filters)
if self.printer == None: self.out(self.printer.print_list(self._view().todos))
_print_text()
else:
# we have set a special format, simply use the printer set in
# self.printer
self.out(str(self._view()))
def execute(self): def execute(self):
if not super(ListCommand, self).execute(): if not super(ListCommand, self).execute():
......
...@@ -70,4 +70,4 @@ class ExpressionCommand(Command): ...@@ -70,4 +70,4 @@ class ExpressionCommand(Command):
sorter = Sorter(self.sort_expression) sorter = Sorter(self.sort_expression)
filters = self._filters() filters = self._filters()
return View(sorter, filters, self.todolist, self.printer) return View(sorter, filters, self.todolist)
...@@ -14,6 +14,11 @@ ...@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterColorFilter,
PrettyPrinterNumbers
)
class Printer(object): class Printer(object):
""" """
An abstract class that turns todo items into strings. An abstract class that turns todo items into strings.
...@@ -21,8 +26,8 @@ class Printer(object): ...@@ -21,8 +26,8 @@ class Printer(object):
Subclasses must at least implement the print_todo method. Subclasses must at least implement the print_todo method.
""" """
def print_todo(self, p_todo): def print_todo(self, p_todo):
""" Base implementation. Simply returns the string conversion. """ """ Base implementation."""
return str(p_todo) return p_todo.source()
def print_list(self, p_todos): def print_list(self, p_todos):
""" """
...@@ -57,9 +62,26 @@ class PrettyPrinter(Printer): ...@@ -57,9 +62,26 @@ class PrettyPrinter(Printer):
def print_todo(self, p_todo): def print_todo(self, p_todo):
""" Given a todo item, pretty print it. """ """ Given a todo item, pretty print it. """
todo_str = str(p_todo) todo_str = p_todo.source()
for ppf in self.filters: for ppf in self.filters:
todo_str = ppf.filter(todo_str, p_todo) todo_str = ppf.filter(todo_str, p_todo)
return todo_str return todo_str
def pretty_printer_factory(p_todolist, p_additional_filters=None):
""" Returns a pretty printer suitable for the ls and dep subcommands. """
p_additional_filters = p_additional_filters or []
printer = PrettyPrinter()
printer.add_filter(PrettyPrinterNumbers(p_todolist))
for ppf in p_additional_filters:
printer.add_filter(ppf)
# apply colors at the last step, the ANSI codes may confuse the
# preceding filters.
printer.add_filter(PrettyPrinterColorFilter())
return printer
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
""" Provides filters used for pretty printing. """ """ Provides filters used for pretty printing. """
import re import re
from six import u
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.Colors import Colors, NEUTRAL_COLOR from topydo.lib.Colors import Colors, NEUTRAL_COLOR
...@@ -99,7 +100,7 @@ class PrettyPrinterNumbers(PrettyPrinterFilter): ...@@ -99,7 +100,7 @@ class PrettyPrinterNumbers(PrettyPrinterFilter):
def filter(self, p_todo_str, p_todo): def filter(self, p_todo_str, p_todo):
""" Prepends the number to the todo string. """ """ Prepends the number to the todo string. """
return "|{:>3}| {}".format(self.todolist.number(p_todo), p_todo_str) return u("|{:>3}| {}").format(self.todolist.number(p_todo), p_todo_str)
class PrettyPrinterHideTagFilter(PrettyPrinterFilter): class PrettyPrinterHideTagFilter(PrettyPrinterFilter):
""" Removes all occurences of the given tags from the text. """ """ Removes all occurences of the given tags from the text. """
......
...@@ -20,12 +20,11 @@ This module contains the class that represents a single todo item. ...@@ -20,12 +20,11 @@ This module contains the class that represents a single todo item.
from datetime import date from datetime import date
import re import re
from six import python_2_unicode_compatible, u from six import u
from topydo.lib.TodoParser import parse_line from topydo.lib.TodoParser import parse_line
from topydo.lib.Utils import is_valid_priority from topydo.lib.Utils import is_valid_priority
@python_2_unicode_compatible
class TodoBase(object): class TodoBase(object):
""" """
This class represents a single todo item in a todo.txt file. It maintains This class represents a single todo item in a todo.txt file. It maintains
...@@ -227,6 +226,3 @@ class TodoBase(object): ...@@ -227,6 +226,3 @@ class TodoBase(object):
""" Returns the creation date of a todo. """ """ Returns the creation date of a todo. """
return self.fields['creationDate'] return self.fields['creationDate']
def __str__(self):
""" A printer for the todo item. """
return self.source()
...@@ -49,7 +49,7 @@ class TodoFile(object): ...@@ -49,7 +49,7 @@ class TodoFile(object):
to the file. to the file.
""" """
todofile = open(self.path, 'w') todofile = codecs.open(self.path, 'w', encoding="utf-8")
if p_todos is list: if p_todos is list:
for todo in p_todos: for todo in p_todos:
......
...@@ -20,6 +20,7 @@ A list of todo items. ...@@ -20,6 +20,7 @@ A list of todo items.
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.Graph import DirectedGraph from topydo.lib.Graph import DirectedGraph
from topydo.lib.PrettyPrinter import PrettyPrinter
from topydo.lib.TodoListBase import TodoListBase from topydo.lib.TodoListBase import TodoListBase
class TodoList(TodoListBase): class TodoList(TodoListBase):
...@@ -231,4 +232,3 @@ class TodoList(TodoListBase): ...@@ -231,4 +232,3 @@ class TodoList(TodoListBase):
for todo in self._todos: for todo in self._todos:
todo.attributes['parents'] = self.parents(todo) todo.attributes['parents'] = self.parents(todo)
...@@ -255,7 +255,10 @@ class TodoListBase(object): ...@@ -255,7 +255,10 @@ class TodoListBase(object):
self._todo_id_map[todo] = uid self._todo_id_map[todo] = uid
self._id_todo_map[uid] = todo self._id_todo_map[uid] = todo
def __str__(self): def print_todos(self):
"""
Returns a pretty-printed string (without colors) of the todo items in
this list.
"""
printer = PrettyPrinter() printer = PrettyPrinter()
return printer.print_list(self._todos) return printer.print_list(self._todos)
...@@ -16,66 +16,23 @@ ...@@ -16,66 +16,23 @@
""" A view is a list of todos, sorted and filtered. """ """ A view is a list of todos, sorted and filtered. """
from six import python_2_unicode_compatible
from topydo.lib.PrettyPrinterFilter import (
PrettyPrinterColorFilter,
PrettyPrinterNumbers
)
from topydo.lib.PrettyPrinter import PrettyPrinter
@python_2_unicode_compatible
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._sorter = p_sorter self._sorter = p_sorter
self._filters = p_filters self._filters = p_filters
self._printer = p_printer
self.update()
def update(self):
"""
Updates the view data. Should be called when the backing todo list
has changed.
"""
self._viewdata = self._sorter.sort(self._todolist.todos())
for _filter in self._filters:
self._viewdata = _filter.filter(self._viewdata)
def pretty_print(self, p_pp_filters=None):
""" Pretty prints the view. """
p_pp_filters = p_pp_filters or []
# since we're using filters, always use PrettyPrinter
printer = PrettyPrinter()
printer.add_filter(PrettyPrinterNumbers(self._todolist))
for ppf in p_pp_filters:
printer.add_filter(ppf)
# apply colors at the last step, the ANSI codes may confuse the
# preceding filters.
printer.add_filter(PrettyPrinterColorFilter())
return printer.print_list(self._viewdata)
@property
def todos(self): def todos(self):
""" Returns a sorted and filtered list of todos in this view. """ """ Returns a sorted and filtered list of todos in this view. """
return self._viewdata result = self._sorter.sort(self._todolist.todos())
for _filter in self._filters:
result = _filter.filter(result)
def __str__(self): return result
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