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(
251 os.path.expanduser(params['output']))
254 class LoadCommand (PlaylistAddingCommand):
257 def __init__(self, plugin):
258 super(LoadCommand, self).__init__(
259 name='load playlist',
261 Argument(name='input', type='file', optional=False,
263 File name for the input playlist.
265 Argument(name='drivers', type='driver', optional=True,
266 count=-1, callback=all_drivers_callback,
268 Drivers for loading curves.
271 help=self.__doc__, plugin=plugin)
273 def _run(self, hooke, inqueue, outqueue, params):
274 p = load(os.path.expanduser(path=params['input']),
275 drivers=params['drivers'], hooke=hooke)
276 self._set_playlist(hooke, params, p)
280 class AddCommand (PlaylistCommand):
281 """Add a curve to a playlist.
283 def __init__(self, plugin):
284 super(AddCommand, self).__init__(
285 name='add curve to playlist',
287 Argument(name='input', type='file', optional=False,
289 File name for the input :class:`hooke.curve.Curve`.
291 Argument(name='info', type='dict', optional=True,
293 Additional information for the input :class:`hooke.curve.Curve`.
296 help=self.__doc__, plugin=plugin)
298 def _run(self, hooke, inqueue, outqueue, params):
299 self._playlist(hooke, params).append_curve_by_path(
300 os.path.expanduser(params['input']), params['info'], hooke=hooke)
303 class AddGlobCommand (PlaylistCommand):
304 """Add curves to a playlist with file globbing.
306 Adding lots of files one at a time can be tedious. With this
307 command you can use globs (`data/curves/*.dat`) to add curves
308 for all matching files at once.
310 def __init__(self, plugin):
311 super(AddGlobCommand, self).__init__(
312 name='glob curves to playlist',
314 Argument(name='input', type='string', optional=False,
316 File name glob for the input :class:`hooke.curve.Curve`.
318 Argument(name='info', type='dict', optional=True,
320 Additional information for the input :class:`hooke.curve.Curve`.
323 help=self.__doc__, plugin=plugin)
325 def _run(self, hooke, inqueue, outqueue, params):
326 p = self._playlist(hooke, params)
327 for path in sorted(glob.glob(params['input'])):
329 p.append_curve_by_path(path, params['info'], hooke=hooke)
330 except NotRecognized, e:
331 log = logging.getLogger('hooke')
336 class RemoveCommand (PlaylistCommand):
337 """Remove a curve from a playlist.
339 def __init__(self, plugin):
340 super(RemoveCommand, self).__init__(
341 name='remove curve from playlist',
343 Argument(name='index', type='int', optional=False, help="""
344 Index of target curve.
347 help=self.__doc__, plugin=plugin)
349 def _run(self, hooke, inqueue, outqueue, params):
350 self._playlist(hooke, params).pop(params['index'])
351 self._playlist(hooke, params).jump(params.index())
354 class ApplyCommand (PlaylistCommand):
355 """Apply a :class:`~hooke.command_stack.CommandStack` to each
358 TODO: discuss `evaluate`.
360 def __init__(self, plugin):
361 super(ApplyCommand, self).__init__(
362 name='apply command stack to playlist',
364 Argument(name='commands', type='command stack',
366 Command stack to apply to each curve. Defaults to the `command_stack`
367 plugin's current stack.
369 Argument(name='evaluate', type='bool', default=False,
371 Evaluate the applied command stack immediately.
374 help=self.__doc__, plugin=plugin)
376 def _run(self, hooke, inqueue, outqueue, params):
377 params = self._setup_params(hooke=hooke, params=params)
378 p = self._playlist(hooke, params)
379 if params['evaluate'] == True:
380 exec_cmd = hooke.command_by_name['execute command stack']
381 for curve in p.items():
382 hooke.run_command(exec_cmd.name,
383 {'commands':params['commands'],
387 for command in params['commands']:
388 curve.command_stack.append(command)
389 curve.set_hooke(hooke)
392 def _setup_params(self, hooke, params):
393 if params['commands'] == None:
394 cstack_plugin = [p for p in hooke.plugins
395 if p.name == 'command_stack'][0]
396 params['commands'] = cstack_plugin.command_stack
400 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
401 """Create a subset playlist via a selection function.
403 Removing lots of curves one at a time can be tedious. With this
404 command you can use a function `filter` to select the curves you
409 There are issues with pickling functions bound to class
410 attributes, because the pickle module doesn't know where those
411 functions were originally defined (where it should point the
412 loader). Because of this, subclasses with hard-coded filter
413 functions are encouraged to define their filter function as a
414 method of their subclass. See, for example,
415 :meth:`NoteFilterCommand.filter`.
417 def __init__(self, plugin, name='filter playlist', load_curves=True):
418 super(FilterCommand, self).__init__(
419 name=name, help=self.__doc__, plugin=plugin)
420 self._load_curves = load_curves
421 if not hasattr(self, 'filter'):
422 self.arguments.append(
423 Argument(name='filter', type='function', optional=False,
425 Function returning `True` for "good" curves.
426 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
429 def _run(self, hooke, inqueue, outqueue, params):
430 if not hasattr(self, 'filter'):
431 filter_fn = params['filter']
433 filter_fn = self.filter
434 p = self._playlist(hooke, params).filter(
435 filter_fn, load_curves=self._load_curves,
436 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
437 self._set_playlist(hooke, params, p)
438 if hasattr(p, 'path') and p.path != None:
439 p.set_path(os.path.join(os.path.dirname(p.path), p.name))