1 # Copyright (C) 2007-2010 Massimo Sandal <devicerandom@gmail.com>
2 # W. Trevor King <wking@drexel.edu>
4 # This file is part of Hooke.
6 # Hooke is free software: you can redistribute it and/or modify it
7 # under the terms of the GNU Lesser General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
11 # Hooke is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14 # Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with Hooke. If not, see
18 # <http://www.gnu.org/licenses/>.
20 """This plugin contains example commands to teach how to write an
21 Hooke plugin, including description of main Hooke internals.
28 from numpy import arange
30 from ..command import Command, Argument, Failure
31 from ..config import Setting
32 from ..interaction import PointRequest, PointResponse
33 from ..util.si import ppSI, split_data_label
35 from .curve import CurveArgument
38 class TutorialPlugin (Plugin):
39 """An example plugin explaining how to code plugins.
41 Unlike previous versions of Hooke, the class name is no longer
42 important. Plugins identify themselves to
43 :func:`hooke.util.pluggable.construct_graph` by being subclasses
44 of :class:`hooke.plugin.Plugin`. However, for consistency we
45 suggest the following naming scheme, show here for the 'tutorial'
48 =========== ==============
49 module file tutorial.py
50 class name TutorialPlugin
52 =========== ==============
54 To ensure filename sanity,
55 :func:`hooke.util.pluggable.construct_graph` requires that
56 :attr:`name` does match the submodule name, but don't worry,
57 you'll get a clear exception message if you make a mistake.
60 """TutorialPlugin initialization code.
62 We call our base class' :meth:`__init__` and setup
65 # This is the plugin initialization. When Hooke starts and
66 # the plugin is loaded, this function is executed. If there
67 # is something you need to do when Hooke starts, code it in
69 print >> sys.stderr, 'I am the Tutorial plugin initialization!'
71 # This super() call similar to the old-style
73 # but super() is more robust under multiple inheritance.
74 # See Guido's introduction:
75 # http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
76 # And the related PEPs:
77 # http://www.python.org/dev/peps/pep-0253/
78 # http://www.python.org/dev/peps/pep-3135/
79 super(TutorialPlugin, self).__init__(name='tutorial')
81 # We want :meth:`commands` to return a list of
82 # :class:`hooke.command.Command` instances. Rather than
83 # instantiate the classes for each call to :meth:`commands`,
84 # we instantiate them in a list here, and rely on
85 # :meth:`hooke.plugin.Plugin.commands` to return copies of
87 self._commands = [DoNothingCommand(self), HookeInfoCommand(self),
88 PointInfoCommand(self),]
90 def dependencies(self):
91 """Return a list of names of :class:`hooke.plugin.Plugin`\s we
94 Some plugins use features from other plugins. Hooke makes sure that
95 plugins are configured in topological order and that no plugin is
96 enabled if it is missing dependencies.
100 def default_settings(self):
101 """Return a list of :class:`hooke.config.Setting`\s for any
102 configurable plugin settings.
104 The suggested section setting is::
106 Setting(section=self.setting_section, help=self.__doc__)
108 You only need to worry about this if your plugin has some
109 "magic numbers" that the user may want to tweak, but that
110 won't be changing on a per-command basis.
112 You should lead off the list of settings with the suggested
113 section setting mentioned above.
116 # We disable help wrapping, since we've wrapped
117 # TutorialPlugin.__doc__ ourselves, and it's more than one
118 # paragraph (textwrap.fill, used in
119 # :meth:`hooke.config.Setting.write` only handles one
120 # paragraph at a time).
121 Setting(section=self.setting_section, help=self.__doc__,
123 Setting(section=self.setting_section, option='favorite color',
124 value='orange', help='Your personal favorite color.'),
128 # Define common or complicated arguments
130 # Often, several commands in a plugin will use similar arguments. For
131 # example, many curves in the 'playlist' plugin need a playlist to act
132 # on. Rather than repeating an argument definition in several times,
133 # you can keep your code DRY (Don't Repeat Yourself) by defining the
134 # argument at the module level and referencing it during each command
137 def color_callback(hooke, command, argument, value):
138 """If `argument` is `None`, default to the configured 'favorite color'.
140 :class:`hooke.command.Argument`\s may have static defaults, but
141 for dynamic defaults, they use callback functions (like this one).
145 return command.plugin.config['favorite color']
147 ColorArgument = Argument(
148 name='color', type='string', callback=color_callback,
149 help="Pick a color, any color.")
150 # See :func:`hooke.ui.gui.panel.propertyeditor.prop_from_argument` for
151 # a situation where :attr:`type` is important.
154 class DoNothingCommand (Command):
155 """This is a boring but working example of an actual Hooke command.
157 As for :class:`hooke.plugin.Plugin`\s, the class name is not
158 important, but :attr:`name` is. :attr:`name` is used (possibly
159 with some adjustment) as the name for accessing the command in the
160 various :class:`hooke.ui.UserInterface`\s. For example the
161 `'do nothing'` command can be run from the command line UI with::
165 Note that if you now start Hooke with the command's plugin
166 activated and you type in the Hooke command line "help do_nothing"
167 you will see this very text as output. That is because we set
168 :attr:`_help` to this class' docstring on initialization.
170 def __init__(self, plugin):
171 # See the comments in TutorialPlugin.__init__ for details
172 # about super() and the docstring of
173 # :class:`hooke.command.Command` for details on the __init__()
175 super(DoNothingCommand, self).__init__(
177 arguments=[ColorArgument],
178 help=self.__doc__, plugin=plugin)
180 def _run(self, hooke, inqueue, outqueue, params):
181 """This is where the command-specific magic will happen.
183 If you haven't already, read the Architecture section of
184 :file:`doc/hacking.txt` (also available `online`_). It
185 explains the engine/UI setup in more detail.
188 http://www.physics.drexel.edu/~wking/rsrch/hooke/hacking.html#architecture
190 The return value (if any) of this method is ignored. You
191 should modify the :class:`hooke.hooke.Hooke` instance passed
192 in via `hooke` and/or return things via `outqueue`. `inqueue`
193 is only important if your command requires mid-command user
196 By the time this method is called, all the argument
197 preprocessing (callbacks, defaults, etc.) have already been
198 handled by :meth:`hooke.command.Command.run`.
200 # On initialization, :class:`hooke.hooke.Hooke` sets up a
201 # logger to use for Hooke-related messages. Please use it
202 # instead of debugging 'print' calls, etc., as it is more
204 log = logging.getLogger('hooke')
205 log.debug('Watching %s paint dry' % params['color'])
208 class HookeInfoCommand (Command):
209 """Get information about the :class:`hooke.hooke.Hooke` instance.
211 def __init__(self, plugin):
212 super(HookeInfoCommand, self).__init__(
214 help=self.__doc__, plugin=plugin)
216 def _run(self, hooke, inqueue, outqueue, params):
217 outqueue.put('Hooke info:')
218 # hooke.config contains a :class:`hooke.config.HookeConfigParser`
219 # with the current hooke configuration settings.
220 config_file = StringIO.StringIO()
221 hooke.config.write(config_file)
222 outqueue.put('configuration:\n %s'
223 % '\n '.join(config_file.getvalue().splitlines()))
224 # The plugin's configuration settings are also available.
225 outqueue.put('plugin config: %s' % self.plugin.config)
226 # hooke.plugins contains :class:`hooke.plugin.Plugin`\s defining
227 # :class:`hooke.command.Command`\s.
228 outqueue.put('plugins: %s'
229 % ', '.join([plugin.name for plugin in hooke.plugins]))
230 # hooke.drivers contains :class:`hooke.driver.Driver`\s for
232 outqueue.put('drivers: %s'
233 % ', '.join([driver.name for driver in hooke.drivers]))
234 # hooke.playlists is a
235 # :class:`hooke.playlist.Playlists` instance full of
236 # :class:`hooke.playlist.FilePlaylist`\s. Each playlist may
237 # contain several :class:`hooke.curve.Curve`\s representing a
238 # grouped collection of data.
239 playlist = hooke.playlists.current()
242 outqueue.put('current playlist: %s (%d of %d)'
244 hooke.playlists.index(),
245 len(hooke.playlists)))
246 curve = playlist.current()
249 outqueue.put('current curve: %s (%d of %d)'
255 class PointInfoCommand (Command):
256 """Get information about user-selected points.
258 Ordinarily a command that knew it would need user selected points
259 would declare an appropriate argument (see, for example,
260 :class:`hooke.plugin.cut.CutCommand`). However, here we find the
261 points via user-interaction to show how user interaction works.
263 def __init__(self, plugin):
264 super(PointInfoCommand, self).__init__(
268 Argument(name='block', type='int', default=0,
270 Data block that points are selected from. For an approach/retract
271 force curve, `0` selects the approaching curve and `1` selects the
275 help=self.__doc__, plugin=plugin)
277 def _run(self, hooke, inqueue, outqueue, params):
278 data = params['curve'].data[params['block']]
280 # Ask the user to select a point.
281 outqueue.put(PointRequest(
282 msg="Select a point",
283 curve=params['curve'],
284 block=params['block']))
286 # Get the user's response
287 result = inqueue.get()
288 if not isinstance(result, PointResponse):
289 inqueue.put(result) # put the message back in the queue
291 'expected a PointResponse instance but got %s.'
295 # Act on the response
299 for column_name in data.info['columns']:
300 name,unit = split_data_label(column_name)
301 column_index = data.info['columns'].index(column_name)
302 value = data[point,column_index]
303 si_value = ppSI(value, unit, decimals=2)
304 values.append('%s: %s' % (name, si_value))
306 outqueue.put('selected point %d: %s'
307 % (point, ', '.join(values)))