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 ..curve import NotRecognized
30 from ..playlist import load
31 from ..util.itertools import reverse_enumerate
35 class PlaylistPlugin (Builtin):
37 super(PlaylistPlugin, self).__init__(name='playlist')
39 NextCommand(self), PreviousCommand(self), JumpCommand(self),
40 GetCommand(self), IndexCommand(self), CurveListCommand(self),
41 NameCommand(self), SaveCommand(self), LoadCommand(self),
42 AddCommand(self), AddGlobCommand(self),
43 RemoveCommand(self), ApplyCommand(self),
48 # Define common or complicated arguments
50 def current_playlist_callback(hooke, command, argument, value):
53 playlist = hooke.playlists.current()
55 raise Failure('No playlists loaded')
58 PlaylistArgument = Argument(
59 name='playlist', type='playlist', callback=current_playlist_callback,
61 :class:`hooke.playlist.Playlist` to act on. Defaults to the current
65 def playlist_name_callback(hooke, command, argument, value):
69 names = [p.name for p in hooke.playlists]
71 name = 'playlist-%d' % i
76 PlaylistNameArgument = Argument(
77 name='output playlist', type='string', optional=True,
78 callback=playlist_name_callback,
80 Name of the new playlist (defaults to an auto-generated name).
83 def all_drivers_callback(hooke, command, argument, value):
87 # Define useful command subclasses
89 class PlaylistCommand (Command):
90 """A :class:`~hooke.command.Command` operating on a
91 :class:`~hooke.playlist.Playlist`.
93 def __init__(self, **kwargs):
94 if 'arguments' in kwargs:
95 kwargs['arguments'].insert(0, PlaylistArgument)
97 kwargs['arguments'] = [PlaylistArgument]
98 super(PlaylistCommand, self).__init__(**kwargs)
100 def _playlist(self, hooke, params):
101 """Get the selected playlist.
105 `hooke` is intended to attach the selected playlist to the
106 local hooke instance; the returned playlist should not be
107 effected by the state of `hooke`.
109 # HACK? rely on params['playlist'] being bound to the local
110 # hooke (i.e. not a copy, as you would get by passing a
111 # playlist through the queue). Ugh. Stupid queues. As an
112 # alternative, we could pass lookup information through the
114 return params['playlist']
117 class PlaylistAddingCommand (Command):
118 """A :class:`~hooke.command.Command` adding a
119 :class:`~hooke.playlist.Playlist`.
121 def __init__(self, **kwargs):
122 if 'arguments' in kwargs:
123 kwargs['arguments'].insert(0, PlaylistNameArgument)
125 kwargs['arguments'] = [PlaylistNameArgument]
126 super(PlaylistAddingCommand, self).__init__(**kwargs)
128 def _set_playlist(self, hooke, params, playlist):
129 """Attach a new playlist.
131 playlist_names = [p.name for p in hooke.playlists]
132 if playlist.name in playlist_names or playlist.name == None:
133 playlist.name = params['output playlist'] # HACK: override input name. How to tell if it is callback-generated?
134 hooke.playlists.append(playlist)
139 class NextCommand (PlaylistCommand):
140 """Move playlist to the next curve.
142 def __init__(self, plugin):
143 super(NextCommand, self).__init__(
144 name='next curve', help=self.__doc__, plugin=plugin)
146 def _run(self, hooke, inqueue, outqueue, params):
147 self._playlist(hooke, params).next()
150 class PreviousCommand (PlaylistCommand):
151 """Move playlist to the previous curve.
153 def __init__(self, plugin):
154 super(PreviousCommand, self).__init__(
155 name='previous curve', help=self.__doc__, plugin=plugin)
157 def _run(self, hooke, inqueue, outqueue, params):
158 self._playlist(hooke, params).previous()
161 class JumpCommand (PlaylistCommand):
162 """Move playlist to a given curve.
164 def __init__(self, plugin):
165 super(JumpCommand, self).__init__(
166 name='jump to curve',
168 Argument(name='index', type='int', optional=False, help="""
169 Index of target curve.
172 help=self.__doc__, plugin=plugin)
174 def _run(self, hooke, inqueue, outqueue, params):
175 self._playlist(hooke, params).jump(params['index'])
178 class IndexCommand (PlaylistCommand):
179 """Print the index of the current curve.
181 The first curve has index 0.
183 def __init__(self, plugin):
184 super(IndexCommand, self).__init__(
185 name='curve index', help=self.__doc__, plugin=plugin)
187 def _run(self, hooke, inqueue, outqueue, params):
188 outqueue.put(self._playlist(hooke, params).index())
191 class GetCommand (PlaylistCommand):
192 """Return a :class:`hooke.playlist.Playlist`.
194 def __init__(self, plugin):
195 super(GetCommand, self).__init__(
196 name='get playlist', help=self.__doc__, plugin=plugin)
198 def _run(self, hooke, inqueue, outqueue, params):
199 outqueue.put(self._playlist(hooke, params))
202 class CurveListCommand (PlaylistCommand):
203 """Get the curves in a playlist.
205 def __init__(self, plugin):
206 super(CurveListCommand, self).__init__(
207 name='playlist curves', help=self.__doc__, plugin=plugin)
209 def _run(self, hooke, inqueue, outqueue, params):
210 outqueue.put(list(self._playlist(hooke, params)))
213 class NameCommand (PlaylistCommand):
214 """(Re)name a playlist.
216 def __init__(self, plugin):
217 super(NameCommand, self).__init__(
218 name='name playlist',
220 Argument(name='name', type='string', optional=False,
222 Name for the playlist.
225 help=self.__doc__, plugin=plugin)
227 def _run(self, hooke, inqueue, outqueue, params):
228 p = self._playlist(hooke, params)
229 p.name = params['name']
233 class SaveCommand (PlaylistCommand):
236 def __init__(self, plugin):
237 super(SaveCommand, self).__init__(
238 name='save playlist',
240 Argument(name='output', type='file',
242 File name for the output playlist. Defaults to overwriting the input
243 playlist. If the playlist does not have an input file (e.g. it was
244 created from scratch with 'new playlist'), this option is required.
247 help=self.__doc__, plugin=plugin)
249 def _run(self, hooke, inqueue, outqueue, params):
250 self._playlist(hooke, params).save(params['output'])
253 class LoadCommand (PlaylistAddingCommand):
256 def __init__(self, plugin):
257 super(LoadCommand, self).__init__(
258 name='load playlist',
260 Argument(name='input', type='file', optional=False,
262 File name for the input playlist.
264 Argument(name='drivers', type='driver', optional=True,
265 count=-1, callback=all_drivers_callback,
267 Drivers for loading curves.
270 help=self.__doc__, plugin=plugin)
272 def _run(self, hooke, inqueue, outqueue, params):
273 p = load(path=params['input'], drivers=params['drivers'], hooke=hooke)
274 self._set_playlist(hooke, params, p)
278 class AddCommand (PlaylistCommand):
279 """Add a curve to a playlist.
281 def __init__(self, plugin):
282 super(AddCommand, self).__init__(
283 name='add curve to playlist',
285 Argument(name='input', type='file', optional=False,
287 File name for the input :class:`hooke.curve.Curve`.
289 Argument(name='info', type='dict', optional=True,
291 Additional information for the input :class:`hooke.curve.Curve`.
294 help=self.__doc__, plugin=plugin)
296 def _run(self, hooke, inqueue, outqueue, params):
297 self._playlist(hooke, params).append_curve_by_path(
298 params['input'], params['info'], hooke=hooke)
301 class AddGlobCommand (PlaylistCommand):
302 """Add curves to a playlist with file globbing.
304 Adding lots of files one at a time can be tedious. With this
305 command you can use globs (`data/curves/*.dat`) to add curves
306 for all matching files at once.
308 def __init__(self, plugin):
309 super(AddGlobCommand, self).__init__(
310 name='glob curves to playlist',
312 Argument(name='input', type='string', optional=False,
314 File name glob for the input :class:`hooke.curve.Curve`.
316 Argument(name='info', type='dict', optional=True,
318 Additional information for the input :class:`hooke.curve.Curve`.
321 help=self.__doc__, plugin=plugin)
323 def _run(self, hooke, inqueue, outqueue, params):
324 p = self._playlist(hooke, params)
325 for path in sorted(glob.glob(params['input'])):
327 p.append_curve_by_path(path, params['info'], hooke=hooke)
328 except NotRecognized, e:
329 log = logging.getLogger('hooke')
334 class RemoveCommand (PlaylistCommand):
335 """Remove a curve from a playlist.
337 def __init__(self, plugin):
338 super(RemoveCommand, self).__init__(
339 name='remove curve from playlist',
341 Argument(name='index', type='int', optional=False, help="""
342 Index of target curve.
345 help=self.__doc__, plugin=plugin)
347 def _run(self, hooke, inqueue, outqueue, params):
348 self._playlist(hooke, params).pop(params['index'])
349 self._playlist(hooke, params).jump(params.index())
352 class ApplyCommand (PlaylistCommand):
353 """Apply a :class:`~hooke.command_stack.CommandStack` to each
356 TODO: discuss `evaluate`.
358 def __init__(self, plugin):
359 super(ApplyCommand, self).__init__(
360 name='apply command stack to playlist',
362 Argument(name='commands', type='command stack',
364 Command stack to apply to each curve. Defaults to the `command_stack`
365 plugin's current stack.
367 Argument(name='evaluate', type='bool', default=False,
369 Evaluate the applied command stack immediately.
372 help=self.__doc__, plugin=plugin)
374 def _run(self, hooke, inqueue, outqueue, params):
375 params = self._setup_params(hooke=hooke, params=params)
376 p = self._playlist(hooke, params)
377 if params['evaluate'] == True:
378 exec_cmd = hooke.command_by_name['execute command stack']
379 for curve in p.items():
380 hooke.run_command(exec_cmd.name,
381 {'commands':params['commands'],
385 for command in params['commands']:
386 curve.command_stack.append(command)
387 curve.set_hooke(hooke)
390 def _setup_params(self, hooke, params):
391 if params['commands'] == None:
392 cstack_plugin = [p for p in hooke.plugins
393 if p.name == 'command_stack'][0]
394 params['commands'] = cstack_plugin.command_stack
398 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
399 """Create a subset playlist via a selection function.
401 Removing lots of curves one at a time can be tedious. With this
402 command you can use a function `filter` to select the curves you
407 There are issues with pickling functions bound to class
408 attributes, because the pickle module doesn't know where those
409 functions were originally defined (where it should point the
410 loader). Because of this, subclasses with hard-coded filter
411 functions are encouraged to define their filter function as a
412 method of their subclass. See, for example,
413 :meth:`NoteFilterCommand.filter`.
415 def __init__(self, plugin, name='filter playlist', load_curves=True):
416 super(FilterCommand, self).__init__(
417 name=name, help=self.__doc__, plugin=plugin)
418 self._load_curves = load_curves
419 if not hasattr(self, 'filter'):
420 self.arguments.append(
421 Argument(name='filter', type='function', optional=False,
423 Function returning `True` for "good" curves.
424 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
427 def _run(self, hooke, inqueue, outqueue, params):
428 if not hasattr(self, 'filter'):
429 filter_fn = params['filter']
431 filter_fn = self.filter
432 p = self._playlist(hooke, params).filter(
433 filter_fn, load_curves=self._load_curves,
434 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
435 self._set_playlist(hooke, params, p)
436 if hasattr(p, 'path') and p.path != None:
437 p.set_path(os.path.join(os.path.dirname(p.path), p.name))