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',
216 Argument(name='output', type='file',
218 File name for the output playlist. Defaults to overwriting the input
219 playlist. If the playlist does not have an input file (e.g. it was
220 created from scratch with 'new playlist'), this option is required.
223 help=self.__doc__, plugin=plugin)
225 def _run(self, hooke, inqueue, outqueue, params):
226 self._playlist(hooke, params).save(params['output'])
229 class LoadCommand (PlaylistAddingCommand):
232 def __init__(self, plugin):
233 super(LoadCommand, self).__init__(
234 name='load playlist',
236 Argument(name='input', type='file', optional=False,
238 File name for the input playlist.
240 Argument(name='drivers', type='driver', optional=True,
241 count=-1, callback=all_drivers_callback,
243 Drivers for loading curves.
246 help=self.__doc__, plugin=plugin)
248 def _run(self, hooke, inqueue, outqueue, params):
249 p = FilePlaylist(drivers=params['drivers'], path=params['input'])
251 self._set_playlist(hooke, params, p)
255 class AddCommand (PlaylistCommand):
256 """Add a curve to a playlist.
258 def __init__(self, plugin):
259 super(AddCommand, self).__init__(
260 name='add curve to playlist',
262 Argument(name='input', type='file', optional=False,
264 File name for the input :class:`hooke.curve.Curve`.
266 Argument(name='info', type='dict', optional=True,
268 Additional information for the input :class:`hooke.curve.Curve`.
271 help=self.__doc__, plugin=plugin)
273 def _run(self, hooke, inqueue, outqueue, params):
274 self._playlist(hooke, params).append_curve_by_path(
275 params['input'], params['info'], hooke=hooke)
278 class AddGlobCommand (PlaylistCommand):
279 """Add curves to a playlist with file globbing.
281 Adding lots of files one at a time can be tedious. With this
282 command you can use globs (`data/curves/*.dat`) to add curves
283 for all matching files at once.
285 def __init__(self, plugin):
286 super(AddGlobCommand, self).__init__(
287 name='glob curves to playlist',
289 Argument(name='input', type='string', optional=False,
291 File name glob for the input :class:`hooke.curve.Curve`.
293 Argument(name='info', type='dict', optional=True,
295 Additional information for the input :class:`hooke.curve.Curve`.
298 help=self.__doc__, plugin=plugin)
300 def _run(self, hooke, inqueue, outqueue, params):
301 for path in sorted(glob.glob(params['input'])):
302 self._playlist(hooke, params).append_curve_by_path(
303 path, params['info'], hooke=hooke)
306 class RemoveCommand (PlaylistCommand):
307 """Remove a curve from a playlist.
309 def __init__(self, plugin):
310 super(RemoveCommand, self).__init__(
311 name='remove curve from playlist',
313 Argument(name='index', type='int', optional=False, help="""
314 Index of target curve.
317 help=self.__doc__, plugin=plugin)
319 def _run(self, hooke, inqueue, outqueue, params):
320 self._playlist(hooke, params).pop(params['index'])
321 self._playlist(hooke, params).jump(params.index())
324 class ApplyCommandStack (PlaylistCommand):
325 """Apply a :class:`~hooke.command_stack.CommandStack` to each
328 TODO: discuss `evaluate`.
330 def __init__(self, plugin):
331 super(ApplyCommandStack, self).__init__(
332 name='apply command stack',
334 Argument(name='commands', type='command stack', optional=False,
336 Command stack to apply to each curve.
338 Argument(name='evaluate', type='bool', default=False,
340 Evaluate the applied command stack immediately.
343 help=self.__doc__, plugin=plugin)
345 def _run(self, hooke, inqueue, outqueue, params):
346 if len(params['commands']) == 0:
348 p = self._playlist(hooke, params)
349 if params['evaluate'] == True:
350 for curve in p.items():
351 for command in params['commands']:
352 curve.command_stack.execute_command(hooke, command)
353 curve.command_stack.append(command)
356 curve.command_stack.extend(params['commands'])
357 curve.unload() # force command stack execution on next access.
360 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
361 """Create a subset playlist via a selection function.
363 Removing lots of curves one at a time can be tedious. With this
364 command you can use a function `filter` to select the curves you
369 There are issues with pickling functions bound to class
370 attributes, because the pickle module doesn't know where those
371 functions were originally defined (where it should point the
372 loader). Because of this, subclasses with hard-coded filter
373 functions are encouraged to define their filter function as a
374 method of their subclass. See, for example,
375 :meth:`NoteFilterCommand.filter`.
377 def __init__(self, plugin, name='filter playlist'):
378 super(FilterCommand, self).__init__(
379 name=name, help=self.__doc__, plugin=plugin)
380 if not hasattr(self, 'filter'):
381 self.arguments.append(
382 Argument(name='filter', type='function', optional=False,
384 Function returning `True` for "good" curves.
385 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
388 def _run(self, hooke, inqueue, outqueue, params):
389 if not hasattr(self, 'filter'):
390 filter_fn = params['filter']
392 filter_fn = self.filter
393 p = self._playlist(hooke, params).filter(filter_fn,
394 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
395 p.name = params['name']
396 if hasattr(p, 'path') and p.path != None:
397 p.set_path(os.path.join(os.path.dirname(p.path), p.name))
398 self._set_playlist(hooke, params, p)
402 class NoteFilterCommand (FilterCommand):
403 """Create a subset playlist of curves with `.info['note'] != None`.
405 def __init__(self, plugin):
406 super(NoteFilterCommand, self).__init__(
407 plugin, name='note filter playlist')
409 def filter(self, curve, hooke, inqueue, outqueue, params):
410 return 'note' in curve.info and curve.info['note'] != None