Commit 4ba571ca authored by Bram Schoenmakers's avatar Bram Schoenmakers

Allow 'do' and 'del' to complete/delete multiple todos at once.

Fixes issue #1.
parent 7b4ccc57
......@@ -116,6 +116,27 @@ class DeleteCommandTest(CommandTest.CommandTest):
self.assertEquals(str(self.todolist), "Foo")
self.assertRaises(InvalidTodoException, self.todolist.todo, 'b0n')
def test_multi_del1(self):
""" Test deletion of multiple items. """
command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _no_prompt)
command.execute()
self.assertEquals(self.todolist.count(), 0)
def test_multi_del2(self):
""" Test deletion of multiple items. """
command = DeleteCommand(["1", "2"], self.todolist, self.out, self.error, _yes_prompt)
command.execute()
self.assertEquals(self.todolist.count(), 0)
def test_multi_del3(self):
""" Test deletion of multiple items. """
command = DeleteCommand(["99", "2"], self.todolist, self.out, self.error, _yes_prompt)
command.execute()
self.assertEquals(self.todolist.count(), 1)
def test_empty(self):
command = DeleteCommand([], self.todolist, self.out, self.error)
command.execute()
......
# 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
# it under the terms of the GNU General Public License as published by
......@@ -279,6 +279,52 @@ class DoCommandTest(CommandTest.CommandTest):
self.assertEquals(self.output, "| 9| {today} Strict due:2014-01-02 rec:1d\nCompleted: x {yesterday} Strict due:2014-01-01 rec:1d\n".format(today=self.today, yesterday=self.yesterday))
self.assertEquals(self.errors, "")
def test_multi_do1(self):
command = DoCommand(["1", "3"], self.todolist, self.out, self.error, _yes_prompt)
command.execute()
self.assertTrue(self.todolist.todo(1).is_completed())
self.assertTrue(self.todolist.todo(2).is_completed())
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEquals(self.output, "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {today} Bar p:1\nCompleted: x {today} Baz p:1\nCompleted: x {today} Foo id:1\n".format(today=self.today))
def test_multi_do2(self):
command = DoCommand(["1", "3"], self.todolist, self.out, self.error, _no_prompt)
command.execute()
self.assertTrue(self.todolist.todo(1).is_completed())
self.assertFalse(self.todolist.todo(2).is_completed())
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEquals(self.output, "| 2| Bar p:1\n| 3| Baz p:1\nCompleted: x {today} Foo id:1\nCompleted: x {today} Baz p:1\n".format(today=self.today))
def test_multi_do3(self):
command = DoCommand(["3", "3"], self.todolist, self.out, self.error, _no_prompt)
command.execute()
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEquals(self.output, "Completed: x {} Baz p:1\n".format(self.today))
def test_multi_do4(self):
command = DoCommand(["99", "3"], self.todolist, self.out, self.error, _no_prompt)
command.execute()
self.assertTrue(self.todolist.todo(3).is_completed())
self.assertEquals(self.output, "Completed: x {} Baz p:1\n".format(self.today))
self.assertEquals(self.errors, "Invalid todo number given.\n")
def test_multi_do5(self):
"""
When a todo item was generated by a recurring todo item, make sure
it cannot be completed in the same invocation.
"""
command = DoCommand(["4", "9"], self.todolist, self.out, self.error, _no_prompt)
command.execute()
self.assertTrue(self.todolist.todo(4).is_completed())
self.assertFalse(self.todolist.todo(9).is_completed())
def test_empty(self):
command = DoCommand([], self.todolist, self.out, self.error)
command.execute()
......
# 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
# it under the terms of the GNU General Public License as published by
......@@ -38,10 +38,12 @@ class DCommand(Command):
self.process_flags()
self.length = len(self.todolist.todos()) # to determine newly activated todos
try:
self.todo = self.todolist.todo(self.argument(0))
except (InvalidCommandArgument, InvalidTodoException):
self.todo = None
self.todos = []
for number in self.args:
try:
self.todos.append(self.todolist.todo(number))
except InvalidTodoException:
self.todos.append(None)
def get_flags(self):
""" Default implementation of getting specific flags. """
......@@ -80,8 +82,8 @@ class DCommand(Command):
""" Prefix to use when printing a todo. """
return ""
def _process_subtasks(self):
children = self._uncompleted_children(self.todo)
def _process_subtasks(self, p_todo):
children = self._uncompleted_children(p_todo)
if children:
self._print_list(children)
......@@ -112,7 +114,7 @@ class DCommand(Command):
return [todo for todo in self.todolist.todos()[:self.length]
if not self._uncompleted_children(todo) and todo.is_active()]
def condition(self):
def condition(self, p_todo):
"""
An additional condition whether execute_specific should be executed.
"""
......@@ -121,7 +123,7 @@ class DCommand(Command):
def condition_failed_text(self):
return ""
def execute_specific(self):
def execute_specific(self, _):
pass
def execute_specific_core(self, p_todo):
......@@ -137,14 +139,18 @@ class DCommand(Command):
if len(self.args) == 0:
self.error(self.usage())
elif not self.todo:
self.error("Invalid todo number given.")
elif self.todo and self.condition():
else:
old_active = self._active_todos()
self._process_subtasks()
self.execute_specific()
for todo in self.todos:
if not todo:
self.error("Invalid todo number given.")
elif todo and self.condition(todo):
self._process_subtasks(todo)
self.execute_specific(todo)
else:
self.error(self.condition_failed_text())
current_active = self._active_todos()
self._print_unlocked_todos(old_active, current_active)
else:
self.error(self.condition_failed_text())
# 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
# it under the terms of the GNU General Public License as published by
......@@ -34,12 +34,13 @@ class DeleteCommand(DCommand):
def execute_specific_core(self, p_todo):
self.todolist.delete(p_todo)
def execute_specific(self):
self.out(self.prefix() + pretty_print(self.todo))
self.execute_specific_core(self.todo)
def execute_specific(self, p_todo):
self.out(self.prefix() + pretty_print(p_todo))
self.execute_specific_core(p_todo)
def usage(self):
return """Synopsis: del [-f] <NUMBER>"""
return """Synopsis: del [-f] <NUMBER1> [<NUMBER2> ...]"""
def help(self):
return """Deletes the todo item with the given number from the list."""
return """\
Deletes the todo item(s) with the given number(s) from the list."""
......@@ -46,13 +46,13 @@ class DoCommand(DCommand):
except ValueError:
self.completion_date = date.today()
def _handle_recurrence(self):
if self.todo.has_tag('rec'):
def _handle_recurrence(self, p_todo):
if p_todo.has_tag('rec'):
if self.strict_recurrence:
new_todo = strict_advance_recurring_todo(self.todo,
new_todo = strict_advance_recurring_todo(p_todo,
self.completion_date)
else:
new_todo = advance_recurring_todo(self.todo,
new_todo = advance_recurring_todo(p_todo,
self.completion_date)
self.todolist.add_todo(new_todo)
......@@ -64,20 +64,20 @@ class DoCommand(DCommand):
def prefix(self):
return "Completed: "
def condition(self):
def condition(self, p_todo):
"""
An additional condition whether execute_specific should be executed.
"""
return not self.todo.is_completed()
return not p_todo.is_completed()
def condition_failed_text(self):
return "Todo has already been completed."
def execute_specific(self):
def execute_specific(self, p_todo):
""" Actions specific to this command. """
self._handle_recurrence()
self.execute_specific_core(self.todo)
self.out(self.prefix() + pretty_print(self.todo))
self._handle_recurrence(p_todo)
self.execute_specific_core(p_todo)
self.out(self.prefix() + pretty_print(p_todo))
def execute_specific_core(self, p_todo):
"""
......@@ -87,16 +87,16 @@ class DoCommand(DCommand):
self.todolist.set_todo_completed(p_todo, self.completion_date)
def usage(self):
return """Synopsis: do [--date] [--force] [--strict] <NUMBER>"""
return """Synopsis: do [--date] [--force] [--strict] <NUMBER1> [<NUMBER2> ...]"""
def help(self):
return """Marks the todo with given number as complete.
return """Marks the todo(s) with given number(s) as complete.
In case the todo has subitems, a question is asked whether the subitems should
be marked as completed as well. When --force is given, no interaction is
required and the subitems are not marked completed.
In case a todo has subitems, a question is asked whether the subitems should be
marked as completed as well. When --force is given, no interaction is required
and the subitems are not marked completed.
In case the completed todo is recurring, a new todo will be added to the list,
In case a completed todo is recurring, a new todo will be added to the list,
while the given todo item is marked as complete. The new date is calculated
based on the todo item's due date. If the due date is in the past, today's date
is used to calculate the new recurrence date. Using --strict prevents this,
......
# 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
# it under the terms of the GNU General Public License as published by
......@@ -90,18 +90,22 @@ class TodoList(TodoListBase):
def delete(self, p_todo):
""" Deletes a todo item from the list. """
number = self._todos.index(p_todo)
try:
number = self._todos.index(p_todo)
for child in self.children(p_todo):
self.remove_dependency(p_todo, child)
for child in self.children(p_todo):
self.remove_dependency(p_todo, child)
for parent in self.parents(p_todo):
self.remove_dependency(parent, p_todo)
for parent in self.parents(p_todo):
self.remove_dependency(parent, p_todo)
del self._todos[number]
self._update_todo_ids()
del self._todos[number]
self._update_todo_ids()
self.dirty = True
self.dirty = True
except ValueError:
# todo item couldn't be found, ignore
pass
def add_dependency(self, p_from_todo, p_to_todo):
""" Adds a dependency from task 1 to task 2. """
......
# 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
# it under the terms of the GNU General Public License as published by
......@@ -153,10 +153,14 @@ class TodoListBase(object):
def delete(self, p_todo):
""" Deletes a todo item from the list. """
number = self._todos.index(p_todo)
del self._todos[number]
self._update_todo_ids()
self.dirty = True
try:
number = self._todos.index(p_todo)
del self._todos[number]
self._update_todo_ids()
self.dirty = True
except ValueError:
# todo item couldn't be found, ignore
pass
def erase(self):
""" Erases all todos from the list. """
......
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