blob: 67b08220d9dffbff8a0cac1740f81bf4b133a32c [file] [log] [blame]
"""Extension to execute a script in a separate process
David Scherer <dscherer@cmu.edu>
The ExecBinding module, a replacement for ScriptBinding, executes
programs in a separate process. Unlike previous versions, this version
communicates with the user process via an RPC protocol (see the 'protocol'
module). The user program is loaded by the 'loader' and 'Remote'
modules. Its standard output and input are directed back to the
ExecBinding class through the RPC mechanism and implemented here.
A "stop program" command is provided and bound to control-break. Closing
the output window also stops the running program.
"""
import sys
import os
import imp
import OutputWindow
import protocol
import spawn
import traceback
import tempfile
# Find Python and the loader. This should be done as early in execution
# as possible, because if the current directory or sys.path is changed
# it may no longer be possible to get correct paths for these things.
pyth_exe = spawn.hardpath( sys.executable )
load_py = spawn.hardpath( imp.find_module("loader")[1] )
# The following mechanism matches loaders up with ExecBindings that are
# trying to load something.
waiting_for_loader = []
def loader_connect(client, addr):
if waiting_for_loader:
a = waiting_for_loader.pop(0)
try:
return a.connect(client, addr)
except:
return loader_connect(client,addr)
protocol.publish('ExecBinding', loader_connect)
class ExecBinding:
keydefs = {
'<<run-complete-script>>': ['<F5>'],
'<<stop-execution>>': ['<Cancel>'], #'<Control-c>'
}
menudefs = [
('run', [None,
('Run program', '<<run-complete-script>>'),
('Stop program', '<<stop-execution>>'),
]
),
]
delegate = 1
def __init__(self, editwin):
self.editwin = editwin
self.client = None
self.temp = []
if not hasattr(editwin, 'source_window'):
self.delegate = 0
self.output = OutputWindow.OnDemandOutputWindow(editwin.flist)
self.output.close_hook = self.stopProgram
self.output.source_window = editwin
else:
if (self.editwin.source_window and
self.editwin.source_window.extensions.has_key('ExecBinding') and
not self.editwin.source_window.extensions['ExecBinding'].delegate):
delegate = self.editwin.source_window.extensions['ExecBinding']
self.run_complete_script_event = delegate.run_complete_script_event
self.stop_execution_event = delegate.stop_execution_event
def __del__(self):
self.stopProgram()
def stop_execution_event(self, event):
if self.client:
self.stopProgram()
self.write('\nProgram stopped.\n','stderr')
def run_complete_script_event(self, event):
filename = self.getfilename()
if not filename: return
filename = os.path.abspath(filename)
self.stopProgram()
self.commands = [ ('run', filename) ]
waiting_for_loader.append(self)
spawn.spawn( pyth_exe, load_py )
def connect(self, client, addr):
# Called by loader_connect() above. It is remotely possible that
# we get connected to two loaders if the user is running the
# program repeatedly in a short span of time. In this case, we
# simply return None, refusing to connect and letting the redundant
# loader die.
if self.client: return None
self.client = client
client.set_close_hook( self.connect_lost )
title = self.editwin.short_title()
if title:
self.output.set_title(title + " Output")
else:
self.output.set_title("Output")
self.output.write('\n',"stderr")
self.output.scroll_clear()
return self
def connect_lost(self):
# Called by the client's close hook when the loader closes its
# socket.
# We print a disconnect message only if the output window is already
# open.
if self.output.owin and self.output.owin.text:
self.output.owin.interrupt()
self.output.write("\nProgram disconnected.\n","stderr")
for t in self.temp:
try:
os.remove(t)
except:
pass
self.temp = []
self.client = None
def get_command(self):
# Called by Remote to find out what it should be executing.
# Later this will be used to implement debugging, interactivity, etc.
if self.commands:
return self.commands.pop(0)
return ('finish',)
def program_exception(self, type, value, tb, first, last):
if type == SystemExit: return 0
for i in range(len(tb)):
filename, lineno, name, line = tb[i]
if filename in self.temp:
filename = 'Untitled'
tb[i] = filename, lineno, name, line
list = traceback.format_list(tb[first:last])
exc = traceback.format_exception_only( type, value )
self.write('Traceback (innermost last)\n', 'stderr')
for i in (list+exc):
self.write(i, 'stderr')
self.commands = []
return 1
def write(self, text, tag):
self.output.write(text,tag)
def readline(self):
return self.output.readline()
def stopProgram(self):
if self.client:
self.client.close()
self.client = None
def getfilename(self):
# Save all files which have been named, because they might be modules
for edit in self.editwin.flist.inversedict.keys():
if edit.io and edit.io.filename and not edit.get_saved():
edit.io.save(None)
# Experimental: execute unnamed buffer
if not self.editwin.io.filename:
filename = os.path.normcase(os.path.abspath(tempfile.mktemp()))
self.temp.append(filename)
if self.editwin.io.writefile(filename):
return filename
# If the file isn't save, we save it. If it doesn't have a filename,
# the user will be prompted.
if self.editwin.io and not self.editwin.get_saved():
self.editwin.io.save(None)
# If the file *still* isn't saved, we give up.
if not self.editwin.get_saved():
return
return self.editwin.io.filename