Convert from "print ..." to "print(...)"
[hooke.git] / hooke / playlist.py
index fd7ac4bf81107795bb3811f2ee36877b6b7b17c4..a25e9d61b9846d7addb3a1fec1e958464aee173a 100644 (file)
@@ -1,20 +1,19 @@
-# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2010-2012 W. Trevor King <wking@tremily.us>
 #
 # 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 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.
+# 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/>.
+# You should have received a copy of the GNU Lesser General Public License
+# along with Hooke.  If not, see <http://www.gnu.org/licenses/>.
 
 """The `playlist` module provides a :class:`Playlist` and its subclass
 :class:`FilePlaylist` for manipulating lists of
@@ -74,7 +73,7 @@ class NoteIndexList (list):
         try:
             self.__dict__.update(state)
         except TypeError, e:
-            print state, type(state), e
+            print(' '.join(str(x) for x in [state, type(state), e]))
         if self.info in [None, {}]:
             self.info = {}
 
@@ -88,14 +87,17 @@ class NoteIndexList (list):
         is `None`.
         """
         if value == None:
+            if self._index >= len(self):  # perhaps items have been popped
+                self._index = len(self) - 1
             return self._index
         return super(NoteIndexList, self).index(value, *args, **kwargs)
 
-    def current(self):
+    def current(self, load=True):
         if len(self) == 0:
             return None
         item = self[self._index]
-        self._setup_item(item)
+        if load == True:
+            self._setup_item(item)
         return item
 
     def jump(self, index):
@@ -126,6 +128,9 @@ class NoteIndexList (list):
         index = self._index
         items = self
         if reverse == True:
+            # could iterate through `c` if current_curve_callback()
+            # would work, but `c` is not bound to the local `hooke`,
+            # so curent_playlist_callback cannot point to it.
             items = reverse_enumerate(self)
         else:
             items = enumerate(self)
@@ -137,11 +142,11 @@ class NoteIndexList (list):
 
     def filter(self, keeper_fn=lambda item:True, load_curves=True,
                *args, **kwargs):
-        c = copy.deepcopy(self)
+        c = copy.copy(self)
         if load_curves == True:
-            items = c.items(reverse=True)
+            items = self.items(reverse=True)
         else:
-            items = reversed(c)
+            items = reversed(self)
         for item in items: 
             if keeper_fn(item, *args, **kwargs) != True:
                 c.remove(item)
@@ -160,14 +165,13 @@ class Playlist (NoteIndexList):
     def __init__(self, drivers, name=None):
         super(Playlist, self).__init__(name=name)
         self.drivers = drivers
-        self._max_loaded = 100 # curves to hold in memory simultaneously.
 
     def _set_default_attrs(self):
         super(Playlist, self)._set_default_attrs()
         self._default_attrs['drivers'] = []
         # List of loaded curves, see :meth:`._setup_item`.
         self._default_attrs['_loaded'] = []
-        self._default_attrs['_max_loaded'] = 100
+        self._default_attrs['_max_loaded'] = 100  # curves to hold in memory simultaneously.
 
     def __setstate__(self, state):
         super(Playlist, self).__setstate__(state)
@@ -194,13 +198,42 @@ class Playlist (NoteIndexList):
                 self.append(curve)
             if curve.driver == None:
                 c.identify(self.drivers)
-            if curve.data == None:
+            if curve.data == None or max([d.size for d in curve.data]) == 0:
                 curve.load()
             self._loaded.append(curve)
             if len(self._loaded) > self._max_loaded:
                 oldest = self._loaded.pop(0)
                 oldest.unload()
 
+    def unload(self, curve):
+        "Inverse of `._setup_item`."
+        curve.unload()
+        try:
+            self._loaded.remove(curve)
+        except ValueError:
+            pass
+
+
+def playlist_path(path, expand=False):
+    """Normalize playlist path extensions.
+
+    Examples
+    --------
+    >>> print(playlist_path('playlist'))
+    playlist.hkp
+    >>> print(playlist_path('playlist.hkp'))
+    playlist.hkp
+    >>> print(playlist_path(None))
+    None
+    """
+    if path == None:
+        return None
+    if not path.endswith('.hkp'):
+        path += '.hkp'
+    if expand:
+        path = os.path.abspath(os.path.expanduser(path))
+    return path
+
 
 class FilePlaylist (Playlist):
     """A file-backed :class:`Playlist`.
@@ -219,10 +252,10 @@ class FilePlaylist (Playlist):
     >>> s = pickle.dumps(p)
     >>> z = pickle.loads(s)
     >>> for curve in z:
-    ...     print curve
+    ...     print(curve)
     <Curve A>
     <Curve B>
-    >>> print z.drivers
+    >>> print(z.drivers)
     ['Driver A', 'Driver B']
 
     The data-type is also YAMLable (see :mod:`hooke.util.yaml`).
@@ -230,10 +263,10 @@ class FilePlaylist (Playlist):
     >>> s = yaml.dump(p)
     >>> z = yaml.load(s)
     >>> for curve in z:
-    ...     print curve
+    ...     print(curve)
     <Curve A>
     <Curve B>
-    >>> print z.drivers
+    >>> print(z.drivers)
     ['Driver A', 'Driver B']
     """
     version = '0.2'
