.__setup_params() -> ._setup_params() in hooke/plugin/*.py.
[hooke.git] / hooke / plugin / command_stack.py
index ab75ce40182cafb785a883c84e408a024c10681b..59ca5ddbe79e98782ff5d60f3d038cf2293349cf 100644 (file)
@@ -23,220 +23,274 @@ and several associated :class:`~hooke.command.Command`\s exposing
 :mod`hooke.command_stack`'s functionality.
 """
 
-from ..command import Command, Argument, Failure
-from ..plugin 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))
+import logging
+import os.path
+from Queue import Queue
+
+from ..command import Command, Argument, Success, Failure
+from ..command_stack import FileCommandStack
+from ..config import Setting
+from ..engine import CloseEngine, CommandMessage
+from . import Builtin
+
+
+# Define useful command subclasses
+
+class CommandStackCommand (Command):
+    """Subclass to avoid pushing control commands to the stack.
+    """
+    def __init__(self, *args, **kwargs):
+        super(CommandStackCommand, self).__init__(*args, **kwargs)
+        stack = [a for a in self.arguments if a.name == 'stack'][0]
+        stack.default = False
+
+    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 (msg.explicit_user_call == False
+                or 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):
+    """Commands for managing a command stack (similar to macros).
+    """
+    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), ExecuteCommand(self),
+           ]
+        self._settings = [
+            Setting(section=self.setting_section, help=self.__doc__),
+            Setting(section=self.setting_section, option='path',
+                    value=os.path.join('resources', 'command_stack'),
+                    help='Directory containing command stack files.'), # TODO: allow colon separated list, like $PATH.
+            ]
+       self.command_stack = FileCommandStack()
+        self.state = 'inactive'
+        # inactive <-> active.
+        self._valid_transitions = {
+            'inactive': ['active'],
+            'active': ['inactive'],
+            }
+
+    def default_settings(self):
+        return self._settings
+
+    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 = FileCommandStack()  # 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',
+            arguments=[
+                Argument(name='output', type='file',
+                         help="""
+File name for the output command stack.  Defaults to overwriting the
+input command stack.  If the command stack does not have an input file
+(e.g. it is the default) then the file name defaults to `default`.
+""".strip()),
+                ],
+            help=self.__doc__, plugin=plugin)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+        params = self._setup_params(hooke, params)
+        self.plugin.command_stack.save(params['output'])
+
+    def _setup_params(self, hooke, params):
+        if params['output'] == None and self.plugin.command_stack.path == None:
+            params['output'] = 'default'
+        if params['output'] != None:
+            params['output'] = os.path.join(
+                self.plugin.config['path'], params['output'])
+        return params
+
+class LoadCommand (CommandStackCommand):
+    """Load the command stack.
+
+    .. warning:: This is *not safe* with untrusted input.
+    """
+    def __init__(self, plugin):
+        super(LoadCommand, self).__init__(
+            name='load command stack',
+            arguments=[
+                Argument(name='input', type='file',
+                         help="""
+File name for the input command stack.
+""".strip()),
+                ],
+            help=self.__doc__, plugin=plugin)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+        params = self._setup_params(hooke, params)
+        self.plugin.command_stack.clear()
+        self.plugin.command_stack.load(params['input'])
+
+    def _setup_params(self, hooke, params):
+        if params['input'] == None and self.plugin.command_stack.path == None:
+            params['input'] = 'default'
+        if params['input'] != None:
+            params['input'] = os.path.join(
+                self.plugin.config['path'], params['input'])
+        return params
+
+
+class ExecuteCommand (Command):
+    """Execute a :class:`~hooke.command_stack.CommandStack`.
+    """
+    def __init__(self, plugin):
+        super(ExecuteCommand, self).__init__(
+            name='execute command stack',
+            arguments=[
+                Argument(name='commands', type='command stack',
+                         help="""
+Command stack to apply to each curve.  Defaults to the plugin's
+current stack.
+""".strip()),
+                ],
+            help=self.__doc__, plugin=plugin)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+        params = self._setup_params(hooke=hooke, params=params)
+        if len(params['commands']) == 0:
+            return
+        params['commands'].execute(hooke=hooke, stack=params['stack'])
+
+    def _setup_params(self, hooke, params):
+        if params['commands'] == None:
+            params['commands'] = self.plugin.command_stack
+        return params