1 # Copyright (C) 2008-2012 Alberto Gomez-Casado <a.gomezcasado@tnw.utwente.nl>
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 os.path.expanduser(self.plugin.config['path']),
243 class LoadCommand (CommandStackCommand):
244 """Load the command stack.
246 .. warning:: This is *not safe* with untrusted input.
248 def __init__(self, plugin):
249 super(LoadCommand, self).__init__(
250 name='load command stack',
252 Argument(name='input', type='file',
254 File name for the input command stack.
257 help=self.__doc__, plugin=plugin)
259 def _run(self, hooke, inqueue, outqueue, params):
260 params = self._setup_params(hooke, params)
261 self.plugin.command_stack.clear()
262 self.plugin.command_stack.load(params['input'])
264 def _setup_params(self, hooke, params):
265 if params['input'] == None and self.plugin.command_stack.path == None:
266 params['input'] = 'default'
267 if params['input'] != None:
268 params['input'] = os.path.join(
269 os.path.expanduser(self.plugin.config['path']),
274 class ExecuteCommand (Command):
275 """Execute a :class:`~hooke.command_stack.CommandStack`.
277 def __init__(self, plugin):
278 super(ExecuteCommand, self).__init__(
279 name='execute command stack',
281 Argument(name='commands', type='command stack',
283 Command stack to apply to each curve. Defaults to the plugin's
287 help=self.__doc__, plugin=plugin)
289 def _run(self, hooke, inqueue, outqueue, params):
290 params = self._setup_params(hooke=hooke, params=params)
291 if len(params['commands']) == 0:
293 params['commands'].execute(hooke=hooke, stack=params['stack'])
295 def _setup_params(self, hooke, params):
296 if params['commands'] == None:
297 params['commands'] = self.plugin.command_stack