"""Extension to execute a script in a separate process David Scherer 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 = { '<>': [''], '<>': [''], #'' } menudefs = [ ('run', [None, ('Run program', '<>'), ('Stop program', '<>'), ] ), ] 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