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.name = params['output playlist']
131 hooke.playlists.append(playlist)
136 class NextCommand (PlaylistCommand):
137 """Move playlist to the next curve.
139 def __init__(self, plugin):
140 super(NextCommand, self).__init__(
141 name='next curve', help=self.__doc__, plugin=plugin)
143 def _run(self, hooke, inqueue, outqueue, params):
144 self._playlist(hooke, params).next()
147 class PreviousCommand (PlaylistCommand):
148 """Move playlist to the previous curve.
150 def __init__(self, plugin):
151 super(PreviousCommand, self).__init__(
152 name='previous curve', help=self.__doc__, plugin=plugin)
154 def _run(self, hooke, inqueue, outqueue, params):
155 self._playlist(hooke, params).previous()
158 class JumpCommand (PlaylistCommand):
159 """Move playlist to a given curve.
161 def __init__(self, plugin):
162 super(JumpCommand, self).__init__(
163 name='jump to curve',
165 Argument(name='index', type='int', optional=False, help="""
166 Index of target curve.
169 help=self.__doc__, plugin=plugin)
171 def _run(self, hooke, inqueue, outqueue, params):
172 self._playlist(hooke, params).jump(params['index'])
175 class IndexCommand (PlaylistCommand):
176 """Print the index of the current curve.
178 The first curve has index 0.
180 def __init__(self, plugin):
181 super(IndexCommand, self).__init__(
182 name='curve index', help=self.__doc__, plugin=plugin)
184 def _run(self, hooke, inqueue, outqueue, params):
185 outqueue.put(self._playlist(hooke, params).index())
188 class GetCommand (PlaylistCommand):
189 """Return a :class:`hooke.playlist.Playlist`.
191 def __init__(self, plugin):
192 super(GetCommand, self).__init__(
193 name='get playlist', help=self.__doc__, plugin=plugin)
195 def _run(self, hooke, inqueue, outqueue, params):
196 outqueue.put(self._playlist(hooke, params))
199 class CurveListCommand (PlaylistCommand):
200 """Get the curves in a playlist.
202 def __init__(self, plugin):
203 super(CurveListCommand, self).__init__(
204 name='playlist curves', help=self.__doc__, plugin=plugin)
206 def _run(self, hooke, inqueue, outqueue, params):
207 outqueue.put(list(self._playlist(hooke, params)))
210 class SaveCommand (PlaylistCommand):
213 def __init__(self, plugin):
214 super(SaveCommand, self).__init__(
215 name='save playlist',
217 Argument(name='output', type='file',
219 File name for the output playlist. Defaults to overwriting the input
220 playlist. If the playlist does not have an input file (e.g. it was
221 created from scratch with 'new playlist'), this option is required.
224 help=self.__doc__, plugin=plugin)
226 def _run(self, hooke, inqueue, outqueue, params):
227 self._playlist(hooke, params).save(params['output'])
230 class LoadCommand (PlaylistAddingCommand):
233 def __init__(self, plugin):
234 super(LoadCommand, self).__init__(
235 name='load playlist',
237 Argument(name='input', type='file', optional=False,
239 File name for the input playlist.
241 Argument(name='drivers', type='driver', optional=True,
242 count=-1, callback=all_drivers_callback,
244 Drivers for loading curves.
247 help=self.__doc__, plugin=plugin)
249 def _run(self, hooke, inqueue, outqueue, params):
250 p = FilePlaylist(drivers=params['drivers'], path=params['input'])
252 playlist_names = [playlist.name for playlist in hooke.playlists]
253 if p.name in playlist_names or p.name == None:
254 p.name = params['output playlist'] # HACK: override input name. How to tell if it is callback-generated?
255 self._set_playlist(hooke, params, p)
259 class AddCommand (PlaylistCommand):
260 """Add a curve to a playlist.
262 def __init__(self, plugin):
263 super(AddCommand, self).__init__(
264 name='add curve to playlist',
266 Argument(name='input', type='file', optional=False,
268 File name for the input :class:`hooke.curve.Curve`.
270 Argument(name='info', type='dict', optional=True,
272 Additional information for the input :class:`hooke.curve.Curve`.
275 help=self.__doc__, plugin=plugin)
277 def _run(self, hooke, inqueue, outqueue, params):
278 self._playlist(hooke, params).append_curve_by_path(
279 params['input'], params['info'], hooke=hooke)
282 class AddGlobCommand (PlaylistCommand):
283 """Add curves to a playlist with file globbing.
285 Adding lots of files one at a time can be tedious. With this
286 command you can use globs (`data/curves/*.dat`) to add curves
287 for all matching files at once.
289 def __init__(self, plugin):
290 super(AddGlobCommand, self).__init__(
291 name='glob curves to playlist',
293 Argument(name='input', type='string', optional=False,
295 File name glob for the input :class:`hooke.curve.Curve`.
297 Argument(name='info', type='dict', optional=True,
299 Additional information for the input :class:`hooke.curve.Curve`.
302 help=self.__doc__, plugin=plugin)
304 def _run(self, hooke, inqueue, outqueue, params):
305 p = self._playlist(hooke, params)
306 for path in sorted(glob.glob(params['input'])):
308 p.append_curve_by_path(path, params['info'], hooke=hooke)
309 except NotRecognized, e:
310 log = logging.getLogger('hooke')
315 class RemoveCommand (PlaylistCommand):
316 """Remove a curve from a playlist.
318 def __init__(self, plugin):
319 super(RemoveCommand, self).__init__(
320 name='remove curve from playlist',
322 Argument(name='index', type='int', optional=False, help="""
323 Index of target curve.
326 help=self.__doc__, plugin=plugin)
328 def _run(self, hooke, inqueue, outqueue, params):
329 self._playlist(hooke, params).pop(params['index'])
330 self._playlist(hooke, params).jump(params.index())
333 class ApplyCommandStack (PlaylistCommand):
334 """Apply a :class:`~hooke.command_stack.CommandStack` to each
337 TODO: discuss `evaluate`.
339 def __init__(self, plugin):
340 super(ApplyCommandStack, self).__init__(
341 name='apply command stack',
343 Argument(name='commands', type='command stack', optional=False,
345 Command stack to apply to each curve.
347 Argument(name='evaluate', type='bool', default=False,
349 Evaluate the applied command stack immediately.
352 help=self.__doc__, plugin=plugin)
354 def _run(self, hooke, inqueue, outqueue, params):
355 if len(params['commands']) == 0:
357 p = self._playlist(hooke, params)
358 if params['evaluate'] == True:
359 for curve in p.items():
360 for command in params['commands']:
361 curve.command_stack.execute_command(hooke, command)
362 curve.command_stack.append(command)
365 curve.command_stack.extend(params['commands'])
366 curve.unload() # force command stack execution on next access.
369 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
370 """Create a subset playlist via a selection function.
372 Removing lots of curves one at a time can be tedious. With this
373 command you can use a function `filter` to select the curves you
378 There are issues with pickling functions bound to class
379 attributes, because the pickle module doesn't know where those
380 functions were originally defined (where it should point the
381 loader). Because of this, subclasses with hard-coded filter
382 functions are encouraged to define their filter function as a
383 method of their subclass. See, for example,
384 :meth:`NoteFilterCommand.filter`.
386 def __init__(self, plugin, name='filter playlist'):
387 super(FilterCommand, self).__init__(
388 name=name, help=self.__doc__, plugin=plugin)
389 if not hasattr(self, 'filter'):
390 self.arguments.append(
391 Argument(name='filter', type='function', optional=False,
393 Function returning `True` for "good" curves.
394 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
397 def _run(self, hooke, inqueue, outqueue, params):
398 if not hasattr(self, 'filter'):
399 filter_fn = params['filter']
401 filter_fn = self.filter
402 p = self._playlist(hooke, params).filter(filter_fn,
403 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
404 p.name = params['name']
405 if hasattr(p, 'path') and p.path != None:
406 p.set_path(os.path.join(os.path.dirname(p.path), p.name))
407 self._set_playlist(hooke, params, p)