blob: 67b08220d9dffbff8a0cac1740f81bf4b133a32c [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001"""Extension to execute a script in a separate process
2
3David Scherer <dscherer@cmu.edu>
4
5 The ExecBinding module, a replacement for ScriptBinding, executes
6 programs in a separate process. Unlike previous versions, this version
7 communicates with the user process via an RPC protocol (see the 'protocol'
8 module). The user program is loaded by the 'loader' and 'Remote'
9 modules. Its standard output and input are directed back to the
10 ExecBinding class through the RPC mechanism and implemented here.
11
12 A "stop program" command is provided and bound to control-break. Closing
13 the output window also stops the running program.
14"""
15
16import sys
17import os
18import imp
19import OutputWindow
20import protocol
21import spawn
22import traceback
23import tempfile
24
25# Find Python and the loader. This should be done as early in execution
26# as possible, because if the current directory or sys.path is changed
27# it may no longer be possible to get correct paths for these things.
28
29pyth_exe = spawn.hardpath( sys.executable )
30load_py = spawn.hardpath( imp.find_module("loader")[1] )
31
32# The following mechanism matches loaders up with ExecBindings that are
33# trying to load something.
34
35waiting_for_loader = []
36
37def loader_connect(client, addr):
38 if waiting_for_loader:
39 a = waiting_for_loader.pop(0)
40 try:
41 return a.connect(client, addr)
42 except:
43 return loader_connect(client,addr)
44
45protocol.publish('ExecBinding', loader_connect)
46
47class ExecBinding:
48 keydefs = {
49 '<<run-complete-script>>': ['<F5>'],
50 '<<stop-execution>>': ['<Cancel>'], #'<Control-c>'
51 }
52
53 menudefs = [
54 ('run', [None,
55 ('Run program', '<<run-complete-script>>'),
56 ('Stop program', '<<stop-execution>>'),
57 ]
58 ),
59 ]
60
61 delegate = 1
62
63 def __init__(self, editwin):
64 self.editwin = editwin
65 self.client = None
66 self.temp = []
67
68 if not hasattr(editwin, 'source_window'):
69 self.delegate = 0
70 self.output = OutputWindow.OnDemandOutputWindow(editwin.flist)
71 self.output.close_hook = self.stopProgram
72 self.output.source_window = editwin
73 else:
74 if (self.editwin.source_window and
75 self.editwin.source_window.extensions.has_key('ExecBinding') and
76 not self.editwin.source_window.extensions['ExecBinding'].delegate):
77 delegate = self.editwin.source_window.extensions['ExecBinding']
78 self.run_complete_script_event = delegate.run_complete_script_event
79 self.stop_execution_event = delegate.stop_execution_event
80
81 def __del__(self):
82 self.stopProgram()
83
84 def stop_execution_event(self, event):
85 if self.client:
86 self.stopProgram()
87 self.write('\nProgram stopped.\n','stderr')
88
89 def run_complete_script_event(self, event):
90 filename = self.getfilename()
91 if not filename: return
92 filename = os.path.abspath(filename)
93
94 self.stopProgram()
95
96 self.commands = [ ('run', filename) ]
97 waiting_for_loader.append(self)
98 spawn.spawn( pyth_exe, load_py )
99
100 def connect(self, client, addr):
101 # Called by loader_connect() above. It is remotely possible that
102 # we get connected to two loaders if the user is running the
103 # program repeatedly in a short span of time. In this case, we
104 # simply return None, refusing to connect and letting the redundant
105 # loader die.
106 if self.client: return None
107
108 self.client = client
109 client.set_close_hook( self.connect_lost )
110
111 title = self.editwin.short_title()
112 if title:
113 self.output.set_title(title + " Output")
114 else:
115 self.output.set_title("Output")
116 self.output.write('\n',"stderr")
117 self.output.scroll_clear()
118
119 return self
120
121 def connect_lost(self):
122 # Called by the client's close hook when the loader closes its
123 # socket.
124
125 # We print a disconnect message only if the output window is already
126 # open.
127 if self.output.owin and self.output.owin.text:
128 self.output.owin.interrupt()
129 self.output.write("\nProgram disconnected.\n","stderr")
130
131 for t in self.temp:
132 try:
133 os.remove(t)
134 except:
135 pass
136 self.temp = []
137 self.client = None
138
139 def get_command(self):
140 # Called by Remote to find out what it should be executing.
141 # Later this will be used to implement debugging, interactivity, etc.
142 if self.commands:
143 return self.commands.pop(0)
144 return ('finish',)
145
146 def program_exception(self, type, value, tb, first, last):
147 if type == SystemExit: return 0
148
149 for i in range(len(tb)):
150 filename, lineno, name, line = tb[i]
151 if filename in self.temp:
152 filename = 'Untitled'
153 tb[i] = filename, lineno, name, line
154
155 list = traceback.format_list(tb[first:last])
156 exc = traceback.format_exception_only( type, value )
157
158 self.write('Traceback (innermost last)\n', 'stderr')
159 for i in (list+exc):
160 self.write(i, 'stderr')
161
162 self.commands = []
163 return 1
164
165 def write(self, text, tag):
166 self.output.write(text,tag)
167
168 def readline(self):
169 return self.output.readline()
170
171 def stopProgram(self):
172 if self.client:
173 self.client.close()
174 self.client = None
175
176 def getfilename(self):
177 # Save all files which have been named, because they might be modules
178 for edit in self.editwin.flist.inversedict.keys():
179 if edit.io and edit.io.filename and not edit.get_saved():
180 edit.io.save(None)
181
182 # Experimental: execute unnamed buffer
183 if not self.editwin.io.filename:
184 filename = os.path.normcase(os.path.abspath(tempfile.mktemp()))
185 self.temp.append(filename)
186 if self.editwin.io.writefile(filename):
187 return filename
188
189 # If the file isn't save, we save it. If it doesn't have a filename,
190 # the user will be prompted.
191 if self.editwin.io and not self.editwin.get_saved():
192 self.editwin.io.save(None)
193
194 # If the file *still* isn't saved, we give up.
195 if not self.editwin.get_saved():
196 return
197
198 return self.editwin.io.filename