1 # Copyright (C) 2008-2012 Alberto Gomez-Casado <a.gomezcasado@tnw.utwente.nl>
2 # Massimo Sandal <devicerandom@gmail.com>
3 # W. Trevor King <wking@tremily.us>
5 # This file is part of Hooke.
7 # Hooke is free software: you can redistribute it and/or modify it under the
8 # terms of the GNU Lesser General Public License as published by the Free
9 # Software Foundation, either version 3 of the License, or (at your option) any
12 # Hooke is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
17 # You should have received a copy of the GNU Lesser General Public License
18 # along with Hooke. If not, see <http://www.gnu.org/licenses/>.
20 """The ``command_stack`` module provides :class:`CommandStackPlugin`
21 and several associated :class:`~hooke.command.Command`\s exposing
22 :mod`hooke.command_stack`'s functionality.
27 from Queue import Queue
29 from ..command import Command, Argument, Success, Failure
30 from ..command_stack import FileCommandStack
31 from ..config import Setting
32 from ..engine import CloseEngine, CommandMessage
36 # Define useful command subclasses
38 class CommandStackCommand (Command):
39 """Subclass to avoid pushing control commands to the stack.
41 def __init__(self, *args, **kwargs):
42 super(CommandStackCommand, self).__init__(*args, **kwargs)
43 stack = [a for a in self.arguments if a.name == 'stack'][0]
46 def _set_state(self, state):
48 self.plugin.set_state(state)
50 self.plugin.log('raising error: %s' % e)
51 raise Failure('invalid state change: %s' % e.state_change)
54 class CaptureCommand (CommandStackCommand):
55 """Run a mock-engine and save the incoming commands.
59 Due to limitations in the --script and --command option
60 implementations in ./bin/hooke, capture sessions will die at the
61 end of the script and command execution before entering
62 --persist's interactive session.
64 def __init__(self, name, plugin):
65 super(CaptureCommand, self).__init__(
66 name=name, help=self.__doc__, plugin=plugin)
68 def _run(self, hooke, inqueue, outqueue, params):
69 # TODO: possibly merge code with CommandEngine.run()
71 # Fake successful completion so UI continues sending commands.
72 outqueue.put(Success())
76 if isinstance(msg, CloseEngine):
77 outqueue.put('CloseEngine')
78 inqueue.put(msg) # Put CloseEngine back for CommandEngine.
79 self._set_state('inactive')
81 assert isinstance(msg, CommandMessage), type(msg)
82 cmd = hooke.command_by_name[msg.command]
83 if (msg.explicit_user_call == False
84 or 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 os.path.expanduser(self.plugin.config['path']),
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 os.path.expanduser(self.plugin.config['path']),
273 class ExecuteCommand (Command):
274 """Execute a :class:`~hooke.command_stack.CommandStack`.
276 def __init__(self, plugin):
277 super(ExecuteCommand, self).__init__(
278 name='execute command stack',
280 Argument(name='commands', type='command stack',
282 Command stack to apply to each curve. Defaults to the plugin's
286 help=self.__doc__, plugin=plugin)
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