Commit 4570a210 authored by Bram Schoenmakers's avatar Bram Schoenmakers Committed by GitHub

Merge pull request #158 from mruwek/multi-4all

Allow actions on multiple ids without MultiCommand
parents 68fde1b1 89e96176
...@@ -405,6 +405,11 @@ class AddCommandTest(CommandTest): ...@@ -405,6 +405,11 @@ class AddCommandTest(CommandTest):
"| 1| x 2015-01-01 {} Already completed\n".format(self.today)) "| 1| x 2015-01-01 {} Already completed\n".format(self.today))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_add_name(self):
name = AddCommand.AddCommand.name()
self.assertEqual(name, 'add')
def test_help(self): def test_help(self):
command = AddCommand.AddCommand(["help"], self.todolist, self.out, command = AddCommand.AddCommand(["help"], self.todolist, self.out,
self.error) self.error)
......
...@@ -102,6 +102,11 @@ class AppendCommandTest(CommandTest): ...@@ -102,6 +102,11 @@ class AppendCommandTest(CommandTest):
"| 2| Qux due:%s t:%s p:1 p:2\n" % (self.today, self.today)) "| 2| Qux due:%s t:%s p:1 p:2\n" % (self.today, self.today))
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_append_name(self):
name = AppendCommand.name()
self.assertEqual(name, 'append')
def test_help(self): def test_help(self):
command = AppendCommand(["help"], self.todolist, self.out, self.error) command = AppendCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -243,6 +243,11 @@ class DeleteCommandTest(CommandTest): ...@@ -243,6 +243,11 @@ class DeleteCommandTest(CommandTest):
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_delete_name(self):
name = DeleteCommand.name()
self.assertEqual(name, 'delete')
def test_help(self): def test_help(self):
command = DeleteCommand(["help"], self.todolist, self.out, self.error) command = DeleteCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -360,6 +360,11 @@ node [ shape="none" margin="0" fontsize="9" fontname="Helvetica" ] ...@@ -360,6 +360,11 @@ node [ shape="none" margin="0" fontsize="9" fontname="Helvetica" ]
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
self.assertFalse(self.todolist.dirty) self.assertFalse(self.todolist.dirty)
def test_dep_name(self):
name = DepCommand.name()
self.assertEqual(name, 'dep')
def test_help(self): def test_help(self):
command = DepCommand(["help"], self.todolist, self.out, self.error) command = DepCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -167,6 +167,11 @@ class DepriCommandTest(CommandTest): ...@@ -167,6 +167,11 @@ class DepriCommandTest(CommandTest):
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_depri_name(self):
name = DepriCommand.name()
self.assertEqual(name, 'depri')
def test_help(self): def test_help(self):
command = DepriCommand(["help"], self.todolist, self.out, self.error) command = DepriCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -454,6 +454,11 @@ class DoCommandTest(CommandTest): ...@@ -454,6 +454,11 @@ class DoCommandTest(CommandTest):
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_do_name(self):
name = DoCommand.name()
self.assertEqual(name, 'do')
def test_help(self): def test_help(self):
command = DoCommand(["help"], self.todolist, self.out, self.error) command = DoCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -196,6 +196,11 @@ class EditCommandTest(CommandTest): ...@@ -196,6 +196,11 @@ class EditCommandTest(CommandTest):
self.assertEqual(self.todolist.print_todos(), result) self.assertEqual(self.todolist.print_todos(), result)
mock_call.assert_called_once_with([editor, todotxt]) mock_call.assert_called_once_with([editor, todotxt])
def test_edit_name(self):
name = EditCommand.name()
self.assertEqual(name, 'edit')
def test_help(self): def test_help(self):
command = EditCommand(["help"], self.todolist, self.out, self.error, command = EditCommand(["help"], self.todolist, self.out, self.error,
None) None)
......
...@@ -420,6 +420,11 @@ class ListCommandTest(CommandTest): ...@@ -420,6 +420,11 @@ class ListCommandTest(CommandTest):
self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n") self.assertEqual(self.output, "| 1| (C) 2015-11-05 Foo @Context2 Not@Context +Project1 Not+Project\n")
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_list_name(self):
name = ListCommand.name()
self.assertEqual(name, 'list')
def test_help(self): def test_help(self):
command = ListCommand(["help"], self.todolist, self.out, self.error) command = ListCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -38,6 +38,11 @@ class ListContextCommandTest(CommandTest): ...@@ -38,6 +38,11 @@ class ListContextCommandTest(CommandTest):
self.assertEqual(self.output, "Context1\nContext2\n") self.assertEqual(self.output, "Context1\nContext2\n")
self.assertFalse(self.errors) self.assertFalse(self.errors)
def test_listcontext_name(self):
name = ListContextCommand.name()
self.assertEqual(name, 'listcontext')
def test_help(self): def test_help(self):
command = ListContextCommand(["help"], None, self.out, self.error) command = ListContextCommand(["help"], None, self.out, self.error)
command.execute() command.execute()
......
...@@ -38,6 +38,11 @@ class ListProjectCommandTest(CommandTest): ...@@ -38,6 +38,11 @@ class ListProjectCommandTest(CommandTest):
self.assertEqual(self.output, "Project1\nProject2\n") self.assertEqual(self.output, "Project1\nProject2\n")
self.assertFalse(self.errors) self.assertFalse(self.errors)
def test_listproject_name(self):
name = ListProjectCommand.name()
self.assertEqual(name, 'listproject')
def test_help(self): def test_help(self):
command = ListProjectCommand(["help"], None, self.out, self.error) command = ListProjectCommand(["help"], None, self.out, self.error)
command.execute() command.execute()
......
...@@ -314,6 +314,11 @@ class PostponeCommandTest(CommandTest): ...@@ -314,6 +314,11 @@ class PostponeCommandTest(CommandTest):
self.assertEqual(self.output, result) self.assertEqual(self.output, result)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_postpone_name(self):
name = PostponeCommand.name()
self.assertEqual(name, 'postpone')
def test_help(self): def test_help(self):
command = PostponeCommand(["help"], self.todolist, self.out, command = PostponeCommand(["help"], self.todolist, self.out,
self.error) self.error)
......
...@@ -249,6 +249,11 @@ class PriorityCommandTest(CommandTest): ...@@ -249,6 +249,11 @@ class PriorityCommandTest(CommandTest):
self.assertFalse(self.output) self.assertFalse(self.output)
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_priority_name(self):
name = PriorityCommand.name()
self.assertEqual(name, 'priority')
def test_help(self): def test_help(self):
command = PriorityCommand(["help"], self.todolist, self.out, command = PriorityCommand(["help"], self.todolist, self.out,
self.error) self.error)
......
...@@ -57,7 +57,7 @@ class RevertCommandTest(CommandTest): ...@@ -57,7 +57,7 @@ class RevertCommandTest(CommandTest):
self.archive = TodoList([]) self.archive = TodoList([])
def test_revert01(self): def test_revert01(self):
backup = ChangeSet(p_call=['do 1']) backup = ChangeSet(p_label=['do 1'])
backup.add_todolist(self.todolist) backup.add_todolist(self.todolist)
backup.add_archive(self.archive) backup.add_archive(self.archive)
backup.timestamp = '1' backup.timestamp = '1'
...@@ -318,6 +318,11 @@ class RevertCommandTest(CommandTest): ...@@ -318,6 +318,11 @@ class RevertCommandTest(CommandTest):
self.assertEqual(config().backup_count(), 5) self.assertEqual(config().backup_count(), 5)
def test_revert_name(self):
name = RevertCommand.name()
self.assertEqual(name, 'revert')
def test_help(self): def test_help(self):
command = RevertCommand(["help"], self.todolist, self.out, self.error) command = RevertCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -53,6 +53,11 @@ class SortCommandTest(CommandTest): ...@@ -53,6 +53,11 @@ class SortCommandTest(CommandTest):
self.assertEqual(todo1.source(), todo2.source()) self.assertEqual(todo1.source(), todo2.source())
def test_sort_name(self):
name = SortCommand.name()
self.assertEqual(name, 'sort')
def test_help(self): def test_help(self):
command = SortCommand(["help"], self.todolist, self.out, self.error) command = SortCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -288,6 +288,11 @@ class TagCommandTest(CommandTest): ...@@ -288,6 +288,11 @@ class TagCommandTest(CommandTest):
self.assertEqual(self.output, "") self.assertEqual(self.output, "")
self.assertEqual(self.errors, command.usage() + "\n") self.assertEqual(self.errors, command.usage() + "\n")
def test_tag_name(self):
name = TagCommand.name()
self.assertEqual(name, 'tag')
def test_help(self): def test_help(self):
command = TagCommand(["help"], self.todolist, self.out, self.error) command = TagCommand(["help"], self.todolist, self.out, self.error)
command.execute() command.execute()
......
...@@ -247,6 +247,20 @@ class TodoListTester(TopydoTest): ...@@ -247,6 +247,20 @@ class TodoListTester(TopydoTest):
self.assertEqual(todo.src, results[i]) self.assertEqual(todo.src, results[i])
i += 1 i += 1
def test_ids_linenumber(self):
""" Confirms the ids method lists all todo IDs as line-numbers. """
config(p_overrides={('topydo', 'identifiers'): 'linenumber'})
results = {'1', '2', '3', '4', '5'}
self.assertEqual(results, self.todolist.ids())
def test_ids_uids(self):
""" Confirms the ids method lists all todo IDs as text uids. """
config("test/data/todolist-uid.conf")
results = {'n8m', 'mfg', 'z63', 't5c', 'wa5'}
self.assertEqual(results, self.todolist.ids())
class TodoListDependencyTester(TopydoTest): class TodoListDependencyTester(TopydoTest):
def setUp(self): def setUp(self):
......
...@@ -43,7 +43,7 @@ class RevertCommand(Command): ...@@ -43,7 +43,7 @@ class RevertCommand(Command):
archive_file.write(archive.print_todos()) archive_file.write(archive.print_todos())
last_change.delete() last_change.delete()
self.out("Successfully reverted: " + last_change.call) self.out("Successfully reverted: " + last_change.label)
except (ValueError, KeyError): except (ValueError, KeyError):
self.error('No backup was found for the current state of ' + config().todotxt()) self.error('No backup was found for the current state of ' + config().todotxt())
......
...@@ -43,11 +43,11 @@ def get_backup_path(): ...@@ -43,11 +43,11 @@ def get_backup_path():
class ChangeSet(object): class ChangeSet(object):
""" Class for operations related with backup management. """ """ Class for operations related with backup management. """
def __init__(self, p_todolist=None, p_archive=None, p_call=[]): def __init__(self, p_todolist=None, p_archive=None, p_label=[]):
self.todolist = deepcopy(p_todolist) self.todolist = deepcopy(p_todolist)
self.archive = deepcopy(p_archive) self.archive = deepcopy(p_archive)
self.timestamp = str(int(time.time())) self.timestamp = str(int(time.time()))
self.call = ' '.join(p_call) self.label = ' '.join(p_label)
try: try:
self.json_file = open(get_backup_path(), 'r+b') self.json_file = open(get_backup_path(), 'r+b')
...@@ -104,7 +104,7 @@ class ChangeSet(object): ...@@ -104,7 +104,7 @@ class ChangeSet(object):
except AttributeError: except AttributeError:
list_archive = [] list_archive = []
self.backup_dict[self.timestamp] = (list_todo, list_archive, self.call) self.backup_dict[self.timestamp] = (list_todo, list_archive, self.label)
index = self._get_index() index = self._get_index()
index.insert(0, (self.timestamp, current_hash)) index.insert(0, (self.timestamp, current_hash))
...@@ -161,7 +161,7 @@ class ChangeSet(object): ...@@ -161,7 +161,7 @@ class ChangeSet(object):
def get_backup(self, p_todolist): def get_backup(self, p_todolist):
""" """
Retrieves a backup for p_todolist from backup file and sets todolist, Retrieves a backup for p_todolist from backup file and sets todolist,
archive and call attributes to appropriate data from it. archive and label attributes to appropriate data from it.
""" """
change_hash = hash_todolist(p_todolist) change_hash = hash_todolist(p_todolist)
...@@ -172,7 +172,7 @@ class ChangeSet(object): ...@@ -172,7 +172,7 @@ class ChangeSet(object):
self.todolist = TodoList(d[0]) self.todolist = TodoList(d[0])
self.archive = TodoList(d[1]) self.archive = TodoList(d[1])
self.call = d[2] self.label = d[2]
def apply(self, p_todolist, p_archive): def apply(self, p_todolist, p_archive):
""" Applies backup on supplied p_todolist. """ """ Applies backup on supplied p_todolist. """
......
...@@ -85,6 +85,11 @@ class Command(object): ...@@ -85,6 +85,11 @@ class Command(object):
return result return result
@classmethod
def name(cls):
"""" Returns short-name of the command. """
return cls.__name__[:-7].lower() # strip 'Command'
def usage(self): def usage(self):
""" Returns a one-line synopsis for this command. """ """ Returns a one-line synopsis for this command. """
raise NotImplementedError raise NotImplementedError
......
...@@ -289,4 +289,3 @@ class TodoList(TodoListBase): ...@@ -289,4 +289,3 @@ class TodoList(TodoListBase):
self._depgraph.transitively_reduce() self._depgraph.transitively_reduce()
clean_parent_relations() clean_parent_relations()
clean_orphan_relations() clean_orphan_relations()
...@@ -280,3 +280,11 @@ class TodoListBase(object): ...@@ -280,3 +280,11 @@ class TodoListBase(object):
""" """
printer = PrettyPrinter() printer = PrettyPrinter()
return "\n".join([str(s) for s in printer.print_list(self._todos)]) return "\n".join([str(s) for s in printer.print_list(self._todos)])
def ids(self):
""" Returns set with all todo IDs. """
if config().identifiers() == 'text':
ids = self._id_todo_map.keys()
else:
ids = [str(i + 1) for i in range(self.count())]
return set(ids)
...@@ -247,12 +247,13 @@ class CLIApplicationBase(object): ...@@ -247,12 +247,13 @@ class CLIApplicationBase(object):
READ_ONLY_COMMANDS) READ_ONLY_COMMANDS)
return p_command.__module__.endswith(read_only_commands) return p_command.__module__.endswith(read_only_commands)
def _backup(self, p_command, p_args): def _backup(self, p_command, p_args=[], p_label=None):
if config().backup_count() > 0 and p_command and not self.is_read_only(p_command): if config().backup_count() > 0 and p_command and not self.is_read_only(p_command):
call = [p_command.__module__.lower()[16:-7]] + p_args # strip "topydo.commands" and "Command" call = [p_command.name()]+ p_args
from topydo.lib.ChangeSet import ChangeSet from topydo.lib.ChangeSet import ChangeSet
self.backup = ChangeSet(self.todolist, p_call=call) label = p_label if p_label else call
self.backup = ChangeSet(self.todolist, p_label=label)
def _execute(self, p_command, p_args): def _execute(self, p_command, p_args):
""" """
......
...@@ -38,6 +38,7 @@ from topydo.ui.columns.ConsoleWidget import ConsoleWidget ...@@ -38,6 +38,7 @@ from topydo.ui.columns.ConsoleWidget import ConsoleWidget
from topydo.ui.columns.KeystateWidget import KeystateWidget from topydo.ui.columns.KeystateWidget import KeystateWidget
from topydo.ui.columns.TodoWidget import TodoWidget from topydo.ui.columns.TodoWidget import TodoWidget
from topydo.ui.columns.TodoListWidget import TodoListWidget from topydo.ui.columns.TodoListWidget import TodoListWidget
from topydo.ui.columns.Transaction import Transaction
from topydo.ui.columns.Utils import PaletteItem, to_urwid_color from topydo.ui.columns.Utils import PaletteItem, to_urwid_color
from topydo.ui.columns.ViewWidget import ViewWidget from topydo.ui.columns.ViewWidget import ViewWidget
from topydo.ui.columns.ColumnLayout import columns from topydo.ui.columns.ColumnLayout import columns
...@@ -118,7 +119,7 @@ class UIApplication(CLIApplicationBase): ...@@ -118,7 +119,7 @@ class UIApplication(CLIApplicationBase):
self.todofile = TodoFileWatched(config().todotxt(), callback) self.todofile = TodoFileWatched(config().todotxt(), callback)
self.todolist = TodoList.TodoList(self.todofile.read()) self.todolist = TodoList.TodoList(self.todofile.read())
self.marked_todos = [] self.marked_todos = set()
self.columns = urwid.Columns([], dividechars=0, self.columns = urwid.Columns([], dividechars=0,
min_width=config().column_width()) min_width=config().column_width())
...@@ -260,6 +261,24 @@ class UIApplication(CLIApplicationBase): ...@@ -260,6 +261,24 @@ class UIApplication(CLIApplicationBase):
def _output(self, p_text): def _output(self, p_text):
self._print_to_console(p_text) self._print_to_console(p_text)
def _check_id_validity(self, p_ids):
"""
Checks if there are any invalid todo IDs in p_ids list.
Returns proper error message if any ID is invalid and None otherwise.
"""
errors = []
valid_ids = self.todolist.ids()
if len(p_ids) == 0:
errors.append('No todo item was selected')
else:
errors = ["Invalid todo ID: {}".format(todo_id)
for todo_id in p_ids - valid_ids]
errors = '\n'.join(errors) if errors else None
return errors
def _execute_handler(self, p_command, p_todo_id=None, p_output=None): def _execute_handler(self, p_command, p_todo_id=None, p_output=None):
""" """
Executes a command, given as a string. Executes a command, given as a string.
...@@ -269,11 +288,6 @@ class UIApplication(CLIApplicationBase): ...@@ -269,11 +288,6 @@ class UIApplication(CLIApplicationBase):
self._last_cmd = (p_command, p_output == self._output) self._last_cmd = (p_command, p_output == self._output)
if '{}' in p_command:
if self._has_marked_todos():
p_todo_id = ' '.join(self.marked_todos)
p_command = p_command.format(p_todo_id)
try: try:
p_command = shlex.split(p_command) p_command = shlex.split(p_command)
except ValueError as verr: except ValueError as verr:
...@@ -281,26 +295,37 @@ class UIApplication(CLIApplicationBase): ...@@ -281,26 +295,37 @@ class UIApplication(CLIApplicationBase):
return return
try: try:
(subcommand, args) = get_subcommand(p_command) subcommand, args = get_subcommand(p_command)
except ConfigError as cerr: except ConfigError as cerr:
self._print_to_console( self._print_to_console(
'Error: {}. Check your aliases configuration.'.format(cerr)) 'Error: {}. Check your aliases configuration.'.format(cerr))
return return
self._backup(subcommand, args) env_args = (self.todolist, p_output, self._output, self._input)
ids = None
try: if '{}' in args:
command = subcommand( if self._has_marked_todos():
args, ids = self.marked_todos
self.todolist, else:
p_output, ids = {p_todo_id} if p_todo_id else set()
self._output,
self._input,
)
if command.execute() != False: invalid_ids = self._check_id_validity(ids)
self._post_execute()
if invalid_ids:
self._print_to_console('Error: ' + invalid_ids)
return
transaction = Transaction(subcommand, env_args, ids)
transaction.prepare(args)
label = transaction.label
self._backup(subcommand, p_label=label)
try:
if transaction.execute():
self._post_execute()
else:
self._rollback()
except TypeError: except TypeError:
# TODO: show error message # TODO: show error message
pass pass
...@@ -318,6 +343,12 @@ class UIApplication(CLIApplicationBase): ...@@ -318,6 +343,12 @@ class UIApplication(CLIApplicationBase):
if dirty or self.marked_todos: if dirty or self.marked_todos:
self._reset_state() self._reset_state()
def _rollback(self):
try:
self.backup.apply(self.todolist, p_archive=None)
except AttributeError:
pass
def _repeat_last_cmd(self, p_todo_id=None): def _repeat_last_cmd(self, p_todo_id=None):
try: try:
cmd, verbosity = self._last_cmd cmd, verbosity = self._last_cmd
...@@ -330,7 +361,7 @@ class UIApplication(CLIApplicationBase): ...@@ -330,7 +361,7 @@ class UIApplication(CLIApplicationBase):
def _reset_state(self): def _reset_state(self):
for widget in TodoWidget.cache.values(): for widget in TodoWidget.cache.values():
widget.unmark() widget.unmark()
self.marked_todos = [] self.marked_todos.clear()
self._update_all_columns() self._update_all_columns()
def _blur_commandline(self): def _blur_commandline(self):
...@@ -600,7 +631,7 @@ class UIApplication(CLIApplicationBase): ...@@ -600,7 +631,7 @@ class UIApplication(CLIApplicationBase):
False otherwise. False otherwise.
""" """
if p_todo_id not in self.marked_todos: if p_todo_id not in self.marked_todos:
self.marked_todos.append(p_todo_id) self.marked_todos.add(p_todo_id)
return True return True
else: else:
self.marked_todos.remove(p_todo_id) self.marked_todos.remove(p_todo_id)
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2017 Bram Schoenmakers <bram@topydo.org>
#
# 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/>.
from topydo.lib.MultiCommand import MultiCommand
class Transaction(object):
"""
This class implements basic handling of executing any subcommand on multiple
todo items.
"""
def __init__(self, p_subcommand=None, p_env_args=(), p_todo_ids=None):
self._multi = issubclass(p_subcommand, MultiCommand)
self._cmd = lambda op: p_subcommand(op, *p_env_args)
self._todo_ids = p_todo_ids
self._operations = []
self._cmd_name = p_subcommand.name()
self.label = []
def prepare(self, p_args):
"""
Prepares list of operations to execute based on p_args, list of
todo items contained in _todo_ids attribute and _subcommand
attribute.
"""
if self._todo_ids:
id_position = p_args.index('{}')
# Not using MultiCommand abilities would make EditCommand awkward
if self._multi:
p_args[id_position:id_position + 1] = self._todo_ids
self._operations.append(p_args)
else:
for todo_id in self._todo_ids:
operation_args = p_args[:]
operation_args[id_position] = todo_id
self._operations.append(operation_args)
else:
self._operations.append(p_args)
self._create_label()
def _create_label(self):
if len(self._operations) > 1:
for operation in self._operations:
self.label.append(self._cmd_name + ' ' +
' '.join(operation) + ';')
else:
self.label.append(self._cmd_name + ' ' +
' '.join(self._operations[0]))
def execute(self):
"""
Executes each operation from _operations attribute.
"""
last_operation = len(self._operations) - 1
for i, operation in enumerate(self._operations):
command = self._cmd(operation)
if command.execute() is False:
return False
elif i == last_operation:
return True
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