:mod`hooke.command_stack`'s functionality.
"""
-from ..command import Command, Argument, Failure
+import logging
+from Queue import Queue
+
+from ..command import Command, Argument, Success, Failure
+from ..command_stack import CommandStack
+from ..engine import CloseEngine, CommandMessage
from . import Builtin
-from .curve import CurveCommand
-
-class macroCommands(object):
-
- currentmacro=[]
- pause=0
- auxprompt=[]
- macrodir=None
-
-
- def _plug_init(self):
- self.currentmacro=[]
- self.auxprompt=self.prompt
- self.macrodir=self.config['workdir']
- if not os.path.exists(os.path.join(self.macrodir,'macros')):
- try:
- os.mkdir('macros')
- except:
- print 'Warning: cannot create macros folder.'
- print 'Probably you do not have permissions in your Hooke folder, use macro at your own risk.'
- self.macrodir=os.path.join(self.macrodir,'macros')
-
- def collect(self):
-
- print 'Enter STOP / PAUSE to go back to normal mode\nUNDO to remove last command'
- line=[]
- while not(line=='STOP' or line=='PAUSE'):
- line=raw_input('hooke (macroREC): ')
- if line=='PAUSE':
- self.pause=1
- self.prompt='hooke (macroPAUSE): '
- break
- if line=='STOP':
- self.prompt=self.auxprompt
- self.do_recordmacro('stop')
- break
- if line=='UNDO':
- self.currentmacro.pop()
- continue
- param=line.split()
-
- #FIXME check if accessing param[2] when it doesnt exist breaks something
- if param[0] =='export':
- exportline=param[0]+' __curve__ '
- if len(param)==3:
- exportline=exportline+param[2]
- self.currentmacro.append(exportline)
- self.onecmd(line)
- continue
-
- if param[0] =='txt':
- exportline=param[0]
- if len(param)==3:
- exportline=exportline+' '+param[2]
- exportline=exportline+'__curve__'
- self.currentmacro.append(exportline)
- self.onecmd(line)
- continue
-
- self.onecmd(line)
-
- self.currentmacro.append(line)
-
-
- def do_recordmacro(self, args):
- '''RECORDMACRO
- Stores input commands to create script files
- -------
- Syntax: recordmacro [start / stop]
- If a macro is currently paused start resumes recording
- '''
-
-
- if len(args)==0:
- args='start'
-
- if args=='stop':
- self.pause=0
- self.prompt=self.auxprompt
- if len(self.currentmacro) != 0:
- answer=linput.safeinput('Do you want to save this macro? ',['y'])
- if answer[0].lower() == 'y':
- self.do_savemacro('')
- else:
- print 'Macro discarded'
- self.currentmacro=[]
- else:
- print 'Macro was empty'
-
- if args=='start':
-
- if self.pause==1:
- self.pause=0
- self.collect()
- else:
- if len(self.currentmacro) != 0:
- answer=linput.safeinput('Another macro is already beign recorded\nDo you want to save it?',['y'])
- if answer[0].lower() == 'y':
- self.do_savemacro('')
- else:
- print 'Old macro discarded, you can start recording the new one'
-
- self.currentmacro=[]
- self.collect()
-
-
- def do_savemacro(self, macroname):
-
- '''SAVEMACRO
- Saves previously recorded macro into a script file for future use
- -------
- Syntax: savemacro [macroname]
- If no macroname is supplied one will be interactively asked
- '''
-
- saved_ok=0
- if self.currentmacro==None:
- print 'No macro is being recorded!'
- return 0
- if len(macroname)==0:
- macroname=linput.safeinput('Enter new macro name: ')
- if len(macroname) == 0:
- print 'Invalid name'
-
- macroname=os.path.join(self.macrodir,macroname+'.hkm')
- if os.path.exists(macroname):
- overwrite=linput.safeinput('That name is in use, overwrite?',['n'])
- if overwrite[0].lower()!='y':
- print 'Cancelled save'
- return 0
- txtfile=open(macroname,'w+')
- self.currentmacro='\n'.join(self.currentmacro)
- txtfile.write(self.currentmacro)
- txtfile.close()
- print 'Saved on '+macroname
- self.currentmacro=[]
-
- def do_execmacro (self, args):
-
- '''EXECMACRO
- Loads a macro and executes it over current curve / playlist
- -----
- Syntax: execmacro macroname [playlist] [v]
-
- macroname.hkm should be present at [hooke]/macros directory
- By default the macro will be executed over current curve
- passing 'playlist' word as second argument executes macroname
- over all curves
- By default curve(s) will be processed silently, passing 'v'
- as second/third argument will print each command that is
- executed
-
- Note that macros applied to playlists should end by export
- commands so the processed curves are not lost
- '''
- verbose=0
- cycle=0
- curve=None
-
- if len(self.currentmacro) != 0:
- print 'Warning!: you are calling a macro while recording other'
- if len(args) == 0:
- print 'You must provide a macro name'
- return 0
- args=args.split()
-
- #print 'args ' + ' '.join(args)
-
- if len(args)>1:
- if args[1] == 'playlist':
- cycle=1
- print 'Remember! macros applied over playlists should include export orders'
- if len(args)>2 and args[2] == 'v':
- verbose=1
- else:
- if args[1] == 'v':
- verbose=1
- #print cycle
- #print verbose
-
- macropath=os.path.join(self.macrodir,args[0]+'.hkm')
- if not os.path.exists(macropath):
- print 'Could not find a macro named '+macropath
- return 0
- txtfile=open(macropath)
- if cycle ==1:
- #print self.current_list
- for item in self.current_list:
- self.current=item
- self.do_plot(0)
-
- for command in txtfile:
-
- if verbose==1:
- print 'Executing command '+command
- testcmd=command.split()
- w=0
- for word in testcmd:
- if word=='__curve__':
- testcmd[w]=os.path.splitext(os.path.basename(item.path))[0]
- w=w+1
- self.onecmd(' '.join(testcmd))
- self.current.curve.close_all()
- txtfile.seek(0)
- else:
- for command in txtfile:
- testcmd=command.split()
- w=0
- for word in testcmd:
- if word=='__curve__':
- w=w+1
- testcmd[w]=os.path.splitext(os.path.basename(self.current.path))[0]+'-'+string.lstrip(os.path.splitext(os.path.basename(self.current.path))[1],'.')
- if verbose==1:
- print 'Executing command '+' '.join(testcmd)
- self.onecmd(' '.join(testcmd))
+
+
+# Define useful command subclasses
+
+class CommandStackCommand (Command):
+ """Subclass to avoid pushing control commands to the stack.
+ """
+ def _set_state(self, state):
+ try:
+ self.plugin.set_state(state)
+ except ValueError, e:
+ self.plugin.log('raising error: %s' % e)
+ raise Failure('invalid state change: %s' % e.state_change)
+
+
+class CaptureCommand (CommandStackCommand):
+ """Run a mock-engine and save the incoming commands.
+
+ Notes
+ -----
+ Due to limitations in the --script and --command option
+ implementations in ./bin/hooke, capture sessions will die at the
+ end of the script and command execution before entering
+ --persist's interactive session.
+ """
+ def __init__(self, name, plugin):
+ super(CaptureCommand, self).__init__(
+ name=name, help=self.__doc__, plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ # TODO: possibly merge code with CommandEngine.run()
+
+ # Fake successful completion so UI continues sending commands.
+ outqueue.put(Success())
+
+ while True:
+ msg = inqueue.get()
+ if isinstance(msg, CloseEngine):
+ outqueue.put('CloseEngine')
+ inqueue.put(msg) # Put CloseEngine back for CommandEngine.
+ self._set_state('inactive')
+ return
+ assert isinstance(msg, CommandMessage), type(msg)
+ cmd = hooke.command_by_name[msg.command]
+ if isinstance(cmd, CommandStackCommand):
+ if isinstance(cmd, StopCaptureCommand):
+ outqueue = Queue() # Grab StopCaptureCommand's completion.
+ cmd.run(hooke, inqueue, outqueue, **msg.arguments)
+ if isinstance(cmd, StopCaptureCommand):
+ assert self.plugin.state == 'inactive', self.plugin.state
+ # Return the stolen completion as our own.
+ raise outqueue.get(block=False)
+ else:
+ self.plugin.log('appending %s' % msg)
+ self.plugin.command_stack.append(msg)
+ # Fake successful completion so UI continues sending commands.
+ outqueue.put(Success())
+
+
+# The plugin itself
+
+class CommandStackPlugin (Builtin):
+ def __init__(self):
+ super(CommandStackPlugin, self).__init__(name='command_stack')
+ self._commands = [
+ StartCaptureCommand(self), StopCaptureCommand(self),
+ ReStartCaptureCommand(self),
+ PopCommand(self), GetCommand(self), GetStateCommand(self),
+ SaveCommand(self), LoadCommand(self),
+ ]
+ self.command_stack = CommandStack()
+ self.path = None
+ self.state = 'inactive'
+ # inactive <-> active.
+ self._valid_transitions = {
+ 'inactive': ['active'],
+ 'active': ['inactive'],
+ }
+
+ def log(self, msg):
+ log = logging.getLogger('hooke')
+ log.debug('%s %s' % (self.name, msg))
+
+ def set_state(self, state):
+ state_change = '%s -> %s' % (self.state, state)
+ self.log('changing state: %s' % state_change)
+ if state not in self._valid_transitions[self.state]:
+ e = ValueError(state)
+ e.state_change = state_change
+ raise e
+ self.state = state
+
+
+# Define commands
+
+class StartCaptureCommand (CaptureCommand):
+ """Clear any previous stack and run the mock-engine.
+ """
+ def __init__(self, plugin):
+ super(StartCaptureCommand, self).__init__(
+ name='start command capture', plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ self._set_state('active')
+ self.plugin.command_stack = CommandStack() # clear command stack
+ super(StartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
+
+
+class ReStartCaptureCommand (CaptureCommand):
+ """Run the mock-engine.
+ """
+ def __init__(self, plugin):
+ super(ReStartCaptureCommand, self).__init__(
+ name='restart command capture', plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ self._set_state('active')
+ super(ReStartCaptureCommand, self)._run(hooke, inqueue, outqueue, params)
+
+
+class StopCaptureCommand (CommandStackCommand):
+ """Stop the mock-engine.
+ """
+ def __init__(self, plugin):
+ super(StopCaptureCommand, self).__init__(
+ name='stop command capture', help=self.__doc__, plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ self._set_state('inactive')
+
+
+class PopCommand (CommandStackCommand):
+ """Pop the top command off the stack.
+ """
+ def __init__(self, plugin):
+ super(PopCommand, self).__init__(
+ name='pop command from stack', help=self.__doc__, plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ outqueue.put(self.plugin.command_stack.pop())
+
+
+class GetCommand (CommandStackCommand):
+ """Return the command stack.
+ """
+ def __init__(self, plugin):
+ super(GetCommand, self).__init__(
+ name='get command stack', help=self.__doc__, plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ outqueue.put(self.plugin.command_stack)
+
+class GetStateCommand (CommandStackCommand):
+ """Return the mock-engine state.
+ """
+ def __init__(self, plugin):
+ super(GetStateCommand, self).__init__(
+ name='get command capture state', help=self.__doc__, plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ outqueue.put(self.plugin.state)
+
+
+class SaveCommand (CommandStackCommand):
+ """Save the command stack.
+ """
+ def __init__(self, plugin):
+ super(SaveCommand, self).__init__(
+ name='save command stack', help=self.__doc__, plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ pass
+
+
+class LoadCommand (CommandStackCommand):
+ """Load the command stack.
+ """
+ def __init__(self, plugin):
+ super(LoadCommand, self).__init__(
+ name='load command stack', help=self.__doc__, plugin=plugin)
+
+ def _run(self, hooke, inqueue, outqueue, params):
+ pass
--- /dev/null
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""
+>>> from hooke.hooke import Hooke, HookeRunner
+>>> h = Hooke()
+>>> r = HookeRunner()
+
+The command stack starts off empty.
+
+>>> h = r.run_lines(h, ['get_command_stack'])
+[]
+Success
+<BLANKLINE>
+
+And inactive, so you can't stop it.
+
+>>> h = r.run_lines(h, ['get_command_capture_state'])
+inactive
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['stop_command_capture'])
+Failure
+invalid state change: inactive -> inactive
+
+Because :meth:`hooke.hooke.HookeRunner.run_lines` spawns and closes
+its own engine subprocess, we need to run the whole capture session in
+a single call. The command stack, on the other hand, will be
+preserved between calls.
+
+You can't restart recording.
+
+>>> h = r.run_lines(h, ['start_command_capture',
+... 'get_command_capture_state',
+... 'start_command_capture',
+... 'restart_command_capture']) # doctest: +REPORT_UDIFF
+Success
+<BLANKLINE>
+active
+Success
+<BLANKLINE>
+Failure
+invalid state change: active -> active
+Failure
+invalid state change: active -> active
+
+But you can stop and restart.
+
+>>> h = r.run_lines(h, ['start_command_capture',
+... 'stop_command_capture',
+... 'restart_command_capture']) # doctest: +REPORT_UDIFF
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+
+Lets add some commands to the stack.
+
+>>> h = r.run_lines(h, ['start_command_capture',
+... 'load_playlist test/data/test',
+... 'get_curve',
+... 'stop_command_capture']) # doctest: +REPORT_UDIFF
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['get_command_stack']) # doctest: +NORMALIZE_WHITESPACE
+[<CommandMessage load playlist {input: test/data/test}>,
+ <CommandMessage get curve>]
+Success
+<BLANKLINE>
+
+When capture is stopped, command execution is normal.
+
+>>> h = r.run_lines(h, ['restart_command_capture',
+... 'curve_info',
+... 'stop_command_capture',
+... 'version',
+... 'restart_command_capture',
+... 'previous_curve',
+... 'stop_command_capture']
+... ) # doctest: +ELLIPSIS, +REPORT_UDIFF
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+Hooke 1.0.0.alpha (Ninken)
+...
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+
+You can pop commands regardless of the recording state.
+
+>>> h = r.run_lines(h, ['pop_command_from_stack'])
+<CommandMessage previous curve>
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['get_command_stack']) # doctest: +NORMALIZE_WHITESPACE
+[<CommandMessage load playlist {input: test/data/test}>,
+ <CommandMessage get curve>,
+ <CommandMessage curve info>]
+Success
+<BLANKLINE>
+
+>>> h = r.run_lines(h, ['restart_command_capture',
+... 'pop_command_from_stack',
+... 'get_command_stack',
+... 'stop_command_capture']
+... ) # doctest: +NORMALIZE_WHITESPACE, +REPORT_UDIFF
+Success
+<BLANKLINE>
+<CommandMessage curve info>
+Success
+<BLANKLINE>
+[<CommandMessage load playlist {input: test/data/test}>,
+ <CommandMessage get curve>]
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+
+If you start up again (using `start` not `restart`), the stack is cleared.
+
+>>> h = r.run_lines(h, ['start_command_capture',
+... 'stop_command_capture'])
+Success
+<BLANKLINE>
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['get_command_stack'])
+[]
+Success
+<BLANKLINE>
+"""