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