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 __init__(self, *args, **kwargs):
43 super(CommandStackCommand, self).__init__(*args, **kwargs)
44 stack = [a for a in self.arguments if a.name == 'stack'][0]
47 def _set_state(self, state):
49 self.plugin.set_state(state)
51 self.plugin.log('raising error: %s' % e)
52 raise Failure('invalid state change: %s' % e.state_change)
55 class CaptureCommand (CommandStackCommand):
56 """Run a mock-engine and save the incoming commands.
60 Due to limitations in the --script and --command option
61 implementations in ./bin/hooke, capture sessions will die at the
62 end of the script and command execution before entering
63 --persist's interactive session.
65 def __init__(self, name, plugin):
66 super(CaptureCommand, self).__init__(
67 name=name, help=self.__doc__, plugin=plugin)
69 def _run(self, hooke, inqueue, outqueue, params):
70 # TODO: possibly merge code with CommandEngine.run()
72 # Fake successful completion so UI continues sending commands.
73 outqueue.put(Success())
77 if isinstance(msg, CloseEngine):
78 outqueue.put('CloseEngine')
79 inqueue.put(msg) # Put CloseEngine back for CommandEngine.
80 self._set_state('inactive')
82 assert isinstance(msg, CommandMessage), type(msg)
83 cmd = hooke.command_by_name[msg.command]
84 if isinstance(cmd, CommandStackCommand):
85 if isinstance(cmd, StopCaptureCommand):
86 outqueue = Queue() # Grab StopCaptureCommand's completion.
87 cmd.run(hooke, inqueue, outqueue, **msg.arguments)
88 if isinstance(cmd, StopCaptureCommand):
89 assert self.plugin.state == 'inactive', self.plugin.state
90 # Return the stolen completion as our own.
91 raise outqueue.get(block=False)
93 self.plugin.log('appending %s' % msg)
94 self.plugin.command_stack.append(msg)
95 # Fake successful completion so UI continues sending commands.
96 outqueue.put(Success())
101 class CommandStackPlugin (Builtin):
102 """Commands for managing a command stack (similar to macros).
105 super(CommandStackPlugin, self).__init__(name='command_stack')
107 StartCaptureCommand(self), StopCaptureCommand(self),
108 ReStartCaptureCommand(self),
109 PopCommand(self), GetCommand(self), GetStateCommand(self),
110 SaveCommand(self), LoadCommand(self), ExecuteCommand(self),
113 Setting(section=self.setting_section, help=self.__doc__),
114 Setting(section=self.setting_section, option='path',
115 value=os.path.join('resources', 'command_stack'),
116 help='Directory containing command stack files.'), # TODO: allow colon separated list, like $PATH.
118 self.command_stack = FileCommandStack()
119 self.state = 'inactive'
120 # inactive <-> active.
121 self._valid_transitions = {
122 'inactive': ['active'],
123 'active': ['inactive'],
126 def default_settings(self):
127 return self._settings
130 log = logging.getLogger('hooke')
131 log.debug('%s %s' % (self.name, msg))
133 def set_state(self, state):
134 state_change = '%s -> %s' % (self.state, state)
135 self.log('changing state: %s' % state_change)
136 if state not in self._valid_transitions[self.state]:
137 e = ValueError(state)
138 e.state_change = state_change
145 class StartCaptureCommand (CaptureCommand):
146 """Clear any previous stack and run the mock-engine.
148 def __init__(self, plugin):
149 super(StartCaptureCommand, self).__init__(
150 name='start command capture', plugin=plugin)
152 def _run(self, hooke, inqueue, outqueue, params):
153 self._set_state('active')
154 self.plugin.command_stack = FileCommandStack() # clear command stack
155 super(StartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
158 class ReStartCaptureCommand (CaptureCommand):
159 """Run the mock-engine.
161 def __init__(self, plugin):
162 super(ReStartCaptureCommand, self).__init__(
163 name='restart command capture', plugin=plugin)
165 def _run(self, hooke, inqueue, outqueue, params):
166 self._set_state('active')
167 super(ReStartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
170 class StopCaptureCommand (CommandStackCommand):
171 """Stop the mock-engine.
173 def __init__(self, plugin):
174 super(StopCaptureCommand, self).__init__(
175 name='stop command capture', help=self.__doc__, plugin=plugin)
177 def _run(self, hooke, inqueue, outqueue, params):
178 self._set_state('inactive')
181 class PopCommand (CommandStackCommand):
182 """Pop the top command off the stack.
184 def __init__(self, plugin):
185 super(PopCommand, self).__init__(
186 name='pop command from stack', help=self.__doc__, plugin=plugin)
188 def _run(self, hooke, inqueue, outqueue, params):
189 outqueue.put(self.plugin.command_stack.pop())
192 class GetCommand (CommandStackCommand):
193 """Return the command stack.
195 def __init__(self, plugin):
196 super(GetCommand, self).__init__(
197 name='get command stack', help=self.__doc__, plugin=plugin)
199 def _run(self, hooke, inqueue, outqueue, params):
200 outqueue.put(self.plugin.command_stack)
202 class GetStateCommand (CommandStackCommand):
203 """Return the mock-engine state.
205 def __init__(self, plugin):
206 super(GetStateCommand, self).__init__(
207 name='get command capture state', help=self.__doc__, plugin=plugin)
209 def _run(self, hooke, inqueue, outqueue, params):
210 outqueue.put(self.plugin.state)
213 class SaveCommand (CommandStackCommand):
214 """Save the command stack.
216 def __init__(self, plugin):
217 super(SaveCommand, self).__init__(
218 name='save command stack',
220 Argument(name='output', type='file',
222 File name for the output command stack. Defaults to overwriting the
223 input command stack. If the command stack does not have an input file
224 (e.g. it is the default) then the file name defaults to `default`.
227 help=self.__doc__, plugin=plugin)
229 def _run(self, hooke, inqueue, outqueue, params):
230 params = self.__setup_params(hooke, params)
231 self.plugin.command_stack.save(params['output'])
233 def __setup_params(self, hooke, params):
234 if params['output'] == None and self.plugin.command_stack.path == None:
235 params['output'] = 'default'
236 if params['output'] != None:
237 params['output'] = os.path.join(
238 self.plugin.config['path'], params['output'])
241 class LoadCommand (CommandStackCommand):
242 """Load the command stack.
244 .. warning:: This is *not safe* with untrusted input.
246 def __init__(self, plugin):
247 super(LoadCommand, self).__init__(
248 name='load command stack',
250 Argument(name='input', type='file',
252 File name for the input command stack.
255 help=self.__doc__, plugin=plugin)
257 def _run(self, hooke, inqueue, outqueue, params):
258 params = self.__setup_params(hooke, params)
259 self.plugin.command_stack.clear()
260 self.plugin.command_stack.load(params['input'])
262 def __setup_params(self, hooke, params):
263 if params['input'] == None and self.plugin.command_stack.path == None:
264 params['input'] = 'default'
265 if params['input'] != None:
266 params['input'] = os.path.join(
267 self.plugin.config['path'], params['input'])
271 class ExecuteCommand (Command):
272 """Execute a :class:`~hooke.command_stack.CommandStack`.
274 def __init__(self, plugin):
275 super(ExecuteCommand, self).__init__(
276 name='execute command stack',
278 Argument(name='commands', type='command stack',
280 Command stack to apply to each curve. Defaults to the plugin's
284 help=self.__doc__, plugin=plugin)
285 stack = [a for a in self.arguments if a.name == 'stack'][0]
286 stack.default = False
288 def _run(self, hooke, inqueue, outqueue, params):
289 params = self.__setup_params(hooke=hooke, params=params)
290 if len(params['commands']) == 0:
292 params['commands'].execute(hooke=hooke, stack=params['stack'])
294 def __setup_params(self, hooke, params):
295 if params['commands'] == None:
296 params['commands'] = self.plugin.command_stack