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