1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 # Public License for more details.
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/>.
19 """The ``playlist`` module provides :class:`PlaylistPlugin` and
20 several associated :class:`hooke.command.Command`\s for handling
21 :mod:`hooke.playlist` classes.
28 from ..command import Command, Argument, Failure
29 from ..playlist import FilePlaylist
30 from ..curve import NotRecognized
34 class PlaylistPlugin (Builtin):
36 super(PlaylistPlugin, self).__init__(name='playlist')
38 NextCommand(self), PreviousCommand(self), JumpCommand(self),
39 GetCommand(self), IndexCommand(self), CurveListCommand(self),
40 SaveCommand(self), LoadCommand(self),
41 AddCommand(self), AddGlobCommand(self),
42 RemoveCommand(self), ApplyCommandStack(self),
47 # Define common or complicated arguments
49 def current_playlist_callback(hooke, command, argument, value):
52 playlist = hooke.playlists.current()
54 raise Failure('No playlists loaded')
57 PlaylistArgument = Argument(
58 name='playlist', type='playlist', callback=current_playlist_callback,
60 :class:`hooke.playlist.Playlist` to act on. Defaults to the current
64 def playlist_name_callback(hooke, command, argument, value):
68 names = [p.name for p in hooke.playlists]
70 name = 'playlist-%d' % i
75 PlaylistNameArgument = Argument(
76 name='output playlist', type='string', optional=True,
77 callback=playlist_name_callback,
79 Name of the new playlist (defaults to an auto-generated name).
82 def all_drivers_callback(hooke, command, argument, value):
86 # Define useful command subclasses
88 class PlaylistCommand (Command):
89 """A :class:`~hooke.command.Command` operating on a
90 :class:`~hooke.playlist.Playlist`.
92 def __init__(self, **kwargs):
93 if 'arguments' in kwargs:
94 kwargs['arguments'].insert(0, PlaylistArgument)
96 kwargs['arguments'] = [PlaylistArgument]
97 super(PlaylistCommand, self).__init__(**kwargs)
99 def _playlist(self, hooke, params):
100 """Get the selected playlist.
104 `hooke` is intended to attach the selected playlist to the
105 local hooke instance; the returned playlist should not be
106 effected by the state of `hooke`.
108 # HACK? rely on params['playlist'] being bound to the local
109 # hooke (i.e. not a copy, as you would get by passing a
110 # playlist through the queue). Ugh. Stupid queues. As an
111 # alternative, we could pass lookup information through the
113 return params['playlist']
116 class PlaylistAddingCommand (Command):
117 """A :class:`~hooke.command.Command` adding a
118 :class:`~hooke.playlist.Playlist`.
120 def __init__(self, **kwargs):
121 if 'arguments' in kwargs:
122 kwargs['arguments'].insert(0, PlaylistNameArgument)
124 kwargs['arguments'] = [PlaylistNameArgument]
125 super(PlaylistAddingCommand, self).__init__(**kwargs)
127 def _set_playlist(self, hooke, params, playlist):
128 """Attach a new playlist.
130 playlist_names = [p.name for p in hooke.playlists]
131 if playlist.name in playlist_names or playlist.name == None:
132 playlist.name = params['output playlist'] # HACK: override input name. How to tell if it is callback-generated?
133 hooke.playlists.append(playlist)
138 class NextCommand (PlaylistCommand):
139 """Move playlist to the next curve.
141 def __init__(self, plugin):
142 super(NextCommand, self).__init__(
143 name='next curve', help=self.__doc__, plugin=plugin)
145 def _run(self, hooke, inqueue, outqueue, params):
146 self._playlist(hooke, params).next()
149 class PreviousCommand (PlaylistCommand):
150 """Move playlist to the previous curve.
152 def __init__(self, plugin):
153 super(PreviousCommand, self).__init__(
154 name='previous curve', help=self.__doc__, plugin=plugin)
156 def _run(self, hooke, inqueue, outqueue, params):
157 self._playlist(hooke, params).previous()
160 class JumpCommand (PlaylistCommand):
161 """Move playlist to a given curve.
163 def __init__(self, plugin):
164 super(JumpCommand, self).__init__(
165 name='jump to curve',
167 Argument(name='index', type='int', optional=False, help="""
168 Index of target curve.
171 help=self.__doc__, plugin=plugin)
173 def _run(self, hooke, inqueue, outqueue, params):
174 self._playlist(hooke, params).jump(params['index'])
177 class IndexCommand (PlaylistCommand):
178 """Print the index of the current curve.
180 The first curve has index 0.
182 def __init__(self, plugin):
183 super(IndexCommand, self).__init__(
184 name='curve index', help=self.__doc__, plugin=plugin)
186 def _run(self, hooke, inqueue, outqueue, params):
187 outqueue.put(self._playlist(hooke, params).index())
190 class GetCommand (PlaylistCommand):
191 """Return a :class:`hooke.playlist.Playlist`.
193 def __init__(self, plugin):
194 super(GetCommand, self).__init__(
195 name='get playlist', help=self.__doc__, plugin=plugin)
197 def _run(self, hooke, inqueue, outqueue, params):
198 outqueue.put(self._playlist(hooke, params))
201 class CurveListCommand (PlaylistCommand):
202 """Get the curves in a playlist.
204 def __init__(self, plugin):
205 super(CurveListCommand, self).__init__(
206 name='playlist curves', help=self.__doc__, plugin=plugin)
208 def _run(self, hooke, inqueue, outqueue, params):
209 outqueue.put(list(self._playlist(hooke, params)))
212 class SaveCommand (PlaylistCommand):
215 def __init__(self, plugin):
216 super(SaveCommand, self).__init__(
217 name='save playlist',
219 Argument(name='output', type='file',
221 File name for the output playlist. Defaults to overwriting the input
222 playlist. If the playlist does not have an input file (e.g. it was
223 created from scratch with 'new playlist'), this option is required.
226 help=self.__doc__, plugin=plugin)
228 def _run(self, hooke, inqueue, outqueue, params):
229 self._playlist(hooke, params).save(params['output'])
232 class LoadCommand (PlaylistAddingCommand):
235 def __init__(self, plugin):
236 super(LoadCommand, self).__init__(
237 name='load playlist',
239 Argument(name='input', type='file', optional=False,
241 File name for the input playlist.
243 Argument(name='drivers', type='driver', optional=True,
244 count=-1, callback=all_drivers_callback,
246 Drivers for loading curves.
249 help=self.__doc__, plugin=plugin)
251 def _run(self, hooke, inqueue, outqueue, params):
252 p = FilePlaylist(drivers=params['drivers'], path=params['input'])
254 self._set_playlist(hooke, params, p)
258 class AddCommand (PlaylistCommand):
259 """Add a curve to a playlist.
261 def __init__(self, plugin):
262 super(AddCommand, self).__init__(
263 name='add curve to playlist',
265 Argument(name='input', type='file', optional=False,
267 File name for the input :class:`hooke.curve.Curve`.
269 Argument(name='info', type='dict', optional=True,
271 Additional information for the input :class:`hooke.curve.Curve`.
274 help=self.__doc__, plugin=plugin)
276 def _run(self, hooke, inqueue, outqueue, params):
277 self._playlist(hooke, params).append_curve_by_path(
278 params['input'], params['info'], hooke=hooke)
281 class AddGlobCommand (PlaylistCommand):
282 """Add curves to a playlist with file globbing.
284 Adding lots of files one at a time can be tedious. With this
285 command you can use globs (`data/curves/*.dat`) to add curves
286 for all matching files at once.
288 def __init__(self, plugin):
289 super(AddGlobCommand, self).__init__(
290 name='glob curves to playlist',
292 Argument(name='input', type='string', optional=False,
294 File name glob for the input :class:`hooke.curve.Curve`.
296 Argument(name='info', type='dict', optional=True,
298 Additional information for the input :class:`hooke.curve.Curve`.
301 help=self.__doc__, plugin=plugin)
303 def _run(self, hooke, inqueue, outqueue, params):
304 p = self._playlist(hooke, params)
305 for path in sorted(glob.glob(params['input'])):
307 p.append_curve_by_path(path, params['info'], hooke=hooke)
308 except NotRecognized, e:
309 log = logging.getLogger('hooke')
314 class RemoveCommand (PlaylistCommand):
315 """Remove a curve from a playlist.
317 def __init__(self, plugin):
318 super(RemoveCommand, self).__init__(
319 name='remove curve from playlist',
321 Argument(name='index', type='int', optional=False, help="""
322 Index of target curve.
325 help=self.__doc__, plugin=plugin)
327 def _run(self, hooke, inqueue, outqueue, params):
328 self._playlist(hooke, params).pop(params['index'])
329 self._playlist(hooke, params).jump(params.index())
332 class ApplyCommandStack (PlaylistCommand):
333 """Apply a :class:`~hooke.command_stack.CommandStack` to each
336 TODO: discuss `evaluate`.
338 def __init__(self, plugin):
339 super(ApplyCommandStack, self).__init__(
340 name='apply command stack',
342 Argument(name='commands', type='command stack', optional=False,
344 Command stack to apply to each curve.
346 Argument(name='evaluate', type='bool', default=False,
348 Evaluate the applied command stack immediately.
351 help=self.__doc__, plugin=plugin)
353 def _run(self, hooke, inqueue, outqueue, params):
354 if len(params['commands']) == 0:
356 p = self._playlist(hooke, params)
357 if params['evaluate'] == True:
358 for curve in p.items():
359 for command in params['commands']:
360 curve.command_stack.execute_command(hooke, command)
361 curve.command_stack.append(command)
364 curve.command_stack.extend(params['commands'])
365 curve.unload() # force command stack execution on next access.
368 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
369 """Create a subset playlist via a selection function.
371 Removing lots of curves one at a time can be tedious. With this
372 command you can use a function `filter` to select the curves you
377 There are issues with pickling functions bound to class
378 attributes, because the pickle module doesn't know where those
379 functions were originally defined (where it should point the
380 loader). Because of this, subclasses with hard-coded filter
381 functions are encouraged to define their filter function as a
382 method of their subclass. See, for example,
383 :meth:`NoteFilterCommand.filter`.
385 def __init__(self, plugin, name='filter playlist'):
386 super(FilterCommand, self).__init__(
387 name=name, help=self.__doc__, plugin=plugin)
388 if not hasattr(self, 'filter'):
389 self.arguments.append(
390 Argument(name='filter', type='function', optional=False,
392 Function returning `True` for "good" curves.
393 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
396 def _run(self, hooke, inqueue, outqueue, params):
397 if not hasattr(self, 'filter'):
398 filter_fn = params['filter']
400 filter_fn = self.filter
401 p = self._playlist(hooke, params).filter(filter_fn,
402 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
403 self._set_playlist(hooke, params, p)
404 if hasattr(p, 'path') and p.path != None:
405 p.set_path(os.path.join(os.path.dirname(p.path), p.name))