Add test/get_curve.py
[hooke.git] / hooke / hooke.py
1 # Copyright (C) 2008-2010 Fabrizio Benedetti
2 #                         Massimo Sandal <devicerandom@gmail.com>
3 #                         Rolf Schmidt <rschmidt@alcor.concordia.ca>
4 #                         W. Trevor King <wking@drexel.edu>
5 #
6 # This file is part of Hooke.
7 #
8 # Hooke is free software: you can redistribute it and/or modify it
9 # under the terms of the GNU Lesser General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
12 #
13 # Hooke is distributed in the hope that it will be useful, but WITHOUT
14 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
16 # Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with Hooke.  If not, see
20 # <http://www.gnu.org/licenses/>.
21
22 """Hooke - A force spectroscopy review & analysis tool.
23 """
24
25 if False: # Queue pickle error debugging code
26     """The Hooke class is passed back from the CommandEngine process
27     to the main process via a :class:`multiprocessing.queues.Queue`,
28     which uses :mod:`pickle` for serialization.  There are a number of
29     objects that are unpicklable, and the error messages are not
30     always helpful.  This block of code hooks you into the Queue's
31     _feed method so you can print out useful tidbits to help find the
32     particular object that is gumming up the pickle works.
33     """
34     import multiprocessing.queues
35     import sys
36     feed = multiprocessing.queues.Queue._feed
37     def new_feed (buffer, notempty, send, writelock, close):
38         def s(obj):
39             print 'SEND:', obj, dir(obj)
40             for a in dir(obj):
41                 attr = getattr(obj, a)
42                 #print '  ', a, attr, type(attr)
43             if obj.__class__.__name__ == 'Hooke':
44                 # Set suspect attributes to None until you resolve the
45                 # PicklingError.  Then fix whatever is breaking the
46                 # pickling.
47                 #obj.commands = None
48                 #obj.drivers = None
49                 #obj.plugins = None
50                 #obj.ui = None
51                 pass
52             sys.stdout.flush()
53             send(obj)
54         feed(buffer, notempty, s, writelock, close)
55     multiprocessing.queues.Queue._feed = staticmethod(new_feed)
56
57 import logging
58 import logging.config
59 import multiprocessing
60 import optparse
61 import os.path
62 import Queue
63 import unittest
64 import StringIO
65 import sys
66
67 from . import version
68 from . import engine
69 from . import config as config_mod
70 from . import playlist
71 from . import plugin as plugin_mod
72 from . import driver as driver_mod
73 from . import ui
74
75
76 class Hooke (object):
77     def __init__(self, config=None, debug=0):
78         self.debug = debug
79         default_settings = (config_mod.DEFAULT_SETTINGS
80                             + plugin_mod.default_settings()
81                             + driver_mod.default_settings()
82                             + ui.default_settings())
83         if config == None:
84             config = config_mod.HookeConfigParser(
85                 paths=config_mod.DEFAULT_PATHS,
86                 default_settings=default_settings)
87             config.read()
88         self.config = config
89         self.load_log()
90         self.load_plugins()
91         self.load_drivers()
92         self.load_ui()
93         self.command = engine.CommandEngine()
94         self.playlists = playlist.NoteIndexList()
95
96     def load_log(self):
97         config_file = StringIO.StringIO()
98         self.config.write(config_file)
99         logging.config.fileConfig(StringIO.StringIO(config_file.getvalue()))
100         # Don't attach the logger because it contains an unpicklable
101         # thread.lock.  Instead, grab it directly every time you need it.
102         #self.log = logging.getLogger('hooke')
103
104     def load_plugins(self):
105         self.plugins = plugin_mod.load_graph(
106             plugin_mod.PLUGIN_GRAPH, self.config, include_section='plugins')
107         self.commands = []
108         for plugin in self.plugins:
109             self.commands.extend(plugin.commands())
110
111     def load_drivers(self):
112         self.drivers = plugin_mod.load_graph(
113             driver_mod.DRIVER_GRAPH, self.config, include_section='drivers')
114
115     def load_ui(self):
116         self.ui = ui.load_ui(self.config)
117
118     def close(self):
119         self.config.write() # Does not preserve original comments
120
121 class HookeRunner (object):
122     def run(self, hooke):
123         """Run Hooke's main execution loop.
124
125         Spawns a :class:`hooke.engine.CommandEngine` subprocess and
126         then runs the UI, rejoining the `CommandEngine` process after
127         the UI exits.
128         """
129         ui_to_command,command_to_ui,command = self._setup_run(hooke)
130         try:
131             hooke.ui.run(hooke.commands, ui_to_command, command_to_ui)
132         finally:
133             hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
134         return hooke
135
136     def run_lines(self, hooke, lines):
137         """Run the pre-set commands `lines` with the "command line" UI.
138
139         Allows for non-interactive sessions that are otherwise
140         equivalent to :meth:'.run'.
141         """
142         cmdline = ui.load_ui(hooke.config, 'command line')
143         ui_to_command,command_to_ui,command = self._setup_run(hooke)
144         try:
145             cmdline.run_lines(
146                 hooke.commands, ui_to_command, command_to_ui, lines)
147         finally:
148             hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
149         return hooke
150
151     def _setup_run(self, hooke):
152         ui_to_command = multiprocessing.Queue()
153         command_to_ui = multiprocessing.Queue()
154         manager = multiprocessing.Manager()
155         command = multiprocessing.Process(name='command engine',
156             target=hooke.command.run, args=(hooke, ui_to_command, command_to_ui))
157         command.start()
158         return (ui_to_command, command_to_ui, command)
159
160     def _cleanup_run(self, ui_to_command, command_to_ui, command):
161         log = logging.getLogger('hooke')
162         log.debug('cleanup sending CloseEngine')
163         ui_to_command.put(ui.CloseEngine())
164         hooke = None
165         while not isinstance(hooke, Hooke):
166             log.debug('cleanup waiting for Hooke instance from the engine.')
167             hooke = command_to_ui.get(block=True)
168             log.debug('cleanup got %s instance' % type(hooke))
169         command.join()
170         return hooke
171
172
173 def main():
174     p = optparse.OptionParser()
175     p.add_option(
176         '--version', dest='version', default=False, action='store_true',
177         help="Print Hooke's version information and exit.")
178     p.add_option(
179         '-s', '--script', dest='script', metavar='FILE',
180         help='Script of command line Hooke commands to run.')
181     p.add_option(
182         '-c', '--command', dest='commands', metavar='COMMAND',
183         action='append', default=[],
184         help='Add a command line Hooke command to run.')
185     p.add_option(
186         '--command-no-exit', dest='command_exit',
187         action='store_false', default=True,
188         help="Don't exit after running a script or commands.")
189     options,arguments = p.parse_args()
190     if len(arguments) > 0:
191         print >> sys.stderr, 'More than 0 arguments to %s: %s' \
192             % (sys.argv[0], arguments)
193         p.print_help(sys.stderr)
194         sys.exit(1)
195
196     hooke = Hooke(debug=__debug__)
197     runner = HookeRunner()
198
199     if options.version == True:
200         print version()
201         sys.exit(0)
202     if options.script != None:
203         with open(os.path.expanduser(options.script), 'r') as f:
204             options.commands.extend(f.readlines())
205     if len(options.commands) > 0:
206         try:
207             hooke = runner.run_lines(hooke, options.commands)
208         finally:
209             if options.command_exit == True:
210                 hooke.close()
211                 sys.exit(0)
212
213     try:
214         hooke = runner.run(hooke)
215     finally:
216         hooke.close()