Kaydet (Commit) 969de458 authored tarafından Kurt B. Kaiser's avatar Kurt B. Kaiser

Rework the code to have only the GvR RPC. Output from execution of user

code is directed to the Shell.
üst 9f709bf9
# changes by dscherer@cmu.edu
# - created format and run menus
# - added silly advice dialog (apologies to Douglas Adams)
# - made Python Documentation work on Windows (requires win32api to
# do a ShellExecute(); other ways of starting a web browser are awkward)
import sys
import os
import string
......
"""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:
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
# changes by dscherer@cmu.edu
# - OutputWindow and OnDemandOutputWindow have been hastily
# extended to provide readline() support, an "iomark" separate
# from the "insert" cursor, and scrolling to clear the window.
# These changes are used by the ExecBinding module to provide
# standard input and output for user programs. Many of the new
# features are very similar to features of PyShell, which is a
# subclass of OutputWindow. Someone should make some sense of
# this.
from Tkinter import *
from EditorWindow import EditorWindow
import re
import tkMessageBox
from UndoDelegator import UndoDelegator
class OutputWindow(EditorWindow):
class OutputUndoDelegator(UndoDelegator):
reading = 0
# Forbid insert/delete before the I/O mark, in the blank lines after
# the output, or *anywhere* if we are not presently doing user input
def insert(self, index, chars, tags=None):
try:
if (self.delegate.compare(index, "<", "iomark") or
self.delegate.compare(index, ">", "endmark") or
(index!="iomark" and not self.reading)):
self.delegate.bell()
return
except TclError:
pass
UndoDelegator.insert(self, index, chars, tags)
def delete(self, index1, index2=None):
try:
if (self.delegate.compare(index1, "<", "iomark") or
self.delegate.compare(index1, ">", "endmark") or
(index2 and self.delegate.compare(index2, ">=", "endmark")) or
not self.reading):
self.delegate.bell()
return
except TclError:
pass
UndoDelegator.delete(self, index1, index2)
"""An editor window that can serve as an output file.
class OutputWindow(EditorWindow):
"""An editor window that can serve as an input and output file.
The input support has been rather hastily hacked in, and should
not be trusted.
Also the future base class for the Python shell window.
This class has no input facilities.
"""
UndoDelegator = OutputUndoDelegator
source_window = None
def __init__(self, *args, **keywords):
if keywords.has_key('source_window'):
self.source_window = keywords['source_window']
def __init__(self, *args):
apply(EditorWindow.__init__, (self,) + args)
self.text.bind("<<goto-file-line>>", self.goto_file_line)
self.text.bind("<<newline-and-indent>>", self.enter_callback)
self.text.mark_set("iomark","1.0")
self.text.mark_gravity("iomark", LEFT)
self.text.mark_set("endmark","1.0")
# Customize EditorWindow
......@@ -69,9 +24,6 @@ class OutputWindow(EditorWindow):
def short_title(self):
return "Output"
def long_title(self):
return ""
def maybesave(self):
# Override base class method -- don't ask any questions
if self.get_saved():
......@@ -79,63 +31,10 @@ class OutputWindow(EditorWindow):
else:
return "no"
# Act as input file - incomplete
def set_line_and_column(self, event=None):
index = self.text.index(INSERT)
if (self.text.compare(index, ">", "endmark")):
self.text.mark_set("insert", "endmark")
self.text.see("insert")
EditorWindow.set_line_and_column(self)
reading = 0
canceled = 0
endoffile = 0
def readline(self):
save = self.reading
try:
self.reading = self.undo.reading = 1
self.text.mark_set("insert", "iomark")
self.text.see("insert")
self.top.mainloop()
finally:
self.reading = self.undo.reading = save
line = self.text.get("input", "iomark")
if self.canceled:
self.canceled = 0
raise KeyboardInterrupt
if self.endoffile:
self.endoffile = 0
return ""
return line or '\n'
def close(self):
self.interrupt()
return EditorWindow.close(self)
def interrupt(self):
if self.reading:
self.endoffile = 1
self.top.quit()
def enter_callback(self, event):
if self.reading and self.text.compare("insert", ">=", "iomark"):
self.text.mark_set("input", "iomark")
self.text.mark_set("iomark", "insert")
self.write('\n',"iomark")
self.text.tag_add("stdin", "input", "iomark")
self.text.update_idletasks()
self.top.quit() # Break out of recursive mainloop() in raw_input()
return "break"
# Act as output file
def write(self, s, tags=(), mark="iomark"):
self.text.mark_gravity(mark, RIGHT)
self.text.insert(mark, s, tags)
self.text.mark_gravity(mark, LEFT)
def write(self, s, tags=(), mark="insert"):
self.text.insert(mark, str(s), tags)
self.text.see(mark)
self.text.update()
......@@ -183,14 +82,8 @@ class OutputWindow(EditorWindow):
master=self.text)
return
filename, lineno = result
edit = self.untitled(filename) or self.flist.open(filename)
edit = self.flist.open(filename)
edit.gotoline(lineno)
edit.wakeup()
def untitled(self, filename):
if filename!='Untitled' or not self.source_window or self.source_window.io.filename:
return None
return self.source_window
def _file_line_helper(self, line):
for prog in self.file_line_progs:
......@@ -200,80 +93,63 @@ class OutputWindow(EditorWindow):
else:
return None
filename, lineno = m.group(1, 2)
if not self.untitled(filename):
try:
f = open(filename, "r")
f.close()
except IOError:
return None
try:
f = open(filename, "r")
f.close()
except IOError:
return None
try:
return filename, int(lineno)
except TypeError:
return None
# This classes now used by ExecBinding.py:
# These classes are currently not used but might come in handy
class OnDemandOutputWindow:
source_window = None
tagdefs = {
# XXX Should use IdlePrefs.ColorPrefs
"stdin": {"foreground": "black"},
"stdout": {"foreground": "blue"},
"stderr": {"foreground": "red"},
}
"stderr": {"foreground": "#007700"},
}
def __init__(self, flist):
self.flist = flist
self.owin = None
self.title = "Output"
self.close_hook = None
self.old_close = None
def owclose(self):
if self.close_hook:
self.close_hook()
if self.old_close:
self.old_close()
def set_title(self, title):
self.title = title
if self.owin and self.owin.text:
self.owin.saved_change_hook()
def write(self, s, tags=(), mark="iomark"):
if not self.owin or not self.owin.text:
def write(self, s, tags, mark):
if not self.owin:
self.setup()
self.owin.write(s, tags, mark)
def readline(self):
if not self.owin or not self.owin.text:
self.setup()
return self.owin.readline()
def scroll_clear(self):
if self.owin and self.owin.text:
lineno = self.owin.getlineno("endmark")
self.owin.text.mark_set("insert","endmark")
self.owin.text.yview(float(lineno))
self.owin.wakeup()
def setup(self):
self.owin = owin = OutputWindow(self.flist, source_window = self.source_window)
owin.short_title = lambda self=self: self.title
self.owin = owin = OutputWindow(self.flist)
text = owin.text
self.old_close = owin.close_hook
owin.close_hook = self.owclose
# xxx Bad hack: 50 blank lines at the bottom so that
# we can scroll the top of the window to the output
# cursor in scroll_clear(). There must be a better way...
owin.text.mark_gravity('endmark', LEFT)
owin.text.insert('iomark', '\n'*50)
owin.text.mark_gravity('endmark', RIGHT)
for tag, cnf in self.tagdefs.items():
if cnf:
apply(text.tag_configure, (tag,), cnf)
text.tag_raise('sel')
self.write = self.owin.write
#class PseudoFile:
#
# def __init__(self, owin, tags, mark="end"):
# self.owin = owin
# self.tags = tags
# self.mark = mark
# def write(self, s):
# self.owin.write(s, self.tags, self.mark)
# def writelines(self, l):
# map(self.write, l)
# def flush(self):
# pass
This diff is collapsed.
"""Remote
This module is imported by the loader and serves to control
the execution of the user program. It presently executes files
and reports exceptions to IDLE. It could be extended to provide
other services, such as interactive mode and debugging. To that
end, it could be a subclass of e.g. InteractiveInterpreter.
Two other classes, pseudoIn and pseudoOut, are file emulators also
used by loader.
"""
import sys, os
import traceback
class Remote:
def __init__(self, main, master):
self.main = main
self.master = master
self.this_file = self.canonic( self.__init__.im_func.func_code.co_filename )
def canonic(self, path):
return os.path.normcase(os.path.abspath(path))
def mainloop(self):
while 1:
args = self.master.get_command()
try:
f = getattr(self,args[0])
apply(f,args[1:])
except:
if not self.report_exception(): raise
def finish(self):
sys.exit()
def run(self, *argv):
sys.argv = argv
path = self.canonic( argv[0] )
dir = self.dir = os.path.dirname(path)
os.chdir(dir)
sys.path[0] = dir
usercode = open(path)
exec usercode in self.main
def report_exception(self):
try:
type, value, tb = sys.exc_info()
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
tblist = traceback.extract_tb(tb)
# Look through the traceback, canonicalizing filenames and
# eliminating leading and trailing system modules.
first = last = 1
for i in range(len(tblist)):
filename, lineno, name, line = tblist[i]
filename = self.canonic(filename)
tblist[i] = filename, lineno, name, line
dir = os.path.dirname(filename)
if filename == self.this_file:
first = i+1
elif dir==self.dir:
last = i+1
# Canonicalize the filename in a syntax error, too:
if type is SyntaxError:
try:
msg, (filename, lineno, offset, line) = value
filename = self.canonic(filename)
value = msg, (filename, lineno, offset, line)
except:
pass
return self.master.program_exception( type, value, tblist, first, last )
finally:
# avoid any circular reference through the traceback
del tb
class pseudoIn:
def __init__(self, readline):
self.readline = readline
def isatty():
return 1
class pseudoOut:
def __init__(self, func, **kw):
self.func = func
self.kw = kw
def write(self, *args):
return apply( self.func, args, self.kw )
def writelines(self, l):
map(self.write, l)
def flush(self):
pass
"""Extension to execute code outside the Python shell window.
This adds the following commands (to the Edit menu, until there's a
separate Python menu):
This adds the following commands:
- Check module (Alt-F5) does a full syntax check of the current module.
- Check module does a full syntax check of the current module.
It also runs the tabnanny to catch any inconsistent tabs.
- Import module (F5) is equivalent to either import or reload of the
- Import module is equivalent to either import or reload of the
current module. The window must have been saved previously. The
module is added to sys.modules, and is also added to the __main__
namespace. Output goes to the shell window.
- Run module (Control-F5) does the same but executes the module's
- Run module does the same but executes the module's
code in the __main__ namespace.
XXX Redesign this interface (yet again) as follows:
......@@ -41,12 +40,14 @@ how many spaces a tab is worth.
To fix case 2, change all tabs to spaces by using Select All followed \
by Untabify Region (both in the Edit menu)."""
# XXX TBD Implement stop-execution KBK 11Jun02
class ScriptBinding:
menudefs = [
('edit', [None,
('Check module', '<<check-module>>'),
('Import module', '<<import-module>>'),
('run', [None,
# ('Check module', '<<check-module>>'),
# ('Import module', '<<import-module>>'),
('Run script', '<<run-script>>'),
]
),
......
......@@ -42,17 +42,17 @@ enable=1
[ZoomHeight_cfgBindings]
zoom-height=<Alt-Key-F2>
[ExecBinding]
enable=1
[ExecBinding_cfgBindings]
run-complete-script=<Key-F5>
stop-execution=<Key-Cancel>
#[ExecBinding] # Revert to ScriptBinding
#enable=1
#[ExecBinding_cfgBindings]
#run-complete-script=<Key-F5>
#stop-execution=<Key-Cancel>
#[ScriptBinding] #currently ExecBinding has replaced ScriptBinding
#enable=0
#[ScriptBinding_cfgBindings]
#run-script=<Key-F5>
#check-module=<Alt-Key-F5> <Meta-Key-F5>
[ScriptBinding]
enable=1
[ScriptBinding_cfgBindings]
run-script=<Key-F5>
#check-module=<Alt-Key-F5>
#import-module=<Control-Key-F5>
[CallTips]
......
......@@ -51,9 +51,7 @@ cursor-background= black
#[ZoomHeight]
#[ScriptBinding] # disabled in favor of ExecBinding
[ExecBinding]
[ScriptBinding]
[CallTips]
......
# Everything is done inside the loader function so that no other names
# are placed in the global namespace. Before user code is executed,
# even this name is unbound.
def loader():
import sys, os, protocol, threading, time
import Remote
## Use to debug the loading process itself:
## sys.stdout = open('c:\\windows\\desktop\\stdout.txt','a')
## sys.stderr = open('c:\\windows\\desktop\\stderr.txt','a')
# Ensure that there is absolutely no pollution of the global
# namespace by deleting the global name of this function.
global loader
del loader
# Connect to IDLE
try:
client = protocol.Client()
except protocol.connectionLost, cL:
print 'loader: Unable to connect to IDLE', cL
return
# Connect to an ExecBinding object that needs our help. If
# the user is starting multiple programs right now, we might get a
# different one than the one that started us. Proving that's okay is
# left as an exercise to the reader. (HINT: Twelve, by the pigeonhole
# principle)
ExecBinding = client.getobject('ExecBinding')
if not ExecBinding:
print "loader: IDLE does not need me."
return
# All of our input and output goes through ExecBinding.
sys.stdin = Remote.pseudoIn( ExecBinding.readline )
sys.stdout = Remote.pseudoOut( ExecBinding.write.void, tag="stdout" )
sys.stderr = Remote.pseudoOut( ExecBinding.write.void, tag="stderr" )
# Create a Remote object and start it running.
remote = Remote.Remote(globals(), ExecBinding)
rthread = threading.Thread(target=remote.mainloop)
rthread.setDaemon(1)
rthread.start()
# Block until either the client or the user program stops
user = rthread.isAlive
while user and client.isAlive():
time.sleep(0.025)
if not user():
user = hasattr(sys, "ready_to_exit") and sys.ready_to_exit
for t in threading.enumerate():
if not t.isDaemon() and t.isAlive() and t!=threading.currentThread():
user = t.isAlive
break
# We need to make sure we actually exit, so that the user doesn't get
# stuck with an invisible process. We want to finalize C modules, so
# we don't use os._exit(), but we don't call sys.exitfunc, which might
# block forever.
del sys.exitfunc
sys.exit()
loader()
This diff is collapsed.
# spawn - This is ugly, OS-specific code to spawn a separate process. It
# also defines a function for getting the version of a path most
# likely to work with cranky API functions.
import os
def hardpath(path):
path = os.path.normcase(os.path.abspath(path))
try:
import win32api
path = win32api.GetShortPathName( path )
except:
pass
return path
if hasattr(os, 'fork'):
# UNIX-ish operating system: we fork() and exec(), and we have to track
# the pids of our children and call waitpid() on them to avoid leaving
# zombies in the process table. kill_zombies() does the dirty work, and
# should be called periodically.
zombies = []
def spawn(bin, *args):
pid = os.fork()
if pid:
zombies.append(pid)
else:
os.execv( bin, (bin, ) + args )
def kill_zombies():
for z in zombies[:]:
stat = os.waitpid(z, os.WNOHANG)
if stat[0]==z:
zombies.remove(z)
elif hasattr(os, 'spawnv'):
# Windows-ish OS: we use spawnv(), and stick quotes around arguments
# in case they contains spaces, since Windows will jam all the
# arguments to spawn() or exec() together into one string. The
# kill_zombies function is a noop.
def spawn(bin, *args):
nargs = ['"'+bin+'"']
for arg in args:
nargs.append( '"'+arg+'"' )
os.spawnv( os.P_NOWAIT, bin, nargs )
def kill_zombies(): pass
else:
# If you get here, you may be able to write an alternative implementation
# of these functions for your OS.
def kill_zombies(): pass
raise OSError, 'This OS does not support fork() or spawnv().'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment