Fleshed out hooke.plugin.command_stack except for save/load
[hooke.git] / hooke / plugin / command_stack.py
1 # Copyright (C) 2008-2010 Alberto Gomez-Casado
2 #                         Massimo Sandal <devicerandom@gmail.com>
3 #                         W. Trevor King <wking@drexel.edu>
4 #
5 # This file is part of Hooke.
6 #
7 # Hooke is free software: you can redistribute it and/or modify it
8 # under the terms of the GNU Lesser General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
11 #
12 # Hooke is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
15 # Public License for more details.
16 #
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with Hooke.  If not, see
19 # <http://www.gnu.org/licenses/>.
20
21 """The ``command_stack`` module provides :class:`CommandStackPlugin`
22 and several associated :class:`~hooke.command.Command`\s exposing
23 :mod`hooke.command_stack`'s functionality.
24 """
25
26 import logging
27 from Queue import Queue
28
29 from ..command import Command, Argument, Success, Failure
30 from ..command_stack import CommandStack
31 from ..engine import CloseEngine, CommandMessage
32 from . import Builtin
33
34
35 # Define useful command subclasses
36
37 class CommandStackCommand (Command):
38     """Subclass to avoid pushing control commands to the stack.
39     """
40     def _set_state(self, state):
41         try:
42             self.plugin.set_state(state)
43         except ValueError, e:
44             self.plugin.log('raising error: %s' % e)
45             raise Failure('invalid state change: %s' % e.state_change)
46
47
48 class CaptureCommand (CommandStackCommand):
49     """Run a mock-engine and save the incoming commands.
50
51     Notes
52     -----
53     Due to limitations in the --script and --command option
54     implementations in ./bin/hooke, capture sessions will die at the
55     end of the script and command execution before entering
56     --persist's interactive session.
57     """
58     def __init__(self, name, plugin):
59         super(CaptureCommand, self).__init__(
60             name=name, help=self.__doc__, plugin=plugin)
61
62     def _run(self, hooke, inqueue, outqueue, params):
63         # TODO: possibly merge code with CommandEngine.run()
64
65         # Fake successful completion so UI continues sending commands.
66         outqueue.put(Success())
67
68         while True:
69             msg = inqueue.get()
70             if isinstance(msg, CloseEngine):
71                 outqueue.put('CloseEngine')
72                 inqueue.put(msg)  # Put CloseEngine back for CommandEngine.
73                 self._set_state('inactive')
74                 return
75             assert isinstance(msg, CommandMessage), type(msg)
76             cmd = hooke.command_by_name[msg.command]
77             if isinstance(cmd, CommandStackCommand):
78                 if isinstance(cmd, StopCaptureCommand):
79                     outqueue = Queue()  # Grab StopCaptureCommand's completion.
80                 cmd.run(hooke, inqueue, outqueue, **msg.arguments)
81                 if isinstance(cmd, StopCaptureCommand):
82                     assert self.plugin.state == 'inactive', self.plugin.state
83                     # Return the stolen completion as our own.
84                     raise outqueue.get(block=False)
85             else:
86                 self.plugin.log('appending %s' % msg)
87                 self.plugin.command_stack.append(msg)
88                 # Fake successful completion so UI continues sending commands.
89                 outqueue.put(Success())
90
91
92 # The plugin itself
93
94 class CommandStackPlugin (Builtin):
95     def __init__(self):
96         super(CommandStackPlugin, self).__init__(name='command_stack')
97         self._commands = [
98             StartCaptureCommand(self), StopCaptureCommand(self),
99             ReStartCaptureCommand(self),
100             PopCommand(self), GetCommand(self), GetStateCommand(self),
101             SaveCommand(self), LoadCommand(self),
102             ]
103         self.command_stack = CommandStack()
104         self.path = None
105         self.state = 'inactive'
106         # inactive <-> active.
107         self._valid_transitions = {
108             'inactive': ['active'],
109             'active': ['inactive'],
110             }
111
112     def log(self, msg):
113         log = logging.getLogger('hooke')
114         log.debug('%s %s' % (self.name, msg))
115
116     def set_state(self, state):
117         state_change = '%s -> %s' % (self.state, state)
118         self.log('changing state: %s' % state_change)
119         if state not in self._valid_transitions[self.state]:
120             e = ValueError(state)
121             e.state_change = state_change
122             raise e
123         self.state = state
124
125
126 # Define commands
127
128 class StartCaptureCommand (CaptureCommand):
129     """Clear any previous stack and run the mock-engine.
130     """
131     def __init__(self, plugin):
132         super(StartCaptureCommand, self).__init__(
133             name='start command capture', plugin=plugin)
134
135     def _run(self, hooke, inqueue, outqueue, params):
136         self._set_state('active')
137         self.plugin.command_stack = CommandStack()  # clear command stack
138         super(StartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
139
140
141 class ReStartCaptureCommand (CaptureCommand):
142     """Run the mock-engine.
143     """
144     def __init__(self, plugin):
145         super(ReStartCaptureCommand, self).__init__(
146             name='restart command capture', plugin=plugin)
147
148     def _run(self, hooke, inqueue, outqueue, params):
149         self._set_state('active')
150         super(ReStartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
151
152
153 class StopCaptureCommand (CommandStackCommand):
154     """Stop the mock-engine.
155     """
156     def __init__(self, plugin):
157         super(StopCaptureCommand, self).__init__(
158             name='stop command capture', help=self.__doc__, plugin=plugin)
159
160     def _run(self, hooke, inqueue, outqueue, params):
161         self._set_state('inactive')
162
163
164 class PopCommand (CommandStackCommand):
165     """Pop the top command off the stack.
166     """
167     def __init__(self, plugin):
168         super(PopCommand, self).__init__(
169             name='pop command from stack', help=self.__doc__, plugin=plugin)
170
171     def _run(self, hooke, inqueue, outqueue, params):
172         outqueue.put(self.plugin.command_stack.pop())
173
174
175 class GetCommand (CommandStackCommand):
176     """Return the command stack.
177     """
178     def __init__(self, plugin):
179         super(GetCommand, self).__init__(
180             name='get command stack', help=self.__doc__, plugin=plugin)
181
182     def _run(self, hooke, inqueue, outqueue, params):
183         outqueue.put(self.plugin.command_stack)
184
185 class GetStateCommand (CommandStackCommand):
186     """Return the mock-engine state.
187     """
188     def __init__(self, plugin):
189         super(GetStateCommand, self).__init__(
190             name='get command capture state', help=self.__doc__, plugin=plugin)
191
192     def _run(self, hooke, inqueue, outqueue, params):
193         outqueue.put(self.plugin.state)
194
195
196 class SaveCommand (CommandStackCommand):
197     """Save the command stack.
198     """
199     def __init__(self, plugin):
200         super(SaveCommand, self).__init__(
201             name='save command stack', help=self.__doc__, plugin=plugin)
202
203     def _run(self, hooke, inqueue, outqueue, params):
204         pass
205
206
207 class LoadCommand (CommandStackCommand):
208     """Load the command stack.
209     """
210     def __init__(self, plugin):
211         super(LoadCommand, self).__init__(
212             name='load command stack', help=self.__doc__, plugin=plugin)
213
214     def _run(self, hooke, inqueue, outqueue, params):
215         pass