Ran update_copyright.py
[hooke.git] / hooke / plugin / playlist.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
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.
9 #
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.
14 #
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/>.
18
19 """The ``playlist`` module provides :class:`PlaylistPlugin` and
20 several associated :class:`hooke.command.Command`\s for handling
21 :mod:`hooke.playlist` classes.
22 """
23
24 import glob
25 import os.path
26
27 from ..command import Command, Argument, Failure
28 from ..playlist import FilePlaylist
29 from ..plugin import Builtin
30
31
32 class PlaylistPlugin (Builtin):
33     def __init__(self):
34         super(PlaylistPlugin, self).__init__(name='playlist')
35         self._commands = [
36             NextCommand(self), PreviousCommand(self), JumpCommand(self),
37             GetCommand(self), IndexCommand(self), CurveListCommand(self),
38             SaveCommand(self), LoadCommand(self),
39             AddCommand(self), AddGlobCommand(self),
40             RemoveCommand(self), FilterCommand(self), NoteFilterCommand(self)]
41
42
43 # Define common or complicated arguments
44
45 def current_playlist_callback(hooke, command, argument, value):
46     if value != None:
47         return value
48     playlist = hooke.playlists.current()
49     if playlist == None:
50         raise Failure('No playlists loaded')
51     return playlist
52
53 PlaylistArgument = Argument(
54     name='playlist', type='playlist', callback=current_playlist_callback,
55     help="""
56 :class:`hooke.playlist.Playlist` to act on.  Defaults to the current
57 playlist.
58 """.strip())
59
60 def playlist_name_callback(hooke, command, argument, value):
61     i = 0
62     names = [p.name for p in hooke.playlists]
63     while True:
64         name = 'playlist-%d' % i
65         if name not in names:
66             return name
67         i += 1
68
69 PlaylistNameArgument = Argument(
70     name='name', type='string', optional=True, callback=playlist_name_callback,
71     help="""
72 Name of the new playlist (defaults to an auto-generated name).
73 """.strip())
74
75 def all_drivers_callback(hooke, command, argument, value):
76     return hooke.drivers
77
78
79 # Define commands
80
81 class NextCommand (Command):
82     """Move playlist to the next curve.
83     """
84     def __init__(self, plugin):
85         super(NextCommand, self).__init__(
86             name='next curve',
87             arguments=[PlaylistArgument],
88             help=self.__doc__, plugin=plugin)
89
90     def _run(self, hooke, inqueue, outqueue, params):
91         params['playlist'].next()
92
93 class PreviousCommand (Command):
94     """Move playlist to the previous curve.
95     """
96     def __init__(self, plugin):
97         super(PreviousCommand, self).__init__(
98             name='previous curve',
99             arguments=[PlaylistArgument],
100             help=self.__doc__, plugin=plugin)
101
102     def _run(self, hooke, inqueue, outqueue, params):
103         params['playlist'].previous()
104
105 class JumpCommand (Command):
106     """Move playlist to a given curve.
107     """
108     def __init__(self, plugin):
109         super(JumpCommand, self).__init__(
110             name='jump to curve',
111             arguments=[
112                 PlaylistArgument,
113                 Argument(name='index', type='int', optional=False, help="""
114 Index of target curve.
115 """.strip()),
116                 ],
117             help=self.__doc__, plugin=plugin)
118
119     def _run(self, hooke, inqueue, outqueue, params):
120         params['playlist'].jump(int(params['index'])) # HACK, int() should be handled by ui
121
122 class IndexCommand (Command):
123     """Print the index of the current curve.
124
125     The first curve has index 0.
126     """
127     def __init__(self, plugin):
128         super(IndexCommand, self).__init__(
129             name='curve index',
130             arguments=[
131                 PlaylistArgument,
132                 ],
133             help=self.__doc__, plugin=plugin)
134
135     def _run(self, hooke, inqueue, outqueue, params):
136         outqueue.put(params['playlist']._index)
137
138 class GetCommand (Command):
139     """Return a :class:`hooke.playlist.Playlist`.
140     """
141     def __init__(self, plugin):
142         super(GetCommand, self).__init__(
143             name='get playlist',
144             arguments=[PlaylistArgument],
145             help=self.__doc__, plugin=plugin)
146
147     def _run(self, hooke, inqueue, outqueue, params):
148         outqueue.put(params['playlist'])
149
150 class CurveListCommand (Command):
151     """Get the curves in a playlist.
152     """
153     def __init__(self, plugin):
154         super(CurveListCommand, self).__init__(
155             name='playlist curves',
156             arguments=[PlaylistArgument],
157             help=self.__doc__, plugin=plugin)
158
159     def _run(self, hooke, inqueue, outqueue, params):
160         outqueue.put(list(params['playlist']))
161
162 class SaveCommand (Command):
163     """Save a playlist.
164     """
165     def __init__(self, plugin):
166         super(SaveCommand, self).__init__(
167             name='save playlist',
168             arguments=[
169                 PlaylistArgument,
170                 Argument(name='output', type='file',
171                          help="""
172 File name for the output playlist.  Defaults to overwriting the input
173 playlist.
174 """.strip()),
175                 ],
176             help=self.__doc__, plugin=plugin)
177
178     def _run(self, hooke, inqueue, outqueue, params):
179         params['playlist'].save(params['output'])
180
181 class LoadCommand (Command):
182     """Load a playlist.
183     """
184     def __init__(self, plugin):
185         super(LoadCommand, self).__init__(
186             name='load playlist',
187             arguments=[
188                 Argument(name='input', type='file', optional=False,
189                          help="""
190 File name for the input playlist.
191 """.strip()),
192                 Argument(name='drivers', type='driver', optional=True,
193                          count=-1, callback=all_drivers_callback,
194                          help="""
195 Drivers for loading curves.
196 """.strip()),
197                 ],
198             help=self.__doc__, plugin=plugin)
199
200     def _run(self, hooke, inqueue, outqueue, params):
201         p = FilePlaylist(drivers=params['drivers'], path=params['input'])
202         p.load()
203         hooke.playlists.append(p)
204         outqueue.put(p)
205
206 class AddCommand (Command):
207     """Add a curve to a playlist.
208     """
209     def __init__(self, plugin):
210         super(AddCommand, self).__init__(
211             name='add curve to playlist',
212             arguments=[
213                 PlaylistArgument,
214                 Argument(name='input', type='file', optional=False,
215                          help="""
216 File name for the input :class:`hooke.curve.Curve`.
217 """.strip()),
218                 Argument(name='info', type='dict', optional=True,
219                          help="""
220 Additional information for the input :class:`hooke.curve.Curve`.
221 """.strip()),
222                 ],
223             help=self.__doc__, plugin=plugin)
224
225     def _run(self, hooke, inqueue, outqueue, params):
226         params['playlist'].append_curve_by_path(params['input'],
227                                                 params['info'])
228
229 class AddGlobCommand (Command):
230     """Add curves to a playlist with file globbing.
231
232     Adding lots of files one at a time can be tedious.  With this
233     command you can use globs (`data/curves/*.dat`) to add curves
234     for all matching files at once.
235     """
236     def __init__(self, plugin):
237         super(AddGlobCommand, self).__init__(
238             name='glob curves to playlist',
239             arguments=[
240                 PlaylistArgument,
241                 Argument(name='input', type='string', optional=False,
242                          help="""
243 File name glob for the input :class:`hooke.curve.Curve`.
244 """.strip()),
245                 Argument(name='info', type='dict', optional=True,
246                          help="""
247 Additional information for the input :class:`hooke.curve.Curve`.
248 """.strip()),
249                 ],
250             help=self.__doc__, plugin=plugin)
251
252     def _run(self, hooke, inqueue, outqueue, params):
253         for path in sorted(glob.glob(params['input'])):
254             params['playlist'].append_curve_by_path(path, params['info'])
255
256 class RemoveCommand (Command):
257     """Remove a curve from a playlist.
258     """
259     def __init__(self, plugin):
260         super(RemoveCommand, self).__init__(
261             name='remove curve from playlist',
262             arguments=[
263                 PlaylistArgument,
264                 Argument(name='index', type='int', optional=False, help="""
265 Index of target curve.
266 """.strip()),
267                 ],
268             help=self.__doc__, plugin=plugin)
269
270     def _run(self, hooke, inqueue, outqueue, params):
271         params['playlist'].pop(params['index'])
272         params['playlist'].jump(params._index)
273
274 class FilterCommand (Command):
275     """Create a subset playlist via a selection function.
276
277     Removing lots of curves one at a time can be tedious.  With this
278     command you can use a function `filter` to select the curves you
279     wish to keep.
280
281     Notes
282     -----
283     There are issues with pickling functions bound to class
284     attributes, because the pickle module doesn't know where those
285     functions were originally defined (where it should point the
286     loader).  Because of this, subclasses with hard-coded filter
287     functions are encouraged to define their filter function as a
288     method of their subclass.  See, for example,
289     :meth:`NoteFilterCommand.filter`.
290     """
291     def __init__(self, plugin, name='filter playlist'):
292         super(FilterCommand, self).__init__(
293             name=name,
294             arguments=[
295                 PlaylistArgument,
296                 PlaylistNameArgument,
297                 ],
298             help=self.__doc__, plugin=plugin)
299         if not hasattr(self, 'filter'):
300             self.arguments.append(
301                 Argument(name='filter', type='function', optional=False,
302                          help="""
303 Function returning `True` for "good" curves.
304 `filter(curve, hooke, inqueue, outqueue, params) -> True/False`.
305 """.strip()))
306
307     def _run(self, hooke, inqueue, outqueue, params):
308         if not hasattr(self, 'filter'):
309             filter_fn = params['filter']
310         else:
311             filter_fn = self.filter
312         p = params['playlist'].filter(filter_fn,
313             hooke=hooke, inqueue=inqueue, outqueue=outqueue, params=params)
314         p.name = params['name']
315         if hasattr(p, 'path') and p.path != None:
316             p.set_path(os.path.join(os.path.dirname(p.path), p.name))
317         hooke.playlists.append(p)
318         outqueue.put(p)
319
320 class NoteFilterCommand (FilterCommand):
321     """Create a subset playlist of curves with `.info['note'] != None`.
322     """
323     def __init__(self, plugin):
324         super(NoteFilterCommand, self).__init__(
325             plugin, name='note filter playlist')
326
327     def filter(self, curve, hooke, inqueue, outqueue, params):
328         return 'note' in curve.info and curve.info['note'] != None