Add another parameter-casting hack.
[hooke.git] / hooke / plugin / playlist.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
5 # Hooke is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation, either
8 # version 3 of the License, or (at your option) any later version.
9 #
10 # Hooke is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Lesser General Public License for more details.
14 #
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/>.
18
19 """The ``playlist`` module provides :class:`PlaylistPlugin` and
20 several associated :class:`hooke.command.Command`\s for handling
21 :mod:`hooke.playlist` classes.
22 """
23
24 import os.path
25 import glob
26
27 from ..command import Command, Argument, Failure
28 from ..playlist import FilePlaylist
29 from ..plugin import Builtin
30
31
32 class PlaylistPlugin (Builtin):
33     def __init__(self):
34         super(PlaylistPlugin, self).__init__(name='playlist')
35         self._commands = [
36             NextCommand(self), PreviousCommand(self), JumpCommand(self),
37             IndexCommand(self), CurveListCommand(self),
38             SaveCommand(self), LoadCommand(self),
39             AddCommand(self), AddGlobCommand(self),
40             RemoveCommand(self), FilterCommand(self), NoteFilterCommand(self)]
41
42
43 # Define common or complicated arguments
44
45 def current_playlist_callback(hooke, command, argument, value):
46     if value != None:
47         return value
48     playlist = hooke.playlists.current()
49     if playlist == None:
50         raise Failure('No playlists loaded')
51     return playlist
52
53 PlaylistArgument = Argument(
54     name='playlist', type='playlist', callback=current_playlist_callback,
55     help="""
56 :class:`hooke.playlist.Playlist` to act on.  Defaults to the current
57 playlist.
58 """.strip())
59
60 def playlist_name_callback(hooke, command, argument, value):
61     i = 0
62     names = [p.name for p in hooke.playlists]
63     while True:
64         name = 'playlist-%d' % i
65         if name not in names:
66             return name
67         i += 1
68
69 PlaylistNameArgument = Argument(
70     name='name', type='string', optional=True, callback=playlist_name_callback,
71     help="""
72 Name of the new playlist (defaults to an auto-generated name).
73 """.strip())
74
75 def all_drivers_callback(hooke, command, argument, value):
76     return hooke.drivers
77
78
79 # Define commands
80
81 class NextCommand (Command):
82     """Move playlist to the next curve.
83     """
84     def __init__(self, plugin):
85         super(NextCommand, self).__init__(
86             name='next curve',
87             arguments=[PlaylistArgument],
88             help=self.__doc__, plugin=plugin)
89
90     def _run(self, hooke, inqueue, outqueue, params):
91         params['playlist'].next()
92
93 class PreviousCommand (Command):
94     """Move playlist to the previous curve.
95     """
96     def __init__(self, plugin):
97         super(PreviousCommand, self).__init__(
98             name='previous curve',
99             arguments=[PlaylistArgument],
100             help=self.__doc__, plugin=plugin)
101
102     def _run(self, hooke, inqueue, outqueue, params):
103         params['playlist'].previous()
104
105 class JumpCommand (Command):
106     """Move playlist to a given curve.
107     """
108     def __init__(self, plugin):
109         super(JumpCommand, self).__init__(
110             name='jump to curve',
111             arguments=[
112                 PlaylistArgument,
113                 Argument(name='index', type='int', optional=False, help="""
114 Index of target curve.
115 """.strip()),
116                 ],
117             help=self.__doc__, plugin=plugin)
118
119     def _run(self, hooke, inqueue, outqueue, params):
120         params['playlist'].jump(int(params['index'])) # HACK, int() should be handled by ui
121
122 class IndexCommand (Command):
123     """Print the index of the current curve.
124
125     The first curve has index 0.
126     """
127     def __init__(self, plugin):
128         super(IndexCommand, self).__init__(
129             name='curve index',
130             arguments=[
131                 PlaylistArgument,
132                 ],
133             help=self.__doc__, plugin=plugin)
134
135     def _run(self, hooke, inqueue, outqueue, params):
136         outqueue.put(params['playlist']._index)
137
138 class CurveListCommand (Command):
139     """Get the curves in a playlist.
140     """
141     def __init__(self, plugin):
142         super(CurveListCommand, self).__init__(
143             name='playlist curves',
144             arguments=[
145                 PlaylistArgument,
146                 ],
147             help=self.__doc__, plugin=plugin)
148
149     def _run(self, hooke, inqueue, outqueue, params):
150         outqueue.put([c for c in params['playlist']])
151
152 class SaveCommand (Command):
153     """Save a playlist.
154     """
155     def __init__(self, plugin):
156         super(SaveCommand, self).__init__(
157             name='save playlist',
158             arguments=[
159                 PlaylistArgument,
160                 Argument(name='output', type='file',
161                          help="""
162 File name for the output playlist.  Defaults to overwriting the input
163 playlist.
164 """.strip()),
165                 ],
166             help=self.__doc__, plugin=plugin)
167
168     def _run(self, hooke, inqueue, outqueue, params):
169         params['playlist'].save(params['output'])
170
171 class LoadCommand (Command):
172     """Load a playlist.
173     """
174     def __init__(self, plugin):
175         super(LoadCommand, self).__init__(
176             name='load playlist',
177             arguments=[
178                 Argument(name='input', type='file', optional=False,
179                          help="""
180 File name for the input playlist.
181 """.strip()),
182                 Argument(name='drivers', type='driver', optional=True,
183                          count=-1, callback=all_drivers_callback,
184                          help="""
185 Drivers for loading curves.
186 """.strip()),
187                 ],
188             help=self.__doc__, plugin=plugin)
189
190     def _run(self, hooke, inqueue, outqueue, params):
191         p = FilePlaylist(drivers=params['drivers'], path=params['input'])
192         p.load()
193         hooke.playlists.append(p)
194         outqueue.put(p)
195
196 class AddCommand (Command):
197     """Add a curve to a playlist.
198     """
199     def __init__(self, plugin):
200         super(AddCommand, self).__init__(
201             name='add curve to playlist',
202             arguments=[
203                 PlaylistArgument,
204                 Argument(name='input', type='file', optional=False,
205                          help="""
206 File name for the input :class:`hooke.curve.Curve`.
207 """.strip()),
208                 Argument(name='info', type='dict', optional=True,
209                          help="""
210 Additional information for the input :class:`hooke.curve.Curve`.
211 """.strip()),
212                 ],
213             help=self.__doc__, plugin=plugin)
214
215     def _run(self, hooke, inqueue, outqueue, params):
216         params['playlist'].append_curve_by_path(params['input'],
217                                                 params['info'])
218
219 class AddGlobCommand (Command):
220     """Add curves to a playlist with file globbing.
221
222     Adding lots of files one at a time can be tedious.  With this
223     command you can use globs (`data/curves/*.dat`) to add curves
224     for all matching files at once.
225     """
226     def __init__(self, plugin):
227         super(AddGlobCommand, self).__init__(
228             name='glob curves to playlist',
229             arguments=[
230                 PlaylistArgument,
231                 Argument(name='input', type='glob', optional=False,
232                          help="""
233 File name glob for the input :class:`hooke.curve.Curve`.
234 """.strip()),
235                 Argument(name='info', type='dict', optional=True,
236                          help="""
237 Additional information for the input :class:`hooke.curve.Curve`.
238 """.strip()),
239                 ],
240             help=self.__doc__, plugin=plugin)
241
242     def _run(self, hooke, inqueue, outqueue, params):
243         for path in sorted(glob.glob(params['input'])):
244             params['playlist'].append_curve_by_path(path, params['info'])
245
246 class RemoveCommand (Command):
247     """Remove a curve from a playlist.
248     """
249     def __init__(self, plugin):
250         super(RemoveCommand, self).__init__(
251             name='remove curve from playlist',
252             arguments=[
253                 PlaylistArgument,
254                 Argument(name='index', type='int', optional=False, help="""
255 Index of target curve.
256 """.strip()),
257                 ],
258             help=self.__doc__, plugin=plugin)
259
260     def _run(self, hooke, inqueue, outqueue, params):
261         params['playlist'].pop(params['index'])
262         params['playlist'].jump(params._index)
263
264 class FilterCommand (Command):
265     """Create a subset playlist via a selection function.
266
267     Removing lots of curves one at a time can be tedious.  With this
268     command you can use a function `filter` to select the curves you
269     wish to keep.
270
271     Notes
272     -----
273     There are issues with pickling functions bound to class
274     attributes, because the pickle module doesn't know where those
275     functions were originally defined (where it should point the
276     loader).  Because of this, subclasses with hard-coded filter
277     functions are encouraged to define their filter function as a
278     method of their subclass.  See, for example,
279     :meth:`NoteFilterCommand.filter`.
280     """
281     def __init__(self, plugin, name='filter playlist'):
282         super(FilterCommand, self).__init__(
283             name=name,
284             arguments=[
285                 PlaylistArgument,
286                 PlaylistNameArgument,
287                 ],
288             help=self.__doc__, plugin=plugin)
289         if not hasattr(self, 'filter'):
290             self.arguments.append(
291                 Argument(name='filter', type='function', optional=False,
292                          help="""
293 Function returning `True` for "good" curves.
294 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
295 """.strip()))
296
297     def _run(self, hooke, inqueue, outqueue, params):
298         if not hasattr(self, 'filter'):
299             filter_fn = params['filter']
300         else:
301             filter_fn = self.filter
302         p = params['playlist'].filter(filter_fn,
303             hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
304         p.name = params['name']
305         if hasattr(p, 'path') and p.path != None:
306             p.set_path(os.path.join(os.path.dirname(p.path), p.name))
307         hooke.playlists.append(p)
308         outqueue.put(p)
309
310 class NoteFilterCommand (FilterCommand):
311     """Create a subset playlist of curves with `.info['note'] != None`.
312     """
313     def __init__(self, plugin):
314         super(NoteFilterCommand, self).__init__(
315             plugin, name='note filter playlist')
316
317     def filter(self, curve, hooke, inqueue, outqueue, params):
318         return 'note' in curve.info and curve.info['note'] != None