| """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 |