1 # Copyright (C) 2008-2010 Alberto Gomez-Casado
2 # Massimo Sandal <devicerandom@gmail.com>
3 # W. Trevor King <wking@drexel.edu>
5 # This file is part of Hooke.
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.
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.
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/>.
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.
28 from Queue import Queue
30 from ..command import Command, Argument, Success, Failure
31 from ..command_stack import FileCommandStack
32 from ..config import Setting
33 from ..engine import CloseEngine, CommandMessage
37 # Define useful command subclasses
39 class CommandStackCommand (Command):
40 """Subclass to avoid pushing control commands to the stack.
42 def _set_state(self, state):
44 self.plugin.set_state(state)
46 self.plugin.log('raising error: %s' % e)
47 raise Failure('invalid state change: %s' % e.state_change)
50 class CaptureCommand (CommandStackCommand):
51 """Run a mock-engine and save the incoming commands.
55 Due to limitations in the --script and --command option
56 implementations in ./bin/hooke, capture sessions will die at the
57 end of the script and command execution before entering
58 --persist's interactive session.
60 def __init__(self, name, plugin):
61 super(CaptureCommand, self).__init__(
62 name=name, help=self.__doc__, plugin=plugin)
64 def _run(self, hooke, inqueue, outqueue, params):
65 # TODO: possibly merge code with CommandEngine.run()
67 # Fake successful completion so UI continues sending commands.
68 outqueue.put(Success())
72 if isinstance(msg, CloseEngine):
73 outqueue.put('CloseEngine')
74 inqueue.put(msg) # Put CloseEngine back for CommandEngine.
75 self._set_state('inactive')
77 assert isinstance(msg, CommandMessage), type(msg)
78 cmd = hooke.command_by_name[msg.command]
79 if isinstance(cmd, CommandStackCommand):
80 if isinstance(cmd, StopCaptureCommand):
81 outqueue = Queue() # Grab StopCaptureCommand's completion.
82 cmd.run(hooke, inqueue, outqueue, **msg.arguments)
83 if isinstance(cmd, StopCaptureCommand):
84 assert self.plugin.state == 'inactive', self.plugin.state
85 # Return the stolen completion as our own.
86 raise outqueue.get(block=False)
88 self.plugin.log('appending %s' % msg)
89 self.plugin.command_stack.append(msg)
90 # Fake successful completion so UI continues sending commands.
91 outqueue.put(Success())
96 class CommandStackPlugin (Builtin):
97 """Commands for managing a command stack (similar to macros).
100 super(CommandStackPlugin, self).__init__(name='command_stack')
102 StartCaptureCommand(self), StopCaptureCommand(self),
103 ReStartCaptureCommand(self),
104 PopCommand(self), GetCommand(self), GetStateCommand(self),
105 SaveCommand(self), LoadCommand(self),
108 Setting(section=self.setting_section, help=self.__doc__),
109 Setting(section=self.setting_section, option='path',
110 value=os.path.join('resources', 'command_stack'),
111 help='Directory containing command stack files.'), # TODO: allow colon separated list, like $PATH.
113 self.command_stack = FileCommandStack()
114 self.state = 'inactive'
115 # inactive <-> active.
116 self._valid_transitions = {
117 'inactive': ['active'],
118 'active': ['inactive'],
121 def default_settings(self):
122 return self._settings
125 log = logging.getLogger('hooke')
126 log.debug('%s %s' % (self.name, msg))
128 def set_state(self, state):
129 state_change = '%s -> %s' % (self.state, state)
130 self.log('changing state: %s' % state_change)
131 if state not in self._valid_transitions[self.state]:
132 e = ValueError(state)
133 e.state_change = state_change
140 class StartCaptureCommand (CaptureCommand):
141 """Clear any previous stack and run the mock-engine.
143 def __init__(self, plugin):
144 super(StartCaptureCommand, self).__init__(
145 name='start command capture', plugin=plugin)
147 def _run(self, hooke, inqueue, outqueue, params):
148 self._set_state('active')
149 self.plugin.command_stack = FileCommandStack() # clear command stack
150 super(StartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
153 class ReStartCaptureCommand (CaptureCommand):
154 """Run the mock-engine.
156 def __init__(self, plugin):
157 super(ReStartCaptureCommand, self).__init__(
158 name='restart command capture', plugin=plugin)
160 def _run(self, hooke, inqueue, outqueue, params):
161 self._set_state('active')
162 super(ReStartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
165 class StopCaptureCommand (CommandStackCommand):
166 """Stop the mock-engine.
168 def __init__(self, plugin):
169 super(StopCaptureCommand, self).__init__(
170 name='stop command capture', help=self.__doc__, plugin=plugin)
172 def _run(self, hooke, inqueue, outqueue, params):
173 self._set_state('inactive')
176 class PopCommand (CommandStackCommand):
177 """Pop the top command off the stack.
179 def __init__(self, plugin):
180 super(PopCommand, self).__init__(
181 name='pop command from stack', help=self.__doc__, plugin=plugin)
183 def _run(self, hooke, inqueue, outqueue, params):
184 outqueue.put(self.plugin.command_stack.pop())
187 class GetCommand (CommandStackCommand):
188 """Return the command stack.
190 def __init__(self, plugin):
191 super(GetCommand, self).__init__(
192 name='get command stack', help=self.__doc__, plugin=plugin)
194 def _run(self, hooke, inqueue, outqueue, params):
195 outqueue.put(self.plugin.command_stack)
197 class GetStateCommand (CommandStackCommand):
198 """Return the mock-engine state.
200 def __init__(self, plugin):
201 super(GetStateCommand, self).__init__(
202 name='get command capture state', help=self.__doc__, plugin=plugin)
204 def _run(self, hooke, inqueue, outqueue, params):
205 outqueue.put(self.plugin.state)
208 class SaveCommand (CommandStackCommand):
209 """Save the command stack.
211 def __init__(self, plugin):
212 super(SaveCommand, self).__init__(
213 name='save command stack',
215 Argument(name='output', type='file',
217 File name for the output command stack. Defaults to overwriting the
218 input command stack. If the command stack does not have an input file
219 (e.g. it is the default) then the file name defaults to `default`.
222 help=self.__doc__, plugin=plugin)
224 def _run(self, hooke, inqueue, outqueue, params):
225 params = self.__setup_params(hooke, params)
226 self.plugin.command_stack.save(params['output'])
228 def __setup_params(self, hooke, params):
229 if params['output'] == None and self.plugin.command_stack.path == None:
230 params['output'] = 'default'
231 if params['output'] != None:
232 params['output'] = os.path.join(
233 self.plugin.config['path'], params['output'])
236 class LoadCommand (CommandStackCommand):
237 """Load the command stack.
239 .. warning:: This is *not safe* with untrusted input.
241 def __init__(self, plugin):
242 super(LoadCommand, self).__init__(
243 name='load command stack',
245 Argument(name='input', type='file',
247 File name for the input command stack.
250 help=self.__doc__, plugin=plugin)
252 def _run(self, hooke, inqueue, outqueue, params):
253 params = self.__setup_params(hooke, params)
254 self.plugin.command_stack.clear()
255 self.plugin.command_stack.load(params['input'])
257 def __setup_params(self, hooke, params):
258 if params['input'] == None and self.plugin.command_stack.path == None:
259 params['input'] = 'default'
260 if params['input'] != None:
261 params['input'] = os.path.join(
262 self.plugin.config['path'], params['input'])