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 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 = load(path=params['input'], drivers=params['drivers'], hooke=hooke)
254 self._set_playlist(hooke, params, p)
258 class AddCommand (PlaylistCommand):
259 """Add a curve to a playlist.
261 def __init__(self, plugin):
262 super(AddCommand, self).__init__(
263 name='add curve to playlist',
265 Argument(name='input', type='file', optional=False,
267 File name for the input :class:`hooke.curve.Curve`.
269 Argument(name='info', type='dict', optional=True,
271 Additional information for the input :class:`hooke.curve.Curve`.
274 help=self.__doc__, plugin=plugin)
276 def _run(self, hooke, inqueue, outqueue, params):
277 self._playlist(hooke, params).append_curve_by_path(
278 params['input'], params['info'], hooke=hooke)
281 class AddGlobCommand (PlaylistCommand):
282 """Add curves to a playlist with file globbing.
284 Adding lots of files one at a time can be tedious. With this
285 command you can use globs (`data/curves/*.dat`) to add curves
286 for all matching files at once.
288 def __init__(self, plugin):
289 super(AddGlobCommand, self).__init__(
290 name='glob curves to playlist',
292 Argument(name='input', type='string', optional=False,
294 File name glob for the input :class:`hooke.curve.Curve`.
296 Argument(name='info', type='dict', optional=True,
298 Additional information for the input :class:`hooke.curve.Curve`.
301 help=self.__doc__, plugin=plugin)
303 def _run(self, hooke, inqueue, outqueue, params):
304 p = self._playlist(hooke, params)
305 for path in sorted(glob.glob(params['input'])):
307 p.append_curve_by_path(path, params['info'], hooke=hooke)
308 except NotRecognized, e:
309 log = logging.getLogger('hooke')
314 class RemoveCommand (PlaylistCommand):
315 """Remove a curve from a playlist.
317 def __init__(self, plugin):
318 super(RemoveCommand, self).__init__(
319 name='remove curve from playlist',
321 Argument(name='index', type='int', optional=False, help="""
322 Index of target curve.
325 help=self.__doc__, plugin=plugin)
327 def _run(self, hooke, inqueue, outqueue, params):
328 self._playlist(hooke, params).pop(params['index'])
329 self._playlist(hooke, params).jump(params.index())
332 class ApplyCommand (PlaylistCommand):
333 """Apply a :class:`~hooke.command_stack.CommandStack` to each
336 TODO: discuss `evaluate`.
338 def __init__(self, plugin):
339 super(ApplyCommand, self).__init__(
340 name='apply command stack to playlist',
342 Argument(name='commands', type='command stack',
344 Command stack to apply to each curve. Defaults to the `command_stack`
345 plugin's current stack.
347 Argument(name='evaluate', type='bool', default=False,
349 Evaluate the applied command stack immediately.
352 help=self.__doc__, plugin=plugin)
354 def _run(self, hooke, inqueue, outqueue, params):
355 params = self.__setup_params(hooke=hooke, params=params)
356 p = self._playlist(hooke, params)
357 if params['evaluate'] == True:
358 exec_cmd = hooke.command_by_name['execute command stack']
359 for curve in p.items():
360 hooke.run_command(exec_cmd.name,
361 {'commands':params['commands'],
365 for command in params['commands']:
366 curve.command_stack.append(command)
367 curve.set_hooke(hooke)
370 def __setup_params(self, hooke, params):
371 if params['commands'] == None:
372 cstack_plugin = [p for p in hooke.plugins
373 if p.name == 'command_stack'][0]
374 params['commands'] = cstack_plugin.command_stack
378 class FilterCommand (PlaylistAddingCommand, PlaylistCommand):
379 """Create a subset playlist via a selection function.
381 Removing lots of curves one at a time can be tedious. With this
382 command you can use a function `filter` to select the curves you
387 There are issues with pickling functions bound to class
388 attributes, because the pickle module doesn't know where those
389 functions were originally defined (where it should point the
390 loader). Because of this, subclasses with hard-coded filter
391 functions are encouraged to define their filter function as a
392 method of their subclass. See, for example,
393 :meth:`NoteFilterCommand.filter`.
395 def __init__(self, plugin, name='filter playlist', load_curves=True):
396 super(FilterCommand, self).__init__(
397 name=name, help=self.__doc__, plugin=plugin)
398 self._load_curves = load_curves
399 if not hasattr(self, 'filter'):
400 self.arguments.append(
401 Argument(name='filter', type='function', optional=False,
403 Function returning `True` for "good" curves.
404 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
407 def _run(self, hooke, inqueue, outqueue, params):
408 if not hasattr(self, 'filter'):
409 filter_fn = params['filter']
411 filter_fn = self.filter
412 p = self._playlist(hooke, params).filter(
413 filter_fn, load_curves=self._load_curves,
414 hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
415 self._set_playlist(hooke, params, p)
416 if hasattr(p, 'path') and p.path != None:
417 p.set_path(os.path.join(os.path.dirname(p.path), p.name))