Commit eb0b1f74 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Split TodoList, create a base class.

The subclass implements the dependency tracking of todos, which can be
quite intensive for huge todo.txt files, and is not always needed. For
instance, dependency tracking is not needed for archiving.
parent a5c5f625
......@@ -21,6 +21,7 @@ import unittest
import Todo
import TodoFile
from TodoListBase import InvalidTodoException
import TodoList
class TodoListTester(unittest.TestCase):
......@@ -94,7 +95,7 @@ class TodoListTester(unittest.TestCase):
"(C) Baz @Context1 +Project1 key:value")
self.assertEquals(self.todolist.count(), count - 1)
self.assertTrue(self.todolist.is_dirty())
self.assertRaises(TodoList.InvalidTodoException, self.todolist.number, todo)
self.assertRaises(InvalidTodoException, self.todolist.number, todo)
def test_append1(self):
todo = self.todolist.todo(3)
......@@ -125,7 +126,7 @@ class TodoListTester(unittest.TestCase):
def test_todo(self):
count = self.todolist.count()
self.assertRaises(TodoList.InvalidTodoException, self.todolist.todo, count + 100)
self.assertRaises(InvalidTodoException, self.todolist.todo, count + 100)
self.assertFalse(self.todolist.is_dirty())
def test_string(self):
......@@ -154,7 +155,7 @@ class TodoListTester(unittest.TestCase):
def test_todo_number2(self):
todo = Todo.Todo("Non-existent")
self.assertRaises(TodoList.InvalidTodoException, self.todolist.number, todo)
self.assertRaises(InvalidTodoException, self.todolist.number, todo)
def test_todo_complete(self):
todo = self.todolist.todo(1)
......@@ -183,7 +184,7 @@ class TodoListTester(unittest.TestCase):
def test_regex1(self):
""" Multiple hits should result in None. """
self.assertRaises(TodoList.InvalidTodoException, self.todolist.todo, "Project1")
self.assertRaises(InvalidTodoException, self.todolist.todo, "Project1")
def test_regex3(self):
todo = self.todolist.todo("project2")
......
......@@ -23,6 +23,7 @@ from Config import config
import Command
from PrettyPrinter import pretty_print
from RelativeDate import relative_date_to_date
from TodoListBase import InvalidTodoException
import TodoList
class AddCommand(Command.Command):
......@@ -70,7 +71,7 @@ class AddCommand(Command.Command):
self.todolist.add_dependency(dep, self.todo)
except ValueError:
continue
except TodoList.InvalidTodoException:
except InvalidTodoException:
pass
self.todo.remove_tag(p_tag, raw_value)
......
......@@ -16,7 +16,7 @@
from Command import *
from PrettyPrinter import pretty_print
import TodoList
from TodoListBase import InvalidTodoException
class AppendCommand(Command):
def __init__(self, p_args, p_todolist,
......@@ -41,7 +41,7 @@ class AppendCommand(Command):
self.error(self.usage())
except InvalidCommandArgument:
self.error(self.usage())
except TodoList.InvalidTodoException:
except InvalidTodoException:
self.error("Invalid todo number given.")
def usage(self):
......
......@@ -16,7 +16,7 @@
from Command import *
from PrettyPrinter import *
from TodoList import InvalidTodoException
from TodoListBase import InvalidTodoException
class DCommand(Command):
"""
......
......@@ -18,7 +18,7 @@ from Command import *
from Config import config
import Filter
import Sorter
import TodoList
from TodoListBase import InvalidTodoException
import View
class DepCommand(Command):
......@@ -66,7 +66,7 @@ class DepCommand(Command):
from_todo = self.todolist.todo(from_todo_nr)
to_todo = self.todolist.todo(to_todo_nr)
except (TodoList.InvalidTodoException):
except (InvalidTodoException):
self.error("Invalid todo number given.")
except InvalidCommandArgument:
self.error(self.usage())
......@@ -98,7 +98,7 @@ class DepCommand(Command):
instance_filter = Filter.InstanceFilter(todos)
view = View.View(sorter, [instance_filter], self.todolist)
self.out(view.pretty_print())
except TodoList.InvalidTodoException:
except InvalidTodoException:
self.error("Invalid todo number given.")
except InvalidCommandArgument:
self.error(self.usage())
......
......@@ -16,7 +16,7 @@
from Command import *
from PrettyPrinter import pretty_print
from TodoList import InvalidTodoException
from TodoListBase import InvalidTodoException
class DepriCommand(Command):
def __init__(self, p_args, p_todolist,
......
......@@ -20,7 +20,7 @@ from Command import *
from Config import config
from PrettyPrinter import *
from RelativeDate import relative_date_to_date
from TodoList import InvalidTodoException
from TodoListBase import InvalidTodoException
from Utils import date_string_to_date
class PostponeCommand(Command):
......
......@@ -16,7 +16,7 @@
from Command import *
from PrettyPrinter import pretty_print
from TodoList import InvalidTodoException
from TodoListBase import InvalidTodoException
from Utils import is_valid_priority
class PriorityCommand(Command):
......
......@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from Command import *
import TodoList
from TodoListBase import InvalidTodoException
from PrettyPrinter import pretty_print
class TagCommand(Command):
......@@ -49,7 +49,7 @@ class TagCommand(Command):
self.todo = self.todolist.todo(self.argument(0))
self.tag = self.argument(1)
self.current_values = self.todo.tag_values(self.tag)
except (InvalidCommandArgument, TodoList.InvalidTodoException):
except (InvalidCommandArgument, InvalidTodoException):
self.error("Invalid todo number.")
try:
......
......@@ -24,12 +24,10 @@ import Filter
import Graph
from PrettyPrinter import pretty_print_list
import Todo
import TodoListBase
import View
class InvalidTodoException(Exception):
pass
class TodoList(object):
class TodoList(TodoListBase.TodoListBase):
"""
Provides operations for a todo list, such as adding items, removing them,
etc.
......@@ -50,38 +48,6 @@ class TodoList(object):
self.add_list(p_todostrings)
self.dirty = False
def todo(self, p_identifier):
"""
The _todos list has the same order as in the backend store (usually
a todo.txt file. The user refers to the first task as number 1, so use
index 0, etc.
"""
result = None
try:
result = self._todos[int(p_identifier) - 1]
except IndexError:
raise InvalidTodoException
except (TypeError, ValueError):
result = self.todo_by_regexp(p_identifier)
return result
def todo_by_regexp(self, p_identifier):
"""
Returns the todo that is (uniquely) identified by the given regexp.
If the regexp matches more than one item, no result is returned.
"""
result = None
candidates = Filter.GrepFilter(p_identifier).filter(self._todos)
if len(candidates) == 1:
result = candidates[0]
else:
raise InvalidTodoException
return result
def todo_by_dep_id(self, p_dep_id):
"""
Returns the todo that has the id tag set to the value p_dep_id.
......@@ -115,37 +81,6 @@ class TodoList(object):
if parent:
self._depgraph.add_edge(hash(parent), hash(p_todo), child)
def add(self, p_src):
""" Given a todo string, parse it and put it to the end of the list. """
todos = self.add_list([p_src])
return todos[0] if len(todos) else None
def add_list(self, p_srcs):
todos = [Todo.Todo(src) for src in p_srcs if re.search(r'\S', src)]
self.add_todos(todos)
return todos
def add_todo(self, p_todo):
"""
Add an Todo object to the list.
Also maintains the dependency graph to track the dependencies between
tasks.
The node ids are the todo numbers.
The edge ids are the numbers denoted by id: and p: tags.
For example:
(C) Parent task id:4
(B) Child task p:4
Then there will be an edge 1 --> 2 with ID 4.
"""
self.add_todos([p_todo])
def add_todos(self, p_todos):
for todo in p_todos:
self._todos.append(todo)
......@@ -169,59 +104,6 @@ class TodoList(object):
self.dirty = True
def erase(self):
"""
Erases all todos from the list.
Not done with self.delete to prevent dependencies disappearing from the
todo items.
"""
self._todos = []
self.dirty = True
def count(self):
""" Returns the number of todos on this list. """
return len(self._todos)
def append(self, p_todo, p_string):
"""
Appends a text to the todo, specified by its number.
The todo will be parsed again, such that tags and projects in de
appended string are processed.
"""
if len(p_string) > 0:
new_text = p_todo.source() + ' ' + p_string
p_todo.set_source_text(new_text)
self.dirty = True
def projects(self):
""" Returns a set of all projects in this list. """
result = set()
for todo in self._todos:
projects = todo.projects()
result = result.union(projects)
return result
def contexts(self):
""" Returns a set of all contexts in this list. """
result = set()
for todo in self._todos:
contexts = todo.contexts()
result = result.union(contexts)
return result
def view(self, p_sorter, p_filters):
"""
Constructs a view of the todo list.
A view is a sorted and filtered todo list, where the properties are
defined by the end user. Todos is this list should not be modified,
modifications should occur through this class.
"""
return View.View(p_sorter, p_filters, self)
def add_dependency(self, p_from_todo, p_to_todo):
""" Adds a dependency from task 1 to task 2. """
def find_next_id():
......@@ -312,37 +194,3 @@ class TodoList(object):
for todo in self._todos:
todo.attributes['parents'] = self.parents(todo)
def is_dirty(self):
return self.dirty
def set_dirty(self):
self.dirty = True
def todos(self):
return self._todos
def set_todo_completed(self, p_todo):
p_todo.set_completed()
self.dirty = True
def set_priority(self, p_todo, p_priority):
if p_todo.priority() != p_priority:
p_todo.set_priority(p_priority)
self.dirty = True
def number(self, p_todo):
try:
return self._todos.index(p_todo) + 1
except ValueError:
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: "%3d %s" % (self.number(p_todo), p_todo_str)
def __str__(self):
return '\n'.join(pretty_print_list(self._todos))
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 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/>.
"""
A list of todo items.
"""
import re
import Filter
from PrettyPrinter import pretty_print_list
import Todo
import View
class InvalidTodoException(Exception):
pass
class TodoListBase(object):
"""
Provides operations for a todo list, such as adding items, removing them,
etc.
The list is usually a complete list found in the program's input (e.g. a
todo.txt file), not an arbitrary set of todo items.
"""
def __init__(self, p_todostrings):
"""
Should be given a list of strings, each element a single todo string.
The string will be parsed.
"""
self._todos = []
self.add_list(p_todostrings)
self.dirty = False
def todo(self, p_identifier):
"""
The _todos list has the same order as in the backend store (usually
a todo.txt file. The user refers to the first task as number 1, so use
index 0, etc.
"""
result = None
try:
result = self._todos[int(p_identifier) - 1]
except IndexError:
raise InvalidTodoException
except (TypeError, ValueError):
result = self.todo_by_regexp(p_identifier)
return result
def todo_by_regexp(self, p_identifier):
"""
Returns the todo that is (uniquely) identified by the given regexp.
If the regexp matches more than one item, no result is returned.
"""
result = None
candidates = Filter.GrepFilter(p_identifier).filter(self._todos)
if len(candidates) == 1:
result = candidates[0]
else:
raise InvalidTodoException
return result
def add(self, p_src):
""" Given a todo string, parse it and put it to the end of the list. """
todos = self.add_list([p_src])
return todos[0] if len(todos) else None
def add_list(self, p_srcs):
todos = [Todo.Todo(src) for src in p_srcs if re.search(r'\S', src)]
self.add_todos(todos)
return todos
def add_todo(self, p_todo):
""" Add an Todo object to the list. """
self.add_todos([p_todo])
def add_todos(self, p_todos):
for todo in p_todos:
self._todos.append(todo)
self.dirty = True
def delete(self, p_todo):
""" Deletes a todo item from the list. """
number = self.number(p_todo)
del self._todos[number - 1]
self.dirty = True
def erase(self):
""" Erases all todos from the list. """
self._todos = []
self.dirty = True
def count(self):
""" Returns the number of todos on this list. """
return len(self._todos)
def append(self, p_todo, p_string):
"""
Appends a text to the todo, specified by its number.
The todo will be parsed again, such that tags and projects in de
appended string are processed.
"""
if len(p_string) > 0:
new_text = p_todo.source() + ' ' + p_string
p_todo.set_source_text(new_text)
self.dirty = True
def projects(self):
""" Returns a set of all projects in this list. """
result = set()
for todo in self._todos:
projects = todo.projects()
result = result.union(projects)
return result
def contexts(self):
""" Returns a set of all contexts in this list. """
result = set()
for todo in self._todos:
contexts = todo.contexts()
result = result.union(contexts)
return result
def view(self, p_sorter, p_filters):
"""
Constructs a view of the todo list.
A view is a sorted and filtered todo list, where the properties are
defined by the end user. Todos is this list should not be modified,
modifications should occur through this class.
"""
return View.View(p_sorter, p_filters, self)
def is_dirty(self):
return self.dirty
def set_dirty(self):
self.dirty = True
def todos(self):
return self._todos
def set_todo_completed(self, p_todo):
p_todo.set_completed()
self.dirty = True
def set_priority(self, p_todo, p_priority):
if p_todo.priority() != p_priority:
p_todo.set_priority(p_priority)
self.dirty = True
def number(self, p_todo):
try:
return self._todos.index(p_todo) + 1
except ValueError:
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: "%3d %s" % (self.number(p_todo), p_todo_str)
def __str__(self):
return '\n'.join(pretty_print_list(self._todos))
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