1 # Copyright (C) 2010-2012 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 under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with Hooke. If not, see <http://www.gnu.org/licenses/>.
18 """The ``playlist`` module provides :class:`PlaylistPlugin` and
19 several associated :class:`hooke.command.Command`\s for handling
20 :mod:`hooke.playlist` classes.
27 from ..command import Command, Argument, Failure
28 from ..curve import NotRecognized
29 from ..playlist import load
30 from ..util.itertools import reverse_enumerate
34 class PlaylistPlugin (Builtin):
36 super(PlaylistPlugin, self).__init__(name='playlist')
38 NextCommand(self), PreviousCommand(self), JumpCommand(self),
39 GetCommand(self), IndexCommand(self), CurveListCommand(self),
40 NameCommand(self), SaveCommand(self), LoadCommand(self),
41 AddCommand(self), AddGlobCommand(self),
42 RemoveCommand(self), ApplyCommand(self),
47 # Define common or complicated arguments
49 def current_playlist_callback(hooke, command, argument, value):
52 playlist = hooke.playlists.current()
54 raise Failure('No playlists loaded')
57 PlaylistArgument = Argument(
58 name='playlist', type='playlist', callback=current_playlist_callback,
60 :class:`hooke.playlist.Playlist` to act on. Defaults to the current
64 def playlist_name_callback(hooke, command, argument, value):
68 names = [p.name for p in hooke.playlists]
70 name = 'playlist-%d' % i
75 PlaylistNameArgument = Argument(
76 name='output playlist', type='string', optional=True,
77 callback=playlist_name_callback,
79 Name of the new playlist (defaults to an auto-generated name).
82 def all_drivers_callback(hooke, command, argument, value):
86 # Define useful command subclasses
88 class PlaylistCommand (Command):
89 """A :class:`~hooke.command.Command` operating on a
90 :class:`~hooke.playlist.Playlist`.
92 def __init__(self, **kwargs):
93 if 'arguments' in kwargs:
94 kwargs['arguments'].insert(0, PlaylistArgument)
96 kwargs['arguments'] = [PlaylistArgument]
97 super(PlaylistCommand, self).__init__(**kwargs)
99 def _playlist(self, hooke, params):
100 """Get the selected playlist.
104 `hooke` is intended to attach the selected playlist to the
105 local hooke instance; the returned playlist should not be
106 effected by the state of `hooke`.
108 # HACK? rely on params['playlist'] being bound to the local
109 # hooke (i.e. not a copy, as you would get by passing a
110 # playlist through the queue). Ugh. Stupid queues. As an
111 # alternative, we could pass lookup information through the
113 return params['playlist']
116 class PlaylistAddingCommand (Command):
117 """A :class:`~hooke.command.Command` adding a
118 :class:`~hooke.playlist.Playlist`.
120 def __init__(self, **kwargs):
121 if 'arguments' in kwargs:
122 kwargs['arguments'].insert(0, PlaylistNameArgument)
124 kwargs['arguments'] = [PlaylistNameArgument]
125 super(PlaylistAddingCommand, self).__init__(**kwargs)
127 def _set_playlist(self, hooke, params, playlist):
128 """Attach a new playlist.
130 playlist_names = [p.name for p in hooke.playlists]
131 if playlist.name in playlist_names or playlist.name == None:
132 playlist.name = params['output playlist'] # HACK: override input name. How to tell if it is callback-generated?
133 hooke.playlists.append(playlist)
138 class NextCommand (PlaylistCommand):
139 """Move playlist to the next curve.
141 def __init__(self, plugin):
142 super(NextCommand, self).__init__(
143 name='next curve', help=self.__doc__, plugin=plugin)
145 def _run(self, hooke, inqueue, outqueue, params):
146 self._playlist(hooke, params).next()
149 class PreviousCommand (PlaylistCommand):
150 """Move playlist to the previous curve.
152 def __init__(self, plugin):
153 super(PreviousCommand, self).__init__(
154 name='previous curve', help=self.__doc__, plugin=plugin)
156 def _run(self, hooke, inqueue, outqueue, params):
157 self._playlist(hooke, params).previous()
160 class JumpCommand (PlaylistCommand):
161 """Move playlist to a given curve.
163 def __init__(self, plugin):
164 super(JumpCommand, self).__init__(
165 name='jump to curve',
167 Argument(name='index', type='int', optional=False, help="""
168 Index of target curve.
171 help=self.__doc__, plugin=plugin)
173 def _run(self, hooke, inqueue, outqueue, params):
174 self._playlist(hooke, params).jump(params['index'])
177 class IndexCommand (PlaylistCommand):
178 """Print the index of the current curve.
180 The first curve has index 0.
182 def __init__(self, plugin):
183 super(IndexCommand, self).__init__(
184 name='curve index', help=self.__doc__, plugin=plugin)
186 def _run(self, hooke, inqueue, outqueue, params):
187 outqueue.put(self._playlist(hooke, params).index())
190 class GetCommand (PlaylistCommand):
191 """Return a :class:`hooke.playlist.Playlist`.
193 def __init__(self, plugin):
194 super(GetCommand, self).__init__(
195 name='get playlist', help=self.__doc__, plugin=plugin)
197 def _run(self, hooke, inqueue, outqueue, params):
198 outqueue.put(self._playlist(hooke, params))
201 class CurveListCommand (PlaylistCommand):
202 """Get the curves in a playlist.
204 def __init__(self, plugin):
205 super(CurveListCommand, self).__init__(
206 name='playlist curves', help=self.__doc__, plugin=plugin)
208 def _run(self, hooke, inqueue, outqueue, params):
209 outqueue.put(list(self._playlist(hooke, params)))
212 class NameCommand (PlaylistCommand):
213 """(Re)name a playlist.
215 def __init__(self, plugin):
216 super(NameCommand, self).__init__(
217 name='name playlist',
219 Argument(name='name', type='string', optional=False,
221 Name for the playlist.
224 help=self.__doc__, plugin=plugin)
226 def _run(self, hooke, inqueue, outqueue, params):
227 p = self._playlist(hooke, params)
228 p.name = params['name']
232 class SaveCommand (PlaylistCommand):
235 def __init__(self, plugin):
236 super(SaveCommand, self).__init__(
237 name='save playlist',
239 Argument(name='output', type='file',
241 File name for the output playlist. Defaults to overwriting the input
242 playlist. If the playlist does not have an input file (e.g. it was
243 created from scratch with 'new playlist'), this option is required.
246 help=self.__doc__, plugin=plugin)
248 def _run(self, hooke, inqueue, outqueue, params):
249 self._playlist(hooke, params).save(params['output'])
252 class LoadCommand (PlaylistAddingCommand):
255 def __init__(self, plugin):
256 super(LoadCommand, self).__init__(
257 name='load playlist',
259 Argument(name='input', type='file', optional=False,
261 File name for the input playlist.
263 Argument(name='drivers', type='driver', optional=True,
264 count=-1, callback=all_drivers_callback,
266 Drivers for loading curves.
269 help=self.__doc__, plugin=plugin)
271 def _run(self, hooke, inqueue, outqueue, params):
272 p = load(path=params['input'], drivers=params['drivers'], hooke=hooke)
273 self._set_playlist(hooke, params, p)
277 class AddCommand (PlaylistCommand):
278 """Add a curve to a playlist.
280 def __init__(self, plugin):
281 super(AddCommand, self).__init__(
282 name='add curve to playlist',
284 Argument(name='input', type='file', optional=False,
286 File name for the input :class:`hooke.curve.Curve`.
288 Argument(name='info', type='dict', optional=True,
290 Additional information for the input :class:`hooke.curve.Curve`.
293 help=self.__doc__, plugin=plugin)
295 def _run(self, hooke, inqueue, outqueue, params):
296 self._playlist(hooke, params).append_curve_by_path(
297 params['input'], params['info'], hooke=hooke)
300 class AddGlobCommand (PlaylistCommand):
301 """Add curves to a playlist with file globbing.
303 Adding lots of files one at a time can be tedious. With this
304 command you can use globs (`data/curves/*.dat`) to add curves
305 for all matching files at once.
307 def __init__(self, plugin):
308 super(AddGlobCommand, self).__init__(
309 name='glob curves to playlist',
311 Argument(name='input', type='string', optional=False,
313 File name glob for the input :class:`hooke.curve.Curve`.
315 Argument(name='info', type='dict', optional=True,
317 Additional information for the input :class:`hooke.curve.Curve`.
320 help=self.__doc__, plugin=plugin)
322 def _run(self, hooke, inqueue, outqueue, params):
323 p = self._playlist(hooke, params)
324 for path in sorted(glob.glob(os.path.expanduser(params['input']))):
326 p.append_curve_by_path(path, params['info'], hooke=hooke)
327 except NotRecognized, e:
328 log = logging.getLogger('hooke')
333 class RemoveCommand (PlaylistCommand):
334 """Remove a curve from a playlist.
336 def __init__(self, plugin):
337 super(RemoveCommand, self).__init__(
338 name='remove curve from playlist',
340 Argument(name='index', type='int', optional=True, help="""
341 Index of target curve.
344 help=self.__doc__, plugin=plugin)
346 def _run(self, hooke, inqueue, outqueue, params):
347 playlist = self._playlist(hooke, params)
348 if params['index'] is None:
349 params['index'] = playlist.index()
350 curve = playlist.pop(params['index'])
351 playlist.jump(playlist.index())
355 class ApplyCommand (PlaylistCommand):
356 """Apply a :class:`~hooke.command_stack.CommandStack` to each
359 TODO: discuss `evaluate`.
361 def __init__(self, plugin):
362 super(ApplyCommand, self).__init__(
363 name='apply command stack to playlist',
365 Argument(name='commands', type='command stack',
367 Command stack to apply to each curve. Defaults to the `command_stack`
368 plugin's current stack.
370 Argument(name='evaluate', type='bool', default=False,
372 Evaluate the applied command stack immediately.
375 help=self.__doc__, plugin=plugin)
377 def _run(self, hooke, inqueue, outqueue, params):
378 params = self._setup_params(hooke=hooke, params=params)
379 p = self._playlist(hooke, params)
380 if params['evaluate'] == True:
381 exec_cmd = hooke.command_by_name['execute command stack']
382 for curve in p.items():
383 hooke.run_command(exec_cmd.name,
384 {'commands':params['commands'],
388 for command in params['commands']:
389 curve.command_stack.append(command)
390 curve.set_hooke(hooke)
393 def _setup_params(self, hooke, params):
394 if params['commands'] == None:
395 cstack_plugin = [p for p in hooke.plugins
396 if p.name == 'command_stack'][0]
397 params['commands'] = cstack_plugin.command_stack
401 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
402 """Create a subset playlist via a selection function.
404 Removing lots of curves one at a time can be tedious. With this
405 command you can use a function `filter` to select the curves you
410 There are issues with pickling functions bound to class
411 attributes, because the pickle module doesn't know where those
412 functions were originally defined (where it should point the
413 loader). Because of this, subclasses with hard-coded filter
414 functions are encouraged to define their filter function as a
415 method of their subclass. See, for example,
416 :meth:`NoteFilterCommand.filter`.
418 def __init__(self, plugin, name='filter playlist', load_curves=True):
419 super(FilterCommand, self).__init__(
420 name=name, help=self.__doc__, plugin=plugin)
421 self._load_curves = load_curves
422 if not hasattr(self, 'filter'):
423 self.arguments.append(
424 Argument(name='filter', type='function', optional=False,
426 Function returning `True` for "good" curves.
427 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
430 def _run(self, hooke, inqueue, outqueue, params):
431 if not hasattr(self, 'filter'):
432 filter_fn = params['filter']
434 filter_fn = self.filter
435 p = self._playlist(hooke, params).filter(
436 filter_fn, load_curves=self._load_curves,
437 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
438 self._set_playlist(hooke, params, p)
439 if hasattr(p, 'path') and p.path != None:
440 p.set_path(os.path.join(os.path.dirname(p.path), p.name))