1 # Copyright (C) 2010-2011 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 # Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """The ``command_stack`` module provides :class:`CommandStackPlugin`
20 and several associated :class:`~hooke.command.Command`\s exposing
21 :mod`hooke.command_stack`'s functionality.
26 from Queue import Queue
28 from ..command import Command, Argument, Success, Failure
29 from ..command_stack import FileCommandStack
30 from ..config import Setting
31 from ..engine import CloseEngine, CommandMessage
35 # Define useful command subclasses
37 class CommandStackCommand (Command):
38 """Subclass to avoid pushing control commands to the stack.
40 def __init__(self, *args, **kwargs):
41 super(CommandStackCommand, self).__init__(*args, **kwargs)
42 stack = [a for a in self.arguments if a.name == 'stack'][0]
45 def _set_state(self, state):
47 self.plugin.set_state(state)
49 self.plugin.log('raising error: %s' % e)
50 raise Failure('invalid state change: %s' % e.state_change)
53 class CaptureCommand (CommandStackCommand):
54 """Run a mock-engine and save the incoming commands.
58 Due to limitations in the --script and --command option
59 implementations in ./bin/hooke, capture sessions will die at the
60 end of the script and command execution before entering
61 --persist's interactive session.
63 def __init__(self, name, plugin):
64 super(CaptureCommand, self).__init__(
65 name=name, help=self.__doc__, plugin=plugin)
67 def _run(self, hooke, inqueue, outqueue, params):
68 # TODO: possibly merge code with CommandEngine.run()
70 # Fake successful completion so UI continues sending commands.
71 outqueue.put(Success())
75 if isinstance(msg, CloseEngine):
76 outqueue.put('CloseEngine')
77 inqueue.put(msg) # Put CloseEngine back for CommandEngine.
78 self._set_state('inactive')
80 assert isinstance(msg, CommandMessage), type(msg)
81 cmd = hooke.command_by_name[msg.command]
82 if (msg.explicit_user_call == False
83 or isinstance(cmd, CommandStackCommand)):
84 if isinstance(cmd, StopCaptureCommand):
85 outqueue = Queue() # Grab StopCaptureCommand's completion.
86 cmd.run(hooke, inqueue, outqueue, **msg.arguments)
87 if isinstance(cmd, StopCaptureCommand):
88 assert self.plugin.state == 'inactive', self.plugin.state
89 # Return the stolen completion as our own.
90 raise outqueue.get(block=False)
92 self.plugin.log('appending %s' % msg)
93 self.plugin.command_stack.append(msg)
94 # Fake successful completion so UI continues sending commands.
95 outqueue.put(Success())
100 class CommandStackPlugin (Builtin):
101 """Commands for managing a command stack (similar to macros).
104 super(CommandStackPlugin, self).__init__(name='command_stack')
106 StartCaptureCommand(self), StopCaptureCommand(self),
107 ReStartCaptureCommand(self),
108 PopCommand(self), GetCommand(self), GetStateCommand(self),
109 SaveCommand(self), LoadCommand(self), ExecuteCommand(self),
112 Setting(section=self.setting_section, help=self.__doc__),
113 Setting(section=self.setting_section, option='path',
114 value=os.path.join('resources', 'command_stack'),
115 help='Directory containing command stack files.'), # TODO: allow colon separated list, like $PATH.
117 self.command_stack = FileCommandStack()
118 self.state = 'inactive'
119 # inactive <-> active.
120 self._valid_transitions = {
121 'inactive': ['active'],
122 'active': ['inactive'],
125 def default_settings(self):
126 return self._settings
129 log = logging.getLogger('hooke')
130 log.debug('%s %s' % (self.name, msg))
132 def set_state(self, state):
133 state_change = '%s -> %s' % (self.state, state)
134 self.log('changing state: %s' % state_change)
135 if state not in self._valid_transitions[self.state]:
136 e = ValueError(state)
137 e.state_change = state_change
144 class StartCaptureCommand (CaptureCommand):
145 """Clear any previous stack and run the mock-engine.
147 def __init__(self, plugin):
148 super(StartCaptureCommand, self).__init__(
149 name='start command capture', plugin=plugin)
151 def _run(self, hooke, inqueue, outqueue, params):
152 self._set_state('active')
153 self.plugin.command_stack = FileCommandStack() # clear command stack
154 super(StartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
157 class ReStartCaptureCommand (CaptureCommand):
158 """Run the mock-engine.
160 def __init__(self, plugin):
161 super(ReStartCaptureCommand, self).__init__(
162 name='restart command capture', plugin=plugin)
164 def _run(self, hooke, inqueue, outqueue, params):
165 self._set_state('active')
166 super(ReStartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
169 class StopCaptureCommand (CommandStackCommand):
170 """Stop the mock-engine.
172 def __init__(self, plugin):
173 super(StopCaptureCommand, self).__init__(
174 name='stop command capture', help=self.__doc__, plugin=plugin)
176 def _run(self, hooke, inqueue, outqueue, params):
177 self._set_state('inactive')
180 class PopCommand (CommandStackCommand):
181 """Pop the top command off the stack.
183 def __init__(self, plugin):
184 super(PopCommand, self).__init__(
185 name='pop command from stack', help=self.__doc__, plugin=plugin)
187 def _run(self, hooke, inqueue, outqueue, params):
188 outqueue.put(self.plugin.command_stack.pop())
191 class GetCommand (CommandStackCommand):
192 """Return the command stack.
194 def __init__(self, plugin):
195 super(GetCommand, self).__init__(
196 name='get command stack', help=self.__doc__, plugin=plugin)
198 def _run(self, hooke, inqueue, outqueue, params):
199 outqueue.put(self.plugin.command_stack)
201 class GetStateCommand (CommandStackCommand):
202 """Return the mock-engine state.
204 def __init__(self, plugin):
205 super(GetStateCommand, self).__init__(
206 name='get command capture state', help=self.__doc__, plugin=plugin)
208 def _run(self, hooke, inqueue, outqueue, params):
209 outqueue.put(self.plugin.state)
212 class SaveCommand (CommandStackCommand):
213 """Save the command stack.
215 def __init__(self, plugin):
216 super(SaveCommand, self).__init__(
217 name='save command stack',
219 Argument(name='output', type='file',
221 File name for the output command stack. Defaults to overwriting the
222 input command stack. If the command stack does not have an input file
223 (e.g. it is the default) then the file name defaults to `default`.
226 help=self.__doc__, plugin=plugin)
228 def _run(self, hooke, inqueue, outqueue, params):
229 params = self._setup_params(hooke, params)
230 self.plugin.command_stack.save(params['output'])
232 def _setup_params(self, hooke, params):
233 if params['output'] == None and self.plugin.command_stack.path == None:
234 params['output'] = 'default'
235 if params['output'] != None:
236 params['output'] = os.path.join(
237 os.path.expanduser(self.plugin.config['path']),
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 os.path.expanduser(self.plugin.config['path']),
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