Add successful Curve.command_stack maintenance.
authorW. Trevor King <wking@drexel.edu>
Thu, 12 Aug 2010 17:15:29 +0000 (13:15 -0400)
committerW. Trevor King <wking@drexel.edu>
Thu, 12 Aug 2010 17:15:29 +0000 (13:15 -0400)
Now commands successfully track CurveCommands applied to them, and run
them again when they're reloaded.  Still to do:
* save/load .command_stacks with the playlist
* hooke.plugin.command_stack

hooke/curve.py
hooke/engine.py
hooke/playlist.py
hooke/plugin/curve.py
hooke/plugin/flatfilt.py
hooke/plugin/playlist.py
hooke/plugin/vclamp.py

index 34fb34e..ed76c8a 100644 (file)
@@ -20,7 +20,9 @@
 storing force curves.
 """
 
+import logging
 import os.path
+
 import numpy
 
 from .command_stack import CommandStack
@@ -172,6 +174,15 @@ class Curve (object):
         self.command_stack = CommandStack()
         self._hooke = None  # Hooke instance for Curve.load()
 
+    def __str__(self):
+        return str(self.__unicode__())
+
+    def __unicode__(self):
+        return u'<%s %s>' % (self.__class__.__name__, self.name)
+
+    def __repr__(self):
+        return self.__str__()
+
     def set_hooke(self, hooke=None):
         if hooke != None:
             self._hooke = hooke
@@ -201,15 +212,22 @@ class Curve (object):
         :meth:`hooke.command_stack.CommandStack.execute`.
         """
         self.set_hooke(hooke)
+        log = logging.getLogger('hooke')
+        log.debug('loading curve %s with driver %s' % (self.name, self.driver))
         data,info = self.driver.read(self.path, self.info)
         self.data = data
         for key,value in info.items():
             self.info[key] = value
-        if self.hooke != None:
-            # TODO: set 'curve' argument explicitly for CurveCommands
-            self.command_stack.execute(self.hooke)
+        if self._hooke != None:
+            self.command_stack.execute(self._hooke)
+        elif len(self.command_stack) > 0:
+            log.warn(
+                'could not execute command stack for %s without Hooke instance'
+                % self.name)
 
     def unload(self):
         """Release memory intensive :attr:`.data`.
         """
+        log = logging.getLogger('hooke')
+        log.debug('unloading curve %s' % self.name)
         self.data = None
index e5fd184..75f74fe 100644 (file)
@@ -21,6 +21,7 @@
 """
 
 import logging
+from Queue import Queue, Empty
 
 from .command import NullQueue
 
@@ -88,7 +89,15 @@ class CommandEngine (object):
         communication queues, so make sure they will not need user
         interaction.
         """
+        log = logging.getLogger('hooke')
         log.debug('engine running internal %s with %s'
                   % (command, arguments))
         cmd = hooke.command_by_name[command]
-        cmd.run(hooke, NullQueue(), NullQueue(), arguments)
+        outqueue = Queue()
+        cmd.run(hooke, NullQueue(), outqueue, **arguments)
+        while True:
+            try:
+                msg = outqueue.get(block=False)
+            except Empty:
+                break
+            log.debug('engine message from %s (%s): %s' % (command, type(msg), msg))
index d3ca4c6..9eb7e8b 100644 (file)
@@ -119,9 +119,10 @@ class Playlist (NoteIndexList):
         self._loaded = [] # List of loaded curves, see :meth:`._setup_item`.
         self._max_loaded = 100 # curves to hold in memory simultaneously.
 
-    def append_curve_by_path(self, path, info=None, identify=True):
+    def append_curve_by_path(self, path, info=None, identify=True, hooke=None):
         path = os.path.normpath(path)
         c = curve.Curve(path, info=info)
+        c.set_hooke(hooke)
         if identify == True:
             c.identify(self.drivers)
         self.append(c)
@@ -338,14 +339,16 @@ class FilePlaylist (Playlist):
         doc = xml.dom.minidom.parseString(string)
         self._from_xml_doc(doc, identify=identify)
 
-    def load(self, path=None, identify=True):
+    def load(self, path=None, identify=True, hooke=None):
         """Load a playlist from a file.
         """
         self.set_path(path)
         doc = xml.dom.minidom.parse(self.path)
         self._from_xml_doc(doc, identify=identify)
         self._digest = self.digest()
-
+        for curve in self:
+            curve.set_hooke(hooke)
+        
     def save(self, path=None):
         """Saves the playlist in a XML file.
         """
index f0d847c..75f28ee 100644 (file)
@@ -30,6 +30,7 @@ import numpy
 
 from ..command import Command, Argument, Failure
 from ..curve import Data
+from ..engine import CommandMessage
 from ..util.calculus import derivative
 from ..util.fft import unitary_avg_power_spectrum
 from ..util.si import ppSI, join_data_label, split_data_label
@@ -85,6 +86,15 @@ class CurveCommand (Command):
         super(CurveCommand, self).__init__(**kwargs)
 
     def _curve(self, hooke, params):
