Commands¶
Two modules make an application’s operations into commands: objects that
can be done, undone and redone (util.command), and that can be
typed on a command line as well as called from code
(util.commandParser).
Commands and the undo list¶
A Command has a name, and do() and undo()
methods. (Their base versions check they’re called in turn – not do twice
without an undo between – so a subclass’s should call them first.) For a
quick one, LambdaCommand takes the do and undo
as functions:
inc = LambdaCommand("increment", lambda: counter.inc(), lambda: counter.dec())
A DoList is an undo history. doList.do(command)
does the command and records it; undo() and redo() step back and
forward through the history, and doing a new command discards what could have
been redone. undoQueue() and redoQueue() describe what’s there, eg: for
a menu’s “Undo move” item.
Command contexts¶
A CommandContext is a class whose methods are
commands – callable from code, and parsable from a command line, both through
the same machinery, and so with the same undo history. Each public method
becomes a command:
its name’s words make the command’s words:
new_nodeis typed asnew node(commands sharing a first word are grouped:new node,new relation, …); a unique prefix of a word will do;its parameters without defaults are the command’s arguments; those with defaults are its
--options– except positional-only ones (before/), which are optional arguments. Their annotations give their types (aboolis a flag), and their docstring fields (:param x:) the help.
A method with a matching nested Command class –
move() and _Move – is an undoable command: calling it (or typing it)
makes a _Move with the arguments, and does it through the context’s
DoList. The method itself is never run: it’s a
signature for the command. A method without one is simply run, and isn’t
undoable – eg: a query.
from __future__ import annotations # (the parser reads the annotations as text)
from util.command import Command
from util.commandParser import CommandContext
count = [0]
class Counter(CommandContext):
def add(self, n:int, /):
"""Add *n* to the count.
:param n: How much to add.
:type n: int
"""
class _Add(Command):
def __init__(self, n:int):
super().__init__("add")
self.n = n
def do(self):
super().do(); count[0] += self.n
def undo(self):
super().undo(); count[0] -= self.n
Then, with ctx = Counter(), ctx.add(3) and the typed line
ctx.parser.parseLine("add 3") do the same thing, and undo (or
ctx.undo()) undoes either. Every context has undo,
redo, help, debug and default commands of its own.
Two dictionaries complete the picture:
the context’s decoders turn typed text into the values the methods expect, by parameter type: eg: Semantic Nets decodes an object’s name, or
#12, into the object itself;its defaults fill in missing arguments – eg: the current model – and can be set with the
defaultcommand.
A TopCommandParser is the root of a context’s
command tree (built with argparse): parser.parseLine(text) splits
a line (respecting quotes) and runs the command.
Composite commands¶
Several commands that make up one action are grouped with a
CompositeCommandMgr:
with CompositeCommandMgr(context):
context.move(a, 10, 20)
context.move(b, 30, 40)
Inside the block, the commands are queued, not done; when it ends, they’re
done together as one CompositeCommand – one
entry in the undo history, undone in reverse order. Because they’re queued, a
command in the group can’t see the effects of the ones before it.