@@ -272,11 +305,9 @@ class FilePlaylist (Playlist):
             if self._base_path == None:
                 self._base_path = os.getcwd()
         else:
-            if not path.endswith('.hkp'):
-                path += '.hkp'
+            path = playlist_path(path, expand=True)
             self.path = path
-            self._base_path = os.path.dirname(os.path.abspath(
-                os.path.expanduser(self.path)))
+            self._base_path = os.path.dirname(self.path)
             if self.name == None:
                 self.name = os.path.basename(path)
         if self._base_path != orig_base_path:
@@ -364,7 +395,7 @@ class FilePlaylist (Playlist):
         ...         CommandMessage('command B', {'arg 0':1, 'curve':c}),
         ...         ])
         >>> p.append_curve(c)
-        >>> print p.flatten()  # doctest: +REPORT_UDIFF
+        >>> print(p.flatten())  # doctest: +REPORT_UDIFF
         # Hooke playlist version 0.2
         !!python/object/new:hooke.playlist.FilePlaylist
         listitems:
@@ -378,11 +409,13 @@ class FilePlaylist (Playlist):
             - !!python/object:hooke.engine.CommandMessage
               arguments: {arg 0: 0, arg 1: X}
               command: command A
+              explicit_user_call: true
             - !!python/object:hooke.engine.CommandMessage
               arguments:
                 arg 0: 1
                 curve: *id001
               command: command B
+              explicit_user_call: true
           info: {attr with spaces: 'The second curve
         <BLANKLINE>
               with endlines'}
@@ -396,7 +429,7 @@ class FilePlaylist (Playlist):
           version: '0.2'
         <BLANKLINE>
         >>> p.relative_curve_paths = False
-        >>> print p.flatten()  # doctest: +REPORT_UDIFF
+        >>> print(p.flatten())  # doctest: +REPORT_UDIFF
         # Hooke playlist version 0.2
         !!python/object/new:hooke.playlist.FilePlaylist
         listitems:
@@ -410,11 +443,13 @@ class FilePlaylist (Playlist):
             - !!python/object:hooke.engine.CommandMessage
               arguments: {arg 0: 0, arg 1: X}
               command: command A
+              explicit_user_call: true
             - !!python/object:hooke.engine.CommandMessage
               arguments:
                 arg 0: 1
                 curve: *id001
               command: command B
+              explicit_user_call: true
           info: {attr with spaces: 'The second curve
         <BLANKLINE>
               with endlines'}
@@ -433,8 +468,10 @@ class FilePlaylist (Playlist):
         self._relative_curve_paths = self.relative_curve_paths
         self.update_curve_paths()
         self._relative_curve_paths = rcp
-
+        digest = self._digest
+        self._digest = None  # don't save the digest (recursive file).
         yaml_string = yaml.dump(self, allow_unicode=True)
+        self._digest = digest
         self.update_curve_paths()
         return ('# Hooke playlist version %s\n' % self.version) + yaml_string
 
@@ -471,7 +508,7 @@ def from_string(string):
     >>> p = from_string(string)
     >>> p.set_path('/path/to/playlist')
     >>> for curve in p:
-    ...     print curve.name, curve.path
+    ...     print('{} {}'.format(curve.name, curve.path))
     one /path/to/curve/one
     two /path/to/curve/two
 
@@ -515,7 +552,7 @@ def from_string(string):
     >>> p.info
     {'note': 'An example playlist'}
     >>> for curve in p:
-    ...     print curve.name, curve.path
+    ...     print('{} {}'.format(curve.name, curve.path))
     one /path/to/curve/one
     two /path/to/curve/two
     >>> p[-1].info['attr with spaces']
@@ -537,13 +574,18 @@ def from_string(string):
     """
     return yaml.load(string)
 
-def load(path=None, identify=True, hooke=None):
+def load(path=None, drivers=None, identify=True, hooke=None):
     """Load a playlist from a file.
     """
-    with open(self.path, 'r') as f:
+    path = playlist_path(path, expand=True)
+    with open(path, 'r') as f:
         text = f.read()
     playlist = from_string(text)
+    playlist.set_path(path)
     playlist._digest = playlist.digest()
+    if drivers != None:
+        playlist.drivers = drivers
+    playlist.set_path(path)
     for curve in playlist:
         curve.set_hooke(hooke)
         if identify == True: