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 self._set_playlist(hooke, params, p)
254 class AddCommand (PlaylistCommand):
255 """Add a curve to a playlist.
257 def __init__(self, plugin):
258 super(AddCommand, self).__init__(
259 name='add curve to playlist',
261 Argument(name='input', type='file', optional=False,
263 File name for the input :class:`hooke.curve.Curve`.
265 Argument(name='info', type='dict', optional=True,
267 Additional information for the input :class:`hooke.curve.Curve`.
270 help=self.__doc__, plugin=plugin)
272 def _run(self, hooke, inqueue, outqueue, params):
273 self._playlist(hooke, params).append_curve_by_path(
274 params['input'], params['info'], hooke=hooke)
277 class AddGlobCommand (PlaylistCommand):
278 """Add curves to a playlist with file globbing.
280 Adding lots of files one at a time can be tedious. With this
281 command you can use globs (`data/curves/*.dat`) to add curves
282 for all matching files at once.
284 def __init__(self, plugin):
285 super(AddGlobCommand, self).__init__(
286 name='glob curves to playlist',
288 Argument(name='input', type='string', optional=False,
290 File name glob for the input :class:`hooke.curve.Curve`.
292 Argument(name='info', type='dict', optional=True,
294 Additional information for the input :class:`hooke.curve.Curve`.
297 help=self.__doc__, plugin=plugin)
299 def _run(self, hooke, inqueue, outqueue, params):
300 for path in sorted(glob.glob(params['input'])):
301 self._playlist(hooke, params).append_curve_by_path(
302 path, params['info'], hooke=hooke)
305 class RemoveCommand (PlaylistCommand):
306 """Remove a curve from a playlist.
308 def __init__(self, plugin):
309 super(RemoveCommand, self).__init__(
310 name='remove curve from playlist',
312 Argument(name='index', type='int', optional=False, help="""
313 Index of target curve.
316 help=self.__doc__, plugin=plugin)
318 def _run(self, hooke, inqueue, outqueue, params):
319 self._playlist(hooke, params).pop(params['index'])
320 self._playlist(hooke, params).jump(params.index())
323 class ApplyCommandStack (PlaylistCommand):
324 """Apply a :class:`~hooke.command_stack.CommandStack` to each
327 TODO: discuss `evaluate`.
329 def __init__(self, plugin):
330 super(ApplyCommandStack, self).__init__(
331 name='apply command stack',
333 Argument(name='commands', type='command stack', optional=False,
335 Command stack to apply to each curve.
337 Argument(name='evaluate', type='bool', default=False,
339 Evaluate the applied command stack immediately.
342 help=self.__doc__, plugin=plugin)
344 def _run(self, hooke, inqueue, outqueue, params):
345 if len(params['commands']) == 0:
347 p = self._playlist(hooke, params)
348 if params['evaluate'] == True:
349 for curve in p.items():
350 for command in params['commands']:
351 curve.command_stack.execute_command(hooke, command)
352 curve.command_stack.append(command)
355 curve.command_stack.extend(params['commands'])
356 curve.unload() # force command stack execution on next access.
359 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
360 """Create a subset playlist via a selection function.
362 Removing lots of curves one at a time can be tedious. With this
363 command you can use a function `filter` to select the curves you
368 There are issues with pickling functions bound to class
369 attributes, because the pickle module doesn't know where those
370 functions were originally defined (where it should point the
371 loader). Because of this, subclasses with hard-coded filter
372 functions are encouraged to define their filter function as a
373 method of their subclass. See, for example,
374 :meth:`NoteFilterCommand.filter`.
376 def __init__(self, plugin, name='filter playlist'):
377 super(FilterCommand, self).__init__(
378 name=name, help=self.__doc__, plugin=plugin)
379 if not hasattr(self, 'filter'):
380 self.arguments.append(
381 Argument(name='filter', type='function', optional=False,
383 Function returning `True` for "good" curves.
384 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
387 def _run(self, hooke, inqueue, outqueue, params):
388 if not hasattr(self, 'filter'):
389 filter_fn = params['filter']
391 filter_fn = self.filter
392 p = self._playlist(hooke, params).filter(filter_fn,
393 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
394 p.name = params['name']
395 if hasattr(p, 'path') and p.path != None:
396 p.set_path(os.path.join(os.path.dirname(p.path), p.name))
397 self._set_playlist(hooke, params, p)
401 class NoteFilterCommand (FilterCommand):
402 """Create a subset playlist of curves with `.info['note'] != None`.
404 def __init__(self, plugin):
405 super(NoteFilterCommand, self).__init__(
406 plugin, name='note filter playlist')
408 def filter(self, curve, hooke, inqueue, outqueue, params):
409 return 'note' in curve.info and curve.info['note'] != None