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.
27 from ..command import Command, Argument, Failure
28 from ..playlist import FilePlaylist
32 class PlaylistPlugin (Builtin):
34 super(PlaylistPlugin, self).__init__(name='playlist')
36 NextCommand(self), PreviousCommand(self), JumpCommand(self),
37 GetCommand(self), IndexCommand(self), CurveListCommand(self),
38 SaveCommand(self), LoadCommand(self),
39 AddCommand(self), AddGlobCommand(self),
40 RemoveCommand(self), ApplyCommandStack(self),
41 FilterCommand(self), NoteFilterCommand(self),
45 # Define common or complicated arguments
47 def current_playlist_callback(hooke, command, argument, value):
50 playlist = hooke.playlists.current()
52 raise Failure('No playlists loaded')
55 PlaylistArgument = Argument(
56 name='playlist', type='playlist', callback=current_playlist_callback,
58 :class:`hooke.playlist.Playlist` to act on. Defaults to the current
62 def playlist_name_callback(hooke, command, argument, value):
66 names = [p.name for p in hooke.playlists]
68 name = 'playlist-%d' % i
73 PlaylistNameArgument = Argument(
74 name='output playlist', type='string', optional=True,
75 callback=playlist_name_callback,
77 Name of the new playlist (defaults to an auto-generated name).
80 def all_drivers_callback(hooke, command, argument, value):
84 # Define useful command subclasses
86 class PlaylistCommand (Command):
87 """A :class:`~hooke.command.Command` operating on a
88 :class:`~hooke.playlist.Playlist`.
90 def __init__(self, **kwargs):
91 if 'arguments' in kwargs:
92 kwargs['arguments'].insert(0, PlaylistArgument)
94 kwargs['arguments'] = [PlaylistArgument]
95 super(PlaylistCommand, self).__init__(**kwargs)
97 def _playlist(self, hooke, params):
98 """Get the selected playlist.
102 `hooke` is intended to attach the selected playlist to the
103 local hooke instance; the returned playlist should not be
104 effected by the state of `hooke`.
106 # HACK? rely on params['playlist'] being bound to the local
107 # hooke (i.e. not a copy, as you would get by passing a
108 # playlist through the queue). Ugh. Stupid queues. As an
109 # alternative, we could pass lookup information through the
111 return params['playlist']
114 class PlaylistAddingCommand (Command):
115 """A :class:`~hooke.command.Command` adding a
116 :class:`~hooke.playlist.Playlist`.
118 def __init__(self, **kwargs):
119 if 'arguments' in kwargs:
120 kwargs['arguments'].insert(0, PlaylistNameArgument)
122 kwargs['arguments'] = [PlaylistNameArgument]
123 super(PlaylistAddingCommand, self).__init__(**kwargs)
125 def _set_playlist(self, hooke, params, playlist):
126 """Attach a new playlist.
128 playlist.name = params['output playlist']
129 hooke.playlists.append(playlist)
134 class NextCommand (PlaylistCommand):
135 """Move playlist to the next curve.
137 def __init__(self, plugin):
138 super(NextCommand, self).__init__(
139 name='next curve', help=self.__doc__, plugin=plugin)
141 def _run(self, hooke, inqueue, outqueue, params):
142 self._playlist(hooke, params).next()
145 class PreviousCommand (PlaylistCommand):
146 """Move playlist to the previous curve.
148 def __init__(self, plugin):
149 super(PreviousCommand, self).__init__(
150 name='previous curve', help=self.__doc__, plugin=plugin)
152 def _run(self, hooke, inqueue, outqueue, params):
153 self._playlist(hooke, params).previous()
156 class JumpCommand (PlaylistCommand):
157 """Move playlist to a given curve.
159 def __init__(self, plugin):
160 super(JumpCommand, self).__init__(
161 name='jump to curve',
163 Argument(name='index', type='int', optional=False, help="""
164 Index of target curve.
167 help=self.__doc__, plugin=plugin)
169 def _run(self, hooke, inqueue, outqueue, params):
170 self._playlist(hooke, params).jump(params['index'])
173 class IndexCommand (PlaylistCommand):
174 """Print the index of the current curve.
176 The first curve has index 0.
178 def __init__(self, plugin):
179 super(IndexCommand, self).__init__(
180 name='curve index', help=self.__doc__, plugin=plugin)
182 def _run(self, hooke, inqueue, outqueue, params):
183 outqueue.put(self._playlist(hooke, params).index())
186 class GetCommand (PlaylistCommand):
187 """Return a :class:`hooke.playlist.Playlist`.
189 def __init__(self, plugin):
190 super(GetCommand, self).__init__(
191 name='get playlist', help=self.__doc__, plugin=plugin)
193 def _run(self, hooke, inqueue, outqueue, params):
194 outqueue.put(self._playlist(hooke, params))
197 class CurveListCommand (PlaylistCommand):
198 """Get the curves in a playlist.
200 def __init__(self, plugin):
201 super(CurveListCommand, self).__init__(
202 name='playlist curves', help=self.__doc__, plugin=plugin)
204 def _run(self, hooke, inqueue, outqueue, params):
205 outqueue.put(list(self._playlist(hooke, params)))
208 class SaveCommand (PlaylistCommand):
211 def __init__(self, plugin):
212 super(SaveCommand, self).__init__(
213 name='save playlist',
215 Argument(name='output', type='file',
217 File name for the output playlist. Defaults to overwriting the input
218 playlist. If the playlist does not have an input file (e.g. it was
219 created from scratch with 'new playlist'), this option is required.
222 help=self.__doc__, plugin=plugin)
224 def _run(self, hooke, inqueue, outqueue, params):
225 self._playlist(hooke, params).save(params['output'])
228 class LoadCommand (PlaylistAddingCommand):
231 def __init__(self, plugin):
232 super(LoadCommand, self).__init__(
233 name='load playlist',
235 Argument(name='input', type='file', optional=False,
237 File name for the input playlist.
239 Argument(name='drivers', type='driver', optional=True,
240 count=-1, callback=all_drivers_callback,
242 Drivers for loading curves.
245 help=self.__doc__, plugin=plugin)
247 def _run(self, hooke, inqueue, outqueue, params):
248 p = FilePlaylist(drivers=params['drivers'], path=params['input'])
250 playlist_names = [p.name for p in hooke.playlists]
251 if p.name not in playlist_names:
252 params['output playlist'] = p.name # HACK: override input name. How to tell if it is callback-generated?
253 self._set_playlist(hooke, params, p)
257 class AddCommand (PlaylistCommand):
258 """Add a curve to a playlist.
260 def __init__(self, plugin):
261 super(AddCommand, self).__init__(
262 name='add curve to playlist',
264 Argument(name='input', type='file', optional=False,
266 File name for the input :class:`hooke.curve.Curve`.
268 Argument(name='info', type='dict', optional=True,
270 Additional information for the input :class:`hooke.curve.Curve`.
273 help=self.__doc__, plugin=plugin)
275 def _run(self, hooke, inqueue, outqueue, params):
276 self._playlist(hooke, params).append_curve_by_path(
277 params['input'], params['info'], hooke=hooke)
280 class AddGlobCommand (PlaylistCommand):
281 """Add curves to a playlist with file globbing.
283 Adding lots of files one at a time can be tedious. With this
284 command you can use globs (`data/curves/*.dat`) to add curves
285 for all matching files at once.
287 def __init__(self, plugin):
288 super(AddGlobCommand, self).__init__(
289 name='glob curves to playlist',
291 Argument(name='input', type='string', optional=False,
293 File name glob for the input :class:`hooke.curve.Curve`.
295 Argument(name='info', type='dict', optional=True,
297 Additional information for the input :class:`hooke.curve.Curve`.
300 help=self.__doc__, plugin=plugin)
302 def _run(self, hooke, inqueue, outqueue, params):
303 for path in sorted(glob.glob(params['input'])):
304 self._playlist(hooke, params).append_curve_by_path(
305 path, params['info'], hooke=hooke)
308 class RemoveCommand (PlaylistCommand):
309 """Remove a curve from a playlist.
311 def __init__(self, plugin):
312 super(RemoveCommand, self).__init__(
313 name='remove curve from playlist',
315 Argument(name='index', type='int', optional=False, help="""
316 Index of target curve.
319 help=self.__doc__, plugin=plugin)
321 def _run(self, hooke, inqueue, outqueue, params):
322 self._playlist(hooke, params).pop(params['index'])
323 self._playlist(hooke, params).jump(params.index())
326 class ApplyCommandStack (PlaylistCommand):
327 """Apply a :class:`~hooke.command_stack.CommandStack` to each
330 TODO: discuss `evaluate`.
332 def __init__(self, plugin):
333 super(ApplyCommandStack, self).__init__(
334 name='apply command stack',
336 Argument(name='commands', type='command stack', optional=False,
338 Command stack to apply to each curve.
340 Argument(name='evaluate', type='bool', default=False,
342 Evaluate the applied command stack immediately.
345 help=self.__doc__, plugin=plugin)
347 def _run(self, hooke, inqueue, outqueue, params):
348 if len(params['commands']) == 0:
350 p = self._playlist(hooke, params)
351 if params['evaluate'] == True:
352 for curve in p.items():
353 for command in params['commands']:
354 curve.command_stack.execute_command(hooke, command)
355 curve.command_stack.append(command)
358 curve.command_stack.extend(params['commands'])
359 curve.unload() # force command stack execution on next access.
362 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
363 """Create a subset playlist via a selection function.
365 Removing lots of curves one at a time can be tedious. With this
366 command you can use a function `filter` to select the curves you
371 There are issues with pickling functions bound to class
372 attributes, because the pickle module doesn't know where those
373 functions were originally defined (where it should point the
374 loader). Because of this, subclasses with hard-coded filter
375 functions are encouraged to define their filter function as a
376 method of their subclass. See, for example,
377 :meth:`NoteFilterCommand.filter`.
379 def __init__(self, plugin, name='filter playlist'):
380 super(FilterCommand, self).__init__(
381 name=name, help=self.__doc__, plugin=plugin)
382 if not hasattr(self, 'filter'):
383 self.arguments.append(
384 Argument(name='filter', type='function', optional=False,
386 Function returning `True` for "good" curves.
387 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
390 def _run(self, hooke, inqueue, outqueue, params):
391 if not hasattr(self, 'filter'):
392 filter_fn = params['filter']
394 filter_fn = self.filter
395 p = self._playlist(hooke, params).filter(filter_fn,
396 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
397 p.name = params['name']
398 if hasattr(p, 'path') and p.path != None:
399 p.set_path(os.path.join(os.path.dirname(p.path), p.name))
400 self._set_playlist(hooke, params, p)
404 class NoteFilterCommand (FilterCommand):
405 """Create a subset playlist of curves with `.info['note'] != None`.
407 def __init__(self, plugin):
408 super(NoteFilterCommand, self).__init__(
409 plugin, name='note filter playlist')
411 def filter(self, curve, hooke, inqueue, outqueue, params):
412 return 'note' in curve.info and curve.info['note'] != None