blob: e148769c9820a3a191c5600ee28c89444969c3c4 [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:
David Scherer7aced172000-08-15 01:13:23 +000048 menudefs = [
49 ('run', [None,
50 ('Run program', '<<run-complete-script>>'),
51 ('Stop program', '<<stop-execution>>'),
52 ]
53 ),
54 ]
55
56 delegate = 1
57
58 def __init__(self, editwin):
59 self.editwin = editwin
60 self.client = None
61 self.temp = []
62
63 if not hasattr(editwin, 'source_window'):
64 self.delegate = 0
65 self.output = OutputWindow.OnDemandOutputWindow(editwin.flist)
66 self.output.close_hook = self.stopProgram
67 self.output.source_window = editwin
68 else:
69 if (self.editwin.source_window and
70 self.editwin.source_window.extensions.has_key('ExecBinding') and
71 not self.editwin.source_window.extensions['ExecBinding'].delegate):
72 delegate = self.editwin.source_window.extensions['ExecBinding']
73 self.run_complete_script_event = delegate.run_complete_script_event
74 self.stop_execution_event = delegate.stop_execution_event
75
76 def __del__(self):
77 self.stopProgram()
78
79 def stop_execution_event(self, event):
80 if self.client:
81 self.stopProgram()
82 self.write('\nProgram stopped.\n','stderr')
83
84 def run_complete_script_event(self, event):
85 filename = self.getfilename()
86 if not filename: return
87 filename = os.path.abspath(filename)
88
89 self.stopProgram()
90
91 self.commands = [ ('run', filename) ]
92 waiting_for_loader.append(self)
93 spawn.spawn( pyth_exe, load_py )
94
95 def connect(self, client, addr):
96 # Called by loader_connect() above. It is remotely possible that
97 # we get connected to two loaders if the user is running the
98 # program repeatedly in a short span of time. In this case, we
99 # simply return None, refusing to connect and letting the redundant
100 # loader die.
101 if self.client: return None
102
103 self.client = client
104 client.set_close_hook( self.connect_lost )
105
106 title = self.editwin.short_title()
107 if title:
108 self.output.set_title(title + " Output")
109 else:
110 self.output.set_title("Output")
111 self.output.write('\n',"stderr")
112 self.output.scroll_clear()
113
114 return self
115
116 def connect_lost(self):
117 # Called by the client's close hook when the loader closes its
118 # socket.
119
120 # We print a disconnect message only if the output window is already
121 # open.
122 if self.output.owin and self.output.owin.text:
123 self.output.owin.interrupt()
124 self.output.write("\nProgram disconnected.\n","stderr")
125
126 for t in self.temp:
127 try:
128 os.remove(t)
129 except:
130 pass
131 self.temp = []
132 self.client = None
133
134 def get_command(self):
135 # Called by Remote to find out what it should be executing.
136 # Later this will be used to implement debugging, interactivity, etc.
137 if self.commands:
138 return self.commands.pop(0)
139 return ('finish',)
140
141 def program_exception(self, type, value, tb, first, last):
142 if type == SystemExit: return 0
143
144 for i in range(len(tb)):
145 filename, lineno, name, line = tb[i]
146 if filename in self.temp:
147 filename = 'Untitled'
148 tb[i] = filename, lineno, name, line
149
150 list = traceback.format_list(tb[first:last])
151 exc = traceback.format_exception_only( type, value )
152
153 self.write('Traceback (innermost last)\n', 'stderr')
154 for i in (list+exc):
155 self.write(i, 'stderr')
156
157 self.commands = []
158 return 1
159
160 def write(self, text, tag):
161 self.output.write(text,tag)
162
163 def readline(self):
164 return self.output.readline()
165
166 def stopProgram(self):
167 if self.client:
168 self.client.close()
169 self.client = None
170
171 def getfilename(self):
172 # Save all files which have been named, because they might be modules
173 for edit in self.editwin.flist.inversedict.keys():
174 if edit.io and edit.io.filename and not edit.get_saved():
175 edit.io.save(None)
176
177 # Experimental: execute unnamed buffer
178 if not self.editwin.io.filename:
179 filename = os.path.normcase(os.path.abspath(tempfile.mktemp()))
180 self.temp.append(filename)
181 if self.editwin.io.writefile(filename):
182 return filename
183
184 # If the file isn't save, we save it. If it doesn't have a filename,
185 # the user will be prompted.
186 if self.editwin.io and not self.editwin.get_saved():
187 self.editwin.io.save(None)
188
189 # If the file *still* isn't saved, we give up.
190 if not self.editwin.get_saved():
191 return
192
193 return self.editwin.io.filename