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 (msg.explicit_user_call == False
85 or isinstance(cmd, CommandStackCommand)):
86 if isinstance(cmd, StopCaptureCommand):
87 outqueue = Queue() # Grab StopCaptureCommand's completion.
88 cmd.run(hooke, inqueue, outqueue, **msg.arguments)
89 if isinstance(cmd, StopCaptureCommand):
90 assert self.plugin.state == 'inactive', self.plugin.state
91 # Return the stolen completion as our own.
92 raise outqueue.get(block=False)
94 self.plugin.log('appending %s' % msg)
95 self.plugin.command_stack.append(msg)
96 # Fake successful completion so UI continues sending commands.
97 outqueue.put(Success())
102 class CommandStackPlugin (Builtin):
103 """Commands for managing a command stack (similar to macros).
106 super(CommandStackPlugin, self).__init__(name='command_stack')
108 StartCaptureCommand(self), StopCaptureCommand(self),
109 ReStartCaptureCommand(self),
110 PopCommand(self), GetCommand(self), GetStateCommand(self),
111 SaveCommand(self), LoadCommand(self), ExecuteCommand(self),
114 Setting(section=self.setting_section, help=self.__doc__),
115 Setting(section=self.setting_section, option='path',
116 value=os.path.join('resources', 'command_stack'),
117 help='Directory containing command stack files.'), # TODO: allow colon separated list, like $PATH.
119 self.command_stack = FileCommandStack()
120 self.state = 'inactive'
121 # inactive <-> active.
122 self._valid_transitions = {
123 'inactive': ['active'],
124 'active': ['inactive'],
127 def default_settings(self):
128 return self._settings
131 log = logging.getLogger('hooke')
132 log.debug('%s %s' % (self.name, msg))
134 def set_state(self, state):
135 state_change = '%s -> %s' % (self.state, state)
136 self.log('changing state: %s' % state_change)
137 if state not in self._valid_transitions[self.state]:
138 e = ValueError(state)
139 e.state_change = state_change
146 class StartCaptureCommand (CaptureCommand):
147 """Clear any previous stack and run the mock-engine.
149 def __init__(self, plugin):
150 super(StartCaptureCommand, self).__init__(
151 name='start command capture', plugin=plugin)
153 def _run(self, hooke, inqueue, outqueue, params):
154 self._set_state('active')
155 self.plugin.command_stack = FileCommandStack() # clear command stack
156 super(StartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
159 class ReStartCaptureCommand (CaptureCommand):
160 """Run the mock-engine.
162 def __init__(self, plugin):
163 super(ReStartCaptureCommand, self).__init__(
164 name='restart command capture', plugin=plugin)
166 def _run(self, hooke, inqueue, outqueue, params):
167 self._set_state('active')
168 super(ReStartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
171 class StopCaptureCommand (CommandStackCommand):
172 """Stop the mock-engine.
174 def __init__(self, plugin):
175 super(StopCaptureCommand, self).__init__(
176 name='stop command capture', help=self.__doc__, plugin=plugin)
178 def _run(self, hooke, inqueue, outqueue, params):
179 self._set_state('inactive')
182 class PopCommand (CommandStackCommand):
183 """Pop the top command off the stack.
185 def __init__(self, plugin):
186 super(PopCommand, self).__init__(
187 name='pop command from stack', help=self.__doc__, plugin=plugin)
189 def _run(self, hooke, inqueue, outqueue, params):
190 outqueue.put(self.plugin.command_stack.pop())
193 class GetCommand (CommandStackCommand):
194 """Return the command stack.
196 def __init__(self, plugin):
197 super(GetCommand, self).__init__(
198 name='get command stack', help=self.__doc__, plugin=plugin)
200 def _run(self, hooke, inqueue, outqueue, params):
201 outqueue.put(self.plugin.command_stack)
203 class GetStateCommand (CommandStackCommand):
204 """Return the mock-engine state.
206 def __init__(self, plugin):
207 super(GetStateCommand, self).__init__(
208 name='get command capture state', help=self.__doc__, plugin=plugin)
210 def _run(self, hooke, inqueue, outqueue, params):
211 outqueue.put(self.plugin.state)
214 class SaveCommand (CommandStackCommand):
215 """Save the command stack.
217 def __init__(self, plugin):
218 super(SaveCommand, self).__init__(
219 name='save command stack',
221 Argument(name='output', type='file',
223 File name for the output command stack. Defaults to overwriting the
224 input command stack. If the command stack does not have an input file
225 (e.g. it is the default) then the file name defaults to `default`.
228 help=self.__doc__, plugin=plugin)
230 def _run(self, hooke, inqueue, outqueue, params):
231 params = self.__setup_params(hooke, params)
232 self.plugin.command_stack.save(params['output'])
234 def __setup_params(self, hooke, params):
235 if params['output'] == None and self.plugin.command_stack.path == None:
236 params['output'] = 'default'
237 if params['output'] != None:
238 params['output'] = os.path.join(
239 self.plugin.config['path'], params['output'])
242 class LoadCommand (CommandStackCommand):
243 """Load the command stack.
245 .. warning:: This is *not safe* with untrusted input.
247 def __init__(self, plugin):
248 super(LoadCommand, self).__init__(
249 name='load command stack',
251 Argument(name='input', type='file',
253 File name for the input command stack.
256 help=self.__doc__, plugin=plugin)
258 def _run(self, hooke, inqueue, outqueue, params):
259 params = self.__setup_params(hooke, params)
260 self.plugin.command_stack.clear()
261 self.plugin.command_stack.load(params['input'])
263 def __setup_params(self, hooke, params):
264 if params['input'] == None and self.plugin.command_stack.path == None:
265 params['input'] = 'default'
266 if params['input'] != None:
267 params['input'] = os.path.join(
268 self.plugin.config['path'], params['input'])
272 class ExecuteCommand (Command):
273 """Execute a :class:`~hooke.command_stack.CommandStack`.
275 def __init__(self, plugin):
276 super(ExecuteCommand, self).__init__(
277 name='execute command stack',
279 Argument(name='commands', type='command stack',
281 Command stack to apply to each curve. Defaults to the plugin's
285 help=self.__doc__, plugin=plugin)
287 def _run(self, hooke, inqueue, outqueue, params):
288 params = self.__setup_params(hooke=hooke, params=params)
289 if len(params['commands']) == 0:
291 params['commands'].execute(hooke=hooke, stack=params['stack'])
293 def __setup_params(self, hooke, params):
294 if params['commands'] == None:
295 params['commands'] = self.plugin.command_stack