+        """Get the selected curve.
+
+        Notes
+        -----
+        `hooke` is intended to attach the selected curve to the local
+        playlist, and the returned curve should not be effected by the
+        state of `hooke`.  This is important for reliable
+        :class:`~hooke.command_stack.CommandStack`\s.
+        """
         # HACK? rely on params['curve'] being bound to the local hooke
         # playlist (i.e. not a copy, as you would get by passing a
         # curve through the queue).  Ugh.  Stupid queues.  As an
@@ -92,6 +102,36 @@ class CurveCommand (Command):
         # queue...
         return params['curve']
 
+    def _add_to_command_stack(self, params):
+        """Store the command name and current `params` values in the
+        curve's `.command_stack`.
+
+        If this would duplicate the command currently on top of the
+        stack, no action is taken.  Call early on, or watch out for
+        repeated param processing.
+
+        Recommended practice is to *not* lock in argument values that
+        are loaded from the plugin's :attr:`.config`.
+
+        Notes
+        -----
+        Perhaps we should subclass :meth:`_run` and use :func:`super`,
+        or embed this in :meth:`run` to avoid subclasses calling this
+        method explicitly, with all the tedium and brittality that
+        implies.  On the other hand, the current implemtnation allows
+        CurveCommands that don't effect the curve itself
+        (e.g. :class:`GetCommand`) to avoid adding themselves to the
+        stack entirely.
+        """
+        curve = self._curve(hooke=None, params=params)
+        if (len(curve.command_stack) > 0
+            and curve.command_stack[-1].command == self.name
+            and curve.command_stack[-1].arguments == params):
+            pass  # no need to place duplicate calls on the stack.
+        else:
+            curve.command_stack.append(CommandMessage(
+                    self.name, params))
+
 
 class BlockCommand (CurveCommand):
     """A :class:`CurveCommand` operating on a :class:`~hooke.curve.Data` block.
@@ -399,6 +439,7 @@ Name of the new column for storing the difference (without units, defaults to
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
         params = self.__setup_params(hooke=hooke, params=params)
         data_A = self._get_column(hooke=hooke, params=params,
                                   block_name='block A',
@@ -473,6 +514,7 @@ central differencing.
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
         params = self.__setup_params(hooke=hooke, params=params)
         x_data = self._get_column(hooke=hooke, params=params,
                                   column_name='x column')
@@ -537,6 +579,7 @@ Otherwise, the chunks are end-to-end, and not overlapping.
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
         params = self.__setup_params(hooke=hooke, params=params)
         data = self._get_column(hooke=hooke, params=params)
         bounds = params['bounds']
index 08c5fa5..1daeca2 100644 (file)
@@ -144,6 +144,7 @@ dictionary.
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
         params = self.__setup_params(hooke=hooke, params=params)
         block = self._block(hooke=hooke, params=params)
         dist_data = self._get_column(hooke=hooke, params=params,
index a924701..1ab04fd 100644 (file)
@@ -202,7 +202,7 @@ Drivers for loading curves.
 
     def _run(self, hooke, inqueue, outqueue, params):
         p = FilePlaylist(drivers=params['drivers'], path=params['input'])
-        p.load()
+        p.load(hooke=hooke)
         hooke.playlists.append(p)
        outqueue.put(p)
 
@@ -226,8 +226,8 @@ Additional information for the input :class:`hooke.curve.Curve`.
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
-        params['playlist'].append_curve_by_path(params['input'],
-                                                params['info'])
+        params['playlist'].append_curve_by_path(
+            params['input'], params['info'], hooke=hooke)
 
 class AddGlobCommand (Command):
     """Add curves to a playlist with file globbing.
@@ -254,7 +254,8 @@ Additional information for the input :class:`hooke.curve.Curve`.
 
     def _run(self, hooke, inqueue, outqueue, params):
         for path in sorted(glob.glob(params['input'])):
-            params['playlist'].append_curve_by_path(path, params['info'])
+            params['playlist'].append_curve_by_path(
+                path, params['info'], hooke=hooke)
 
 class RemoveCommand (Command):
     """Remove a curve from a playlist.
index 9f3f6d6..1115cde 100644 (file)
@@ -260,6 +260,7 @@ Name (without units) for storing fit parameters in the `.info` dictionary.
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
         params = self.__setup_params(hooke=hooke, params=params)
         block = self._block(hooke=hooke, params=params)
         dist_data = self._get_column(hooke=hooke, params=params,
@@ -474,6 +475,7 @@ Name of the spring constant in the `.info` dictionary.
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
         params = self.__setup_params(hooke=hooke, params=params)
         def_data = self._get_column(hooke=hooke, params=params,
                                     column_name='deflection column')
@@ -531,6 +533,7 @@ Name of the spring constant in the `.info` dictionary.
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
         params = self.__setup_params(hooke=hooke, params=params)
         def_data = self._get_column(hooke=hooke, params=params,
                                     column_name='deflection column')
@@ -607,6 +610,7 @@ Name of the flattening information in the `.info` dictionary.
             help=self.__doc__, plugin=plugin)
 
     def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
         params = self.__setup_params(hooke=hooke, params=params)
         block = self._block(hooke=hooke, params=params)
         dist_data = self._get_column(hooke=hooke, params=params,