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 FilePlaylist
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 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 SaveCommand (PlaylistCommand):
216 def __init__(self, plugin):
217 super(SaveCommand, self).__init__(
218 name='save playlist',
220 Argument(name='output', type='file',
222 File name for the output playlist. Defaults to overwriting the input
223 playlist. If the playlist does not have an input file (e.g. it was
224 created from scratch with 'new playlist'), this option is required.
227 help=self.__doc__, plugin=plugin)
229 def _run(self, hooke, inqueue, outqueue, params):
230 self._playlist(hooke, params).save(params['output'])
233 class LoadCommand (PlaylistAddingCommand):
236 def __init__(self, plugin):
237 super(LoadCommand, self).__init__(
238 name='load playlist',
240 Argument(name='input', type='file', optional=False,
242 File name for the input playlist.
244 Argument(name='drivers', type='driver', optional=True,
245 count=-1, callback=all_drivers_callback,
247 Drivers for loading curves.
250 help=self.__doc__, plugin=plugin)
252 def _run(self, hooke, inqueue, outqueue, params):
253 p = FilePlaylist(drivers=params['drivers'], path=params['input'])
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 ApplyCommand (PlaylistCommand):
334 """Apply a :class:`~hooke.command_stack.CommandStack` to each
337 TODO: discuss `evaluate`.
339 def __init__(self, plugin):
340 super(ApplyCommand, self).__init__(
341 name='apply command stack to playlist',
343 Argument(name='commands', type='command stack',
345 Command stack to apply to each curve. Defaults to the `command_stack`
346 plugin's current stack.
348 Argument(name='evaluate', type='bool', default=False,
350 Evaluate the applied command stack immediately.
353 help=self.__doc__, plugin=plugin)
355 def _run(self, hooke, inqueue, outqueue, params):
356 params = self.__setup_params(hooke=hooke, params=params)
357 p = self._playlist(hooke, params)
358 if params['evaluate'] == True:
359 exec_cmd = hooke.command_by_name['execute command stack']
360 for curve in p.items():
361 hooke.run_command(exec_cmd.name,
362 {'commands':params['commands'],
366 for command in params['commands']:
367 curve.command_stack.append(command)
368 curve.set_hooke(hooke)
371 def __setup_params(self, hooke, params):
372 if params['commands'] == None:
373 cstack_plugin = [p for p in hooke.plugins
374 if p.name == 'command_stack'][0]
375 params['commands'] = cstack_plugin.command_stack
379 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
380 """Create a subset playlist via a selection function.
382 Removing lots of curves one at a time can be tedious. With this
383 command you can use a function `filter` to select the curves you
388 There are issues with pickling functions bound to class
389 attributes, because the pickle module doesn't know where those
390 functions were originally defined (where it should point the
391 loader). Because of this, subclasses with hard-coded filter
392 functions are encouraged to define their filter function as a
393 method of their subclass. See, for example,
394 :meth:`NoteFilterCommand.filter`.
396 def __init__(self, plugin, name='filter playlist', load_curves=True):
397 super(FilterCommand, self).__init__(
398 name=name, help=self.__doc__, plugin=plugin)
399 self._load_curves = load_curves
400 if not hasattr(self, 'filter'):
401 self.arguments.append(
402 Argument(name='filter', type='function', optional=False,
404 Function returning `True` for "good" curves.
405 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
408 def _run(self, hooke, inqueue, outqueue, params):
409 if not hasattr(self, 'filter'):
410 filter_fn = params['filter']
412 filter_fn = self.filter
413 p = self._playlist(hooke, params).filter(
414 filter_fn, load_curves=self._load_curves,
415 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
416 self._set_playlist(hooke, params, p)
417 if hasattr(p, 'path') and p.path != None:
418 p.set_path(os.path.join(os.path.dirname(p.path), p.name))