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

code is directed to the Shell.
This commit is contained in:
Kurt B. Kaiser 2002-06-12 03:28:57 +00:00
parent 9f709bf9a1
commit 969de458aa
11 changed files with 168 additions and 1187 deletions

View File

@ -1,9 +1,3 @@
# 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

View File

@ -1,193 +0,0 @@
"""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

View File

@ -1,64 +1,19 @@
# 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 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)
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.
"""An editor window that can serve as an output file.
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

View File

@ -1,34 +1,10 @@
#! /usr/bin/env python
# changes by dscherer@cmu.edu
# The main() function has been replaced by a whole class, in order to
# address the constraint that only one process can sit on the port
# hard-coded into the loader.
# It attempts to load the RPC protocol server and publish itself. If
# that fails, it assumes that some other copy of IDLE is already running
# on the port and attempts to contact it. It then uses the RPC mechanism
# to ask that copy to do whatever it was instructed (via the command
# line) to do. (Think netscape -remote). The handling of command line
# arguments for remotes is still very incomplete.
# Default behavior (no command line options) is to open an editor window
# instead of starting the Python Shell. However, if called as
# Pyshell.main(0), the Shell will be started instead of the editor window.
# In the default editor mode, if files are specified, they are opened.
# If any command line options are specified, a shell does appear, and if
# the -e option is used, both a shell and an editor window open.
import os
import spawn
import sys
import string
import getopt
import re
import protocol
import socket
import time
import warnings
@ -44,13 +20,14 @@ from EditorWindow import EditorWindow, fixwordbreaks
from FileList import FileList
from ColorDelegator import ColorDelegator
from UndoDelegator import UndoDelegator
from OutputWindow import OutputWindow, OnDemandOutputWindow
from OutputWindow import OutputWindow
from configHandler import idleConf
import idlever
import rpc
use_subprocess = 0 # Set to 1 to spawn subprocess for command execution
# XX hardwire this for now, remove later KBK 09Jun02
use_subprocess = 1 # Set to 1 to spawn subprocess for command execution
# Change warnings module to write to sys.__stderr__
try:
@ -204,9 +181,6 @@ class ModifiedInterpreter(InteractiveInterpreter):
InteractiveInterpreter.__init__(self, locals=locals)
self.save_warnings_filters = None
global flist
self.output = OnDemandOutputWindow(flist)
rpcclt = None
rpcpid = None
@ -226,14 +200,14 @@ class ModifiedInterpreter(InteractiveInterpreter):
if i > 3:
print >>sys.__stderr__, "Socket error:", err, "; retry..."
else:
# XXX Make this a dialog?
# XXX Make this a dialog? #GvR
print >>sys.__stderr__, "Can't spawn subprocess!"
# XXX Add Stephen's error msg, resolve the two later... KBK 09Jun02
display_port_binding_error()
return
self.output.stdout=PseudoFile(self.output, "stdout")
self.output.stderr=PseudoFile(self.output, "stderr")
self.rpcclt.register("stdin", self.output)
self.rpcclt.register("stdout", self.output.stdout)
self.rpcclt.register("stderr", self.output.stderr)
self.rpcclt.register("stdin", self.tkconsole)
self.rpcclt.register("stdout", self.tkconsole.stdout)
self.rpcclt.register("stderr", self.tkconsole.stderr)
self.rpcclt.register("flist", self.tkconsole.flist)
self.poll_subprocess()
@ -629,7 +603,7 @@ class PyShell(OutputWindow):
def begin(self):
self.resetoutput()
self.write("Python %s on %s\n%s\nIDLE Fork %s -- press F1 for help\n" %
self.write("Python %s on %s\n%s\nGRPC IDLE Fork %s\n" %
(sys.version, sys.platform, self.COPYRIGHT,
idlever.IDLE_VERSION))
try:
@ -795,8 +769,9 @@ class PyShell(OutputWindow):
i = i-1
line = line[:i]
more = self.interp.runsource(line)
if not more:
self.showprompt()
# XXX This was causing extra prompt with shell KBK
# if not more:
# self.showprompt()
def cancel_check(self, frame, what, args,
dooneevent=tkinter.dooneevent,
@ -876,6 +851,7 @@ class PseudoFile:
def isatty(self):
return 1
usage_msg = """\
usage: idle.py [-c command] [-d] [-i] [-r script] [-s] [-t title] [arg] ...
@ -886,194 +862,115 @@ idle file(s) (without options) edit the file(s)
-e edit mode; arguments are files to be edited
-i open an interactive shell
-i file(s) open a shell and also an editor window for each file
-r script use experimental remote (subprocess) execution feature
-r
-s run $IDLESTARTUP or $PYTHONSTARTUP before anything else
-t title set title of shell window
Remaining arguments are applied to the command (-c) or script (-r).
"""
class usageError:
def __init__(self, string): self.string = string
def __repr__(self): return self.string
def main():
cmd = None
edit = 0
debug = 0
script = None
startup = 0
class main:
def __init__(self, noshell=1):
global flist, root
root = Tk(className="Idle")
fixwordbreaks(root)
root.withdraw()
flist = PyShellFileList(root)
try:
opts, args = getopt.getopt(sys.argv[1:], "c:deir:st:")
except getopt.error, msg:
sys.stderr.write("Error: %s\n" % str(msg))
sys.stderr.write(usage_msg)
sys.exit(2)
# the following causes lockups and silent failures when debugging
# changes to EditorWindow.__init__ ; the console works fine for idle
# debugging in any case, so disable this unnescesary stuff.
#dbg=OnDemandOutputWindow(flist)
#dbg.set_title('IDLE Debugging Messages')
#sys.stdout = PseudoFile(dbg,['stdout'])
#sys.stderr = PseudoFile(dbg,['stderr'])
try:
self.server = protocol.Server(connection_hook = self.address_ok)
protocol.publish( 'IDLE', self.connect )
self.main(sys.argv[1:], noshell)
return
except protocol.connectionLost:
try:
client = protocol.Client()
IDLE = client.getobject('IDLE')
if IDLE:
try:
IDLE.remote( sys.argv[1:] )
except usageError, msg:
sys.stderr.write("Error: %s\n" % str(msg))
sys.stderr.write(usage_msg)
return
except protocol.connectionLost:
pass
for o, a in opts:
if o == '-c':
cmd = a
if o == '-d':
debug = 1
if o == '-e':
edit = 1
if o == '-r':
script = a
if o == '-s':
startup = 1
if o == '-t':
PyShell.shell_title = a
#maybe the following should be handled by a tkmessagebox for
#users who don't start idle from a console??
print """\
if args and args[0] != "-": edit = 1
for i in range(len(sys.path)):
sys.path[i] = os.path.abspath(sys.path[i])
pathx = []
if edit:
for filename in args:
pathx.append(os.path.dirname(filename))
elif args and args[0] != "-":
pathx.append(os.path.dirname(args[0]))
else:
pathx.append(os.curdir)
for dir in pathx:
dir = os.path.abspath(dir)
if not dir in sys.path:
sys.path.insert(0, dir)
global flist, root
root = Tk(className="Idle")
fixwordbreaks(root)
root.withdraw()
flist = PyShellFileList(root)
if edit:
for filename in args:
flist.open(filename)
if not args:
flist.new()
else:
if cmd:
sys.argv = ["-c"] + args
else:
sys.argv = args or [""]
shell = PyShell(flist)
interp = shell.interp
flist.pyshell = shell
if startup:
filename = os.environ.get("IDLESTARTUP") or \
os.environ.get("PYTHONSTARTUP")
if filename and os.path.isfile(filename):
interp.execfile(filename)
if debug:
shell.open_debugger()
if cmd:
interp.execsource(cmd)
elif script:
if os.path.isfile(script):
interp.execfile(script)
else:
print "No script file: ", script
shell.begin()
root.mainloop()
root.destroy()
def display_port_binding_error():
print """\
IDLE cannot run.
IDLE needs to use a specific TCP/IP port (7454) in order to execute and
IDLE needs to use a specific TCP/IP port (8833) in order to execute and
debug programs. IDLE is unable to bind to this port, and so cannot
start. Here are some possible causes of this problem:
1. TCP/IP networking is not installed or not working on this computer
2. Another program is running that uses this port
3. Another copy of IDLE stopped responding but is still bound to the port
4. Personal firewall software is preventing IDLE from using this port
3. Personal firewall software is preventing IDLE from using this port
IDLE makes and accepts connections only with this computer, and does not
communicate over the internet in any way. It's use of port 7454 should not
communicate over the internet in any way. Its use of port 8833 should not
be a security risk on a single-user machine.
"""
dbg.owin.gotoline(1)
dbg.owin.remove_selection()
root.mainloop() # wait for user to read message
def idle(self):
spawn.kill_zombies()
self.server.rpc_loop()
root.after(25, self.idle)
# We permit connections from localhost only
def address_ok(self, addr):
return addr[0] == '127.0.0.1'
def connect(self, client, addr):
return self
def remote( self, argv ):
# xxx Should make this behavior match the behavior in main, or redo
# command line options entirely.
try:
opts, args = getopt.getopt(argv, "c:deist:")
except getopt.error, msg:
raise usageError(msg)
for filename in args:
flist.open(filename)
if not args:
flist.new()
def main(self, argv, noshell):
cmd = None
edit = 0
debug = 0
interactive = 0
script = None
startup = 0
global use_subprocess
try:
opts, args = getopt.getopt(sys.argv[1:], "c:deir:st:")
except getopt.error, msg:
sys.stderr.write("Error: %s\n" % str(msg))
sys.stderr.write(usage_msg)
sys.exit(2)
for o, a in opts:
noshell = 0 # There are options, bring up a shell
if o == '-c':
cmd = a
if o == '-d':
debug = 1
if o == '-e':
edit = 1
if o == '-i':
interactive = 1
if o == '-r':
edit = 1
script = a
use_subprocess = 1
if o == '-s':
startup = 1
if o == '-t':
PyShell.shell_title = a
if noshell: edit=1
if interactive and args and args[0] != "-": edit = 1
for i in range(len(sys.path)):
sys.path[i] = os.path.abspath(sys.path[i])
pathx = []
if edit:
for filename in args:
pathx.append(os.path.dirname(filename))
elif args and args[0] != "-":
pathx.append(os.path.dirname(args[0]))
else:
pathx.append(os.curdir)
for dir in pathx:
dir = os.path.abspath(dir)
if not dir in sys.path:
sys.path.insert(0, dir)
if edit:
for filename in args:
flist.open(filename)
if not args:
flist.new()
else:
if cmd:
sys.argv = ["-c"] + args
else:
sys.argv = args or [""]
if noshell:
flist.pyshell = None
else:
shell = PyShell(flist)
interp = shell.interp
flist.pyshell = shell
if startup:
filename = os.environ.get("IDLESTARTUP") or \
os.environ.get("PYTHONSTARTUP")
if filename and os.path.isfile(filename):
interp.execfile(filename)
if debug:
shell.open_debugger()
if cmd:
interp.execsource(cmd)
elif script:
if os.path.isfile(script):
interp.execfile(script)
else:
print "No script file: ", script
shell.begin()
self.idle()
root.mainloop()
root.destroy()
if __name__ == "__main__":
main()

View File

@ -1,101 +0,0 @@
"""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

View File

@ -1,17 +1,16 @@
"""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>>'),
]
),

View File

@ -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]

View File

@ -51,9 +51,7 @@ cursor-background= black
#[ZoomHeight]
#[ScriptBinding] # disabled in favor of ExecBinding
[ExecBinding]
[ScriptBinding]
[CallTips]

View File

@ -1,64 +0,0 @@
# 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()

View File

@ -1,369 +0,0 @@
"""protocol (David Scherer <dscherer@cmu.edu>)
This module implements a simple RPC or "distributed object" protocol.
I am probably the 100,000th person to write this in Python, but, hey,
it was fun.
Contents:
connectionLost is an exception that will be thrown by functions in
the protocol module or calls to remote methods that fail because
the remote program has closed the socket or because no connection
could be established in the first place.
Server( port=None, connection_hook=None ) creates a server on a
well-known port, to which clients can connect. When a client
connects, a Connection is created for it. If connection_hook
is defined, then connection_hook( socket.getpeername() ) is called
before a Connection is created, and if it returns false then the
connection is refused. connection_hook must be prepared to be
called from any thread.
Client( ip='127.0.0.1', port=None ) returns a Connection to a Server
object at a well-known address and port.
Connection( socket ) creates an RPC connection on an arbitrary socket,
which must already be connected to another program. You do not
need to use this directly if you are using Client() or Server().
publish( name, connect_function ) provides an object with the
specified name to some or all Connections. When another program
calls Connection.getobject() with the specified name, the
specified connect_function is called with the arguments
connect_function( conn, addr )
where conn is the Connection object to the requesting client and
addr is the address returned by socket.getpeername(). If that
function returns an object, that object becomes accessible to
the caller. If it returns None, the caller's request fails.
Connection objects:
.close() refuses additional RPC messages from the peer, and notifies
the peer that the connection has been closed. All pending remote
method calls in either program will fail with a connectionLost
exception. Further remote method calls on this connection will
also result in errors.
.getobject(name) returns a proxy for the remote object with the
specified name, if it exists and the peer permits us access.
Otherwise, it returns None. It may throw a connectionLost
exception. The returned proxy supports basic attribute access
and method calls, and its methods have an extra attribute,
.void, which is a function that has the same effect but always
returns None. This last capability is provided as a performance
hack: object.method.void(params) can return without waiting for
the remote process to respond, but object.method(params) needs
to wait for a return value or exception.
.rpc_loop(block=0) processes *incoming* messages for this connection.
If block=1, it continues processing until an exception or return
value is received, which is normally forever. Otherwise it
returns when all currently pending messages have been delivered.
It may throw a connectionLost exception.
.set_close_hook(f) specifies a function to be called when the remote
object closes the connection during a call to rpc_loop(). This
is a good way for servers to be notified when clients disconnect.
.set_shutdown_hook(f) specifies a function called *immediately* when
the receive loop detects that the connection has been lost. The
provided function must be prepared to run in any thread.
Server objects:
.rpc_loop() processes incoming messages on all connections, and
returns when all pending messages have been processed. It will
*not* throw connectionLost exceptions; the
Connection.set_close_hook() mechanism is much better for servers.
"""
import sys, os, string, types
import socket
from threading import Thread
from Queue import Queue, Empty
from cPickle import Pickler, Unpickler, PicklingError
class connectionLost:
def __init__(self, what=""): self.what = what
def __repr__(self): return self.what
def __str__(self): return self.what
def getmethods(cls):
"Returns a list of the names of the methods of a class."
methods = []
for b in cls.__bases__:
methods = methods + getmethods(b)
d = cls.__dict__
for k in d.keys():
if type(d[k])==types.FunctionType:
methods.append(k)
return methods
class methodproxy:
"Proxy for a method of a remote object."
def __init__(self, classp, name):
self.classp=classp
self.name=name
self.client = classp.client
def __call__(self, *args, **keywords):
return self.client.call( 'm', self.classp.name, self.name, args, keywords )
def void(self, *args, **keywords):
self.client.call_void( 'm', self.classp.name,self.name,args,keywords)
class classproxy:
"Proxy for a remote object."
def __init__(self, client, name, methods):
self.__dict__['client'] = client
self.__dict__['name'] = name
for m in methods:
prox = methodproxy( self, m )
self.__dict__[m] = prox
def __getattr__(self, attr):
return self.client.call( 'g', self.name, attr )
def __setattr__(self, attr, value):
self.client.call_void( 's', self.name, attr, value )
local_connect = {}
def publish(name, connect_function):
local_connect[name]=connect_function
class socketFile:
"File emulator based on a socket. Provides only blocking semantics for now."
def __init__(self, socket):
self.socket = socket
self.buffer = ''
def _recv(self,bytes):
try:
r=self.socket.recv(bytes)
except:
raise connectionLost()
if not r:
raise connectionLost()
return r
def write(self, string):
try:
self.socket.send( string )
except:
raise connectionLost()
def read(self,bytes):
x = bytes-len(self.buffer)
while x>0:
self.buffer=self.buffer+self._recv(x)
x = bytes-len(self.buffer)
s = self.buffer[:bytes]
self.buffer=self.buffer[bytes:]
return s
def readline(self):
while 1:
f = string.find(self.buffer,'\n')
if f>=0:
s = self.buffer[:f+1]
self.buffer=self.buffer[f+1:]
return s
self.buffer = self.buffer + self._recv(1024)
class Connection (Thread):
debug = 0
def __init__(self, socket):
self.local_objects = {}
self.socket = socket
self.name = socket.getpeername()
self.socketfile = socketFile(socket)
self.queue = Queue(-1)
self.refuse_messages = 0
self.cmds = { 'm': self.r_meth,
'g': self.r_get,
's': self.r_set,
'o': self.r_geto,
'e': self.r_exc,
#'r' handled by rpc_loop
}
Thread.__init__(self)
self.setDaemon(1)
self.start()
def getobject(self, name):
methods = self.call( 'o', name )
if methods is None: return None
return classproxy(self, name, methods)
# close_hook is called from rpc_loop(), like a normal remote method
# invocation
def set_close_hook(self,hook): self.close_hook = hook
# shutdown_hook is called directly from the run() thread, and needs
# to be "thread safe"
def set_shutdown_hook(self,hook): self.shutdown_hook = hook
close_hook = None
shutdown_hook = None
def close(self):
self._shutdown()
self.refuse_messages = 1
def call(self, c, *args):
self.send( (c, args, 1 ) )
return self.rpc_loop( block = 1 )
def call_void(self, c, *args):
try:
self.send( (c, args, 0 ) )
except:
pass
# the following methods handle individual RPC calls:
def r_geto(self, obj):
c = local_connect.get(obj)
if not c: return None
o = c(self, self.name)
if not o: return None
self.local_objects[obj] = o
return getmethods(o.__class__)
def r_meth(self, obj, name, args, keywords):
return apply( getattr(self.local_objects[obj],name), args, keywords)
def r_get(self, obj, name):
return getattr(self.local_objects[obj],name)
def r_set(self, obj, name, value):
setattr(self.local_objects[obj],name,value)
def r_exc(self, e, v):
raise e, v
def rpc_exec(self, cmd, arg, ret):
if self.refuse_messages: return
if self.debug: print cmd,arg,ret
if ret:
try:
r=apply(self.cmds.get(cmd), arg)
self.send( ('r', r, 0) )
except:
try:
self.send( ('e', sys.exc_info()[:2], 0) )
except PicklingError:
self.send( ('e', (TypeError, 'Unpicklable exception.'), 0 ) )
else:
# we cannot report exceptions to the caller, so
# we report them in this process.
r=apply(self.cmds.get(cmd), arg)
# the following methods implement the RPC and message loops:
def rpc_loop(self, block=0):
if self.refuse_messages: raise connectionLost('(already closed)')
try:
while 1:
try:
cmd, arg, ret = self.queue.get( block )
except Empty:
return None
if cmd=='r': return arg
self.rpc_exec(cmd,arg,ret)
except connectionLost:
if self.close_hook:
self.close_hook()
self.close_hook = None
raise
def run(self):
try:
while 1:
data = self.recv()
self.queue.put( data )
except:
self.queue.put( ('e', sys.exc_info()[:2], 0) )
# The following send raw pickled data to the peer
def send(self, data):
try:
Pickler(self.socketfile,1).dump( data )
except connectionLost:
self._shutdown()
if self.shutdown_hook: self.shutdown_hook()
raise
def recv(self):
try:
return Unpickler(self.socketfile).load()
except connectionLost:
self._shutdown()
if self.shutdown_hook: self.shutdown_hook()
raise
except:
raise
def _shutdown(self):
try:
self.socket.shutdown(1)
self.socket.close()
except:
pass
class Server (Thread):
default_port = 0x1D1E # "IDlE"
def __init__(self, port=None, connection_hook=None):
self.connections = []
self.port = port or self.default_port
self.connection_hook = connection_hook
try:
self.wellknown = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', self.port))
s.listen(3)
except:
raise connectionLost
Thread.__init__(self)
self.setDaemon(1)
self.start()
def run(self):
s = self.wellknown
while 1:
conn, addr = s.accept()
if self.connection_hook and not self.connection_hook(addr):
try:
conn.shutdown(1)
except:
pass
continue
self.connections.append( Connection(conn) )
def rpc_loop(self):
cns = self.connections[:]
for c in cns:
try:
c.rpc_loop(block = 0)
except connectionLost:
if c in self.connections:
self.connections.remove(c)
def Client(ip='127.0.0.1', port=None):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,port or Server.default_port))
except socket.error, what:
raise connectionLost(str(what))
except:
raise connectionLost()
return Connection(s)

View File

@ -1,58 +0,0 @@
# 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().'