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:
parent
9f709bf9a1
commit
969de458aa
|
@ -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 sys
|
||||||
import os
|
import os
|
||||||
import string
|
import string
|
||||||
|
|
|
@ -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
|
|
|
@ -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 Tkinter import *
|
||||||
from EditorWindow import EditorWindow
|
from EditorWindow import EditorWindow
|
||||||
import re
|
import re
|
||||||
import tkMessageBox
|
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):
|
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
|
"""An editor window that can serve as an output file.
|
||||||
not be trusted.
|
|
||||||
|
Also the future base class for the Python shell window.
|
||||||
|
This class has no input facilities.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
UndoDelegator = OutputUndoDelegator
|
def __init__(self, *args):
|
||||||
source_window = None
|
|
||||||
|
|
||||||
def __init__(self, *args, **keywords):
|
|
||||||
if keywords.has_key('source_window'):
|
|
||||||
self.source_window = keywords['source_window']
|
|
||||||
apply(EditorWindow.__init__, (self,) + args)
|
apply(EditorWindow.__init__, (self,) + args)
|
||||||
self.text.bind("<<goto-file-line>>", self.goto_file_line)
|
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
|
# Customize EditorWindow
|
||||||
|
|
||||||
|
@ -69,9 +24,6 @@ class OutputWindow(EditorWindow):
|
||||||
def short_title(self):
|
def short_title(self):
|
||||||
return "Output"
|
return "Output"
|
||||||
|
|
||||||
def long_title(self):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def maybesave(self):
|
def maybesave(self):
|
||||||
# Override base class method -- don't ask any questions
|
# Override base class method -- don't ask any questions
|
||||||
if self.get_saved():
|
if self.get_saved():
|
||||||
|
@ -79,63 +31,10 @@ class OutputWindow(EditorWindow):
|
||||||
else:
|
else:
|
||||||
return "no"
|
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
|
# Act as output file
|
||||||
|
|
||||||
def write(self, s, tags=(), mark="iomark"):
|
def write(self, s, tags=(), mark="insert"):
|
||||||
self.text.mark_gravity(mark, RIGHT)
|
self.text.insert(mark, str(s), tags)
|
||||||
self.text.insert(mark, s, tags)
|
|
||||||
self.text.mark_gravity(mark, LEFT)
|
|
||||||
self.text.see(mark)
|
self.text.see(mark)
|
||||||
self.text.update()
|
self.text.update()
|
||||||
|
|
||||||
|
@ -183,14 +82,8 @@ class OutputWindow(EditorWindow):
|
||||||
master=self.text)
|
master=self.text)
|
||||||
return
|
return
|
||||||
filename, lineno = result
|
filename, lineno = result
|
||||||
edit = self.untitled(filename) or self.flist.open(filename)
|
edit = self.flist.open(filename)
|
||||||
edit.gotoline(lineno)
|
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):
|
def _file_line_helper(self, line):
|
||||||
for prog in self.file_line_progs:
|
for prog in self.file_line_progs:
|
||||||
|
@ -200,7 +93,6 @@ class OutputWindow(EditorWindow):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
filename, lineno = m.group(1, 2)
|
filename, lineno = m.group(1, 2)
|
||||||
if not self.untitled(filename):
|
|
||||||
try:
|
try:
|
||||||
f = open(filename, "r")
|
f = open(filename, "r")
|
||||||
f.close()
|
f.close()
|
||||||
|
@ -211,69 +103,53 @@ class OutputWindow(EditorWindow):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# This classes now used by ExecBinding.py:
|
# These classes are currently not used but might come in handy
|
||||||
|
|
||||||
class OnDemandOutputWindow:
|
class OnDemandOutputWindow:
|
||||||
source_window = None
|
|
||||||
|
|
||||||
tagdefs = {
|
tagdefs = {
|
||||||
# XXX Should use IdlePrefs.ColorPrefs
|
# XXX Should use IdlePrefs.ColorPrefs
|
||||||
"stdin": {"foreground": "black"},
|
|
||||||
"stdout": {"foreground": "blue"},
|
"stdout": {"foreground": "blue"},
|
||||||
"stderr": {"foreground": "red"},
|
"stderr": {"foreground": "#007700"},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, flist):
|
def __init__(self, flist):
|
||||||
self.flist = flist
|
self.flist = flist
|
||||||
self.owin = None
|
self.owin = None
|
||||||
self.title = "Output"
|
|
||||||
self.close_hook = None
|
|
||||||
self.old_close = None
|
|
||||||
|
|
||||||
def owclose(self):
|
def write(self, s, tags, mark):
|
||||||
if self.close_hook:
|
if not self.owin:
|
||||||
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:
|
|
||||||
self.setup()
|
self.setup()
|
||||||
self.owin.write(s, tags, mark)
|
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):
|
def setup(self):
|
||||||
self.owin = owin = OutputWindow(self.flist, source_window = self.source_window)
|
self.owin = owin = OutputWindow(self.flist)
|
||||||
owin.short_title = lambda self=self: self.title
|
|
||||||
text = owin.text
|
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():
|
for tag, cnf in self.tagdefs.items():
|
||||||
if cnf:
|
if cnf:
|
||||||
apply(text.tag_configure, (tag,), cnf)
|
apply(text.tag_configure, (tag,), cnf)
|
||||||
text.tag_raise('sel')
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,10 @@
|
||||||
#! /usr/bin/env python
|
#! /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 os
|
||||||
import spawn
|
|
||||||
import sys
|
import sys
|
||||||
import string
|
import string
|
||||||
import getopt
|
import getopt
|
||||||
import re
|
import re
|
||||||
import protocol
|
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -44,13 +20,14 @@ from EditorWindow import EditorWindow, fixwordbreaks
|
||||||
from FileList import FileList
|
from FileList import FileList
|
||||||
from ColorDelegator import ColorDelegator
|
from ColorDelegator import ColorDelegator
|
||||||
from UndoDelegator import UndoDelegator
|
from UndoDelegator import UndoDelegator
|
||||||
from OutputWindow import OutputWindow, OnDemandOutputWindow
|
from OutputWindow import OutputWindow
|
||||||
from configHandler import idleConf
|
from configHandler import idleConf
|
||||||
import idlever
|
import idlever
|
||||||
|
|
||||||
import rpc
|
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__
|
# Change warnings module to write to sys.__stderr__
|
||||||
try:
|
try:
|
||||||
|
@ -204,9 +181,6 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||||
InteractiveInterpreter.__init__(self, locals=locals)
|
InteractiveInterpreter.__init__(self, locals=locals)
|
||||||
self.save_warnings_filters = None
|
self.save_warnings_filters = None
|
||||||
|
|
||||||
global flist
|
|
||||||
self.output = OnDemandOutputWindow(flist)
|
|
||||||
|
|
||||||
rpcclt = None
|
rpcclt = None
|
||||||
rpcpid = None
|
rpcpid = None
|
||||||
|
|
||||||
|
@ -226,14 +200,14 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||||
if i > 3:
|
if i > 3:
|
||||||
print >>sys.__stderr__, "Socket error:", err, "; retry..."
|
print >>sys.__stderr__, "Socket error:", err, "; retry..."
|
||||||
else:
|
else:
|
||||||
# XXX Make this a dialog?
|
# XXX Make this a dialog? #GvR
|
||||||
print >>sys.__stderr__, "Can't spawn subprocess!"
|
print >>sys.__stderr__, "Can't spawn subprocess!"
|
||||||
|
# XXX Add Stephen's error msg, resolve the two later... KBK 09Jun02
|
||||||
|
display_port_binding_error()
|
||||||
return
|
return
|
||||||
self.output.stdout=PseudoFile(self.output, "stdout")
|
self.rpcclt.register("stdin", self.tkconsole)
|
||||||
self.output.stderr=PseudoFile(self.output, "stderr")
|
self.rpcclt.register("stdout", self.tkconsole.stdout)
|
||||||
self.rpcclt.register("stdin", self.output)
|
self.rpcclt.register("stderr", self.tkconsole.stderr)
|
||||||
self.rpcclt.register("stdout", self.output.stdout)
|
|
||||||
self.rpcclt.register("stderr", self.output.stderr)
|
|
||||||
self.rpcclt.register("flist", self.tkconsole.flist)
|
self.rpcclt.register("flist", self.tkconsole.flist)
|
||||||
self.poll_subprocess()
|
self.poll_subprocess()
|
||||||
|
|
||||||
|
@ -629,7 +603,7 @@ class PyShell(OutputWindow):
|
||||||
|
|
||||||
def begin(self):
|
def begin(self):
|
||||||
self.resetoutput()
|
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,
|
(sys.version, sys.platform, self.COPYRIGHT,
|
||||||
idlever.IDLE_VERSION))
|
idlever.IDLE_VERSION))
|
||||||
try:
|
try:
|
||||||
|
@ -795,8 +769,9 @@ class PyShell(OutputWindow):
|
||||||
i = i-1
|
i = i-1
|
||||||
line = line[:i]
|
line = line[:i]
|
||||||
more = self.interp.runsource(line)
|
more = self.interp.runsource(line)
|
||||||
if not more:
|
# XXX This was causing extra prompt with shell KBK
|
||||||
self.showprompt()
|
# if not more:
|
||||||
|
# self.showprompt()
|
||||||
|
|
||||||
def cancel_check(self, frame, what, args,
|
def cancel_check(self, frame, what, args,
|
||||||
dooneevent=tkinter.dooneevent,
|
dooneevent=tkinter.dooneevent,
|
||||||
|
@ -876,6 +851,7 @@ class PseudoFile:
|
||||||
def isatty(self):
|
def isatty(self):
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
usage_msg = """\
|
usage_msg = """\
|
||||||
usage: idle.py [-c command] [-d] [-i] [-r script] [-s] [-t title] [arg] ...
|
usage: idle.py [-c command] [-d] [-i] [-r script] [-s] [-t title] [arg] ...
|
||||||
|
|
||||||
|
@ -886,109 +862,19 @@ idle file(s) (without options) edit the file(s)
|
||||||
-e edit mode; arguments are files to be edited
|
-e edit mode; arguments are files to be edited
|
||||||
-i open an interactive shell
|
-i open an interactive shell
|
||||||
-i file(s) open a shell and also an editor window for each file
|
-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
|
-s run $IDLESTARTUP or $PYTHONSTARTUP before anything else
|
||||||
-t title set title of shell window
|
-t title set title of shell window
|
||||||
|
|
||||||
Remaining arguments are applied to the command (-c) or script (-r).
|
Remaining arguments are applied to the command (-c) or script (-r).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class usageError:
|
def main():
|
||||||
def __init__(self, string): self.string = string
|
|
||||||
def __repr__(self): return self.string
|
|
||||||
|
|
||||||
class main:
|
|
||||||
def __init__(self, noshell=1):
|
|
||||||
|
|
||||||
global flist, root
|
|
||||||
root = Tk(className="Idle")
|
|
||||||
fixwordbreaks(root)
|
|
||||||
root.withdraw()
|
|
||||||
flist = PyShellFileList(root)
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
#maybe the following should be handled by a tkmessagebox for
|
|
||||||
#users who don't start idle from a console??
|
|
||||||
print """\
|
|
||||||
IDLE cannot run.
|
|
||||||
|
|
||||||
IDLE needs to use a specific TCP/IP port (7454) 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
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
cmd = None
|
||||||
edit = 0
|
edit = 0
|
||||||
debug = 0
|
debug = 0
|
||||||
interactive = 0
|
|
||||||
script = None
|
script = None
|
||||||
startup = 0
|
startup = 0
|
||||||
global use_subprocess
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "c:deir:st:")
|
opts, args = getopt.getopt(sys.argv[1:], "c:deir:st:")
|
||||||
|
@ -998,26 +884,20 @@ be a security risk on a single-user machine.
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
noshell = 0 # There are options, bring up a shell
|
|
||||||
if o == '-c':
|
if o == '-c':
|
||||||
cmd = a
|
cmd = a
|
||||||
if o == '-d':
|
if o == '-d':
|
||||||
debug = 1
|
debug = 1
|
||||||
if o == '-e':
|
if o == '-e':
|
||||||
edit = 1
|
edit = 1
|
||||||
if o == '-i':
|
|
||||||
interactive = 1
|
|
||||||
if o == '-r':
|
if o == '-r':
|
||||||
edit = 1
|
|
||||||
script = a
|
script = a
|
||||||
use_subprocess = 1
|
|
||||||
if o == '-s':
|
if o == '-s':
|
||||||
startup = 1
|
startup = 1
|
||||||
if o == '-t':
|
if o == '-t':
|
||||||
PyShell.shell_title = a
|
PyShell.shell_title = a
|
||||||
|
|
||||||
if noshell: edit=1
|
if args and args[0] != "-": edit = 1
|
||||||
if interactive and args and args[0] != "-": edit = 1
|
|
||||||
|
|
||||||
for i in range(len(sys.path)):
|
for i in range(len(sys.path)):
|
||||||
sys.path[i] = os.path.abspath(sys.path[i])
|
sys.path[i] = os.path.abspath(sys.path[i])
|
||||||
|
@ -1035,6 +915,12 @@ be a security risk on a single-user machine.
|
||||||
if not dir in sys.path:
|
if not dir in sys.path:
|
||||||
sys.path.insert(0, dir)
|
sys.path.insert(0, dir)
|
||||||
|
|
||||||
|
global flist, root
|
||||||
|
root = Tk(className="Idle")
|
||||||
|
fixwordbreaks(root)
|
||||||
|
root.withdraw()
|
||||||
|
flist = PyShellFileList(root)
|
||||||
|
|
||||||
if edit:
|
if edit:
|
||||||
for filename in args:
|
for filename in args:
|
||||||
flist.open(filename)
|
flist.open(filename)
|
||||||
|
@ -1046,9 +932,6 @@ be a security risk on a single-user machine.
|
||||||
else:
|
else:
|
||||||
sys.argv = args or [""]
|
sys.argv = args or [""]
|
||||||
|
|
||||||
if noshell:
|
|
||||||
flist.pyshell = None
|
|
||||||
else:
|
|
||||||
shell = PyShell(flist)
|
shell = PyShell(flist)
|
||||||
interp = shell.interp
|
interp = shell.interp
|
||||||
flist.pyshell = shell
|
flist.pyshell = shell
|
||||||
|
@ -1069,11 +952,25 @@ be a security risk on a single-user machine.
|
||||||
else:
|
else:
|
||||||
print "No script file: ", script
|
print "No script file: ", script
|
||||||
shell.begin()
|
shell.begin()
|
||||||
|
|
||||||
self.idle()
|
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
root.destroy()
|
root.destroy()
|
||||||
|
|
||||||
|
def display_port_binding_error():
|
||||||
|
print """\
|
||||||
|
IDLE cannot run.
|
||||||
|
|
||||||
|
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. 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. Its use of port 8833 should not
|
||||||
|
be a security risk on a single-user machine.
|
||||||
|
"""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
"""Extension to execute code outside the Python shell window.
|
"""Extension to execute code outside the Python shell window.
|
||||||
|
|
||||||
This adds the following commands (to the Edit menu, until there's a
|
This adds the following commands:
|
||||||
separate Python menu):
|
|
||||||
|
|
||||||
- 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.
|
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
|
current module. The window must have been saved previously. The
|
||||||
module is added to sys.modules, and is also added to the __main__
|
module is added to sys.modules, and is also added to the __main__
|
||||||
namespace. Output goes to the shell window.
|
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.
|
code in the __main__ namespace.
|
||||||
|
|
||||||
XXX Redesign this interface (yet again) as follows:
|
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 \
|
To fix case 2, change all tabs to spaces by using Select All followed \
|
||||||
by Untabify Region (both in the Edit menu)."""
|
by Untabify Region (both in the Edit menu)."""
|
||||||
|
|
||||||
|
|
||||||
|
# XXX TBD Implement stop-execution KBK 11Jun02
|
||||||
class ScriptBinding:
|
class ScriptBinding:
|
||||||
|
|
||||||
menudefs = [
|
menudefs = [
|
||||||
('edit', [None,
|
('run', [None,
|
||||||
('Check module', '<<check-module>>'),
|
# ('Check module', '<<check-module>>'),
|
||||||
('Import module', '<<import-module>>'),
|
# ('Import module', '<<import-module>>'),
|
||||||
('Run script', '<<run-script>>'),
|
('Run script', '<<run-script>>'),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
|
@ -42,17 +42,17 @@ enable=1
|
||||||
[ZoomHeight_cfgBindings]
|
[ZoomHeight_cfgBindings]
|
||||||
zoom-height=<Alt-Key-F2>
|
zoom-height=<Alt-Key-F2>
|
||||||
|
|
||||||
[ExecBinding]
|
#[ExecBinding] # Revert to ScriptBinding
|
||||||
enable=1
|
#enable=1
|
||||||
[ExecBinding_cfgBindings]
|
#[ExecBinding_cfgBindings]
|
||||||
run-complete-script=<Key-F5>
|
#run-complete-script=<Key-F5>
|
||||||
stop-execution=<Key-Cancel>
|
#stop-execution=<Key-Cancel>
|
||||||
|
|
||||||
#[ScriptBinding] #currently ExecBinding has replaced ScriptBinding
|
[ScriptBinding]
|
||||||
#enable=0
|
enable=1
|
||||||
#[ScriptBinding_cfgBindings]
|
[ScriptBinding_cfgBindings]
|
||||||
#run-script=<Key-F5>
|
run-script=<Key-F5>
|
||||||
#check-module=<Alt-Key-F5> <Meta-Key-F5>
|
#check-module=<Alt-Key-F5>
|
||||||
#import-module=<Control-Key-F5>
|
#import-module=<Control-Key-F5>
|
||||||
|
|
||||||
[CallTips]
|
[CallTips]
|
||||||
|
|
|
@ -51,9 +51,7 @@ cursor-background= black
|
||||||
|
|
||||||
#[ZoomHeight]
|
#[ZoomHeight]
|
||||||
|
|
||||||
#[ScriptBinding] # disabled in favor of ExecBinding
|
[ScriptBinding]
|
||||||
|
|
||||||
[ExecBinding]
|
|
||||||
|
|
||||||
[CallTips]
|
[CallTips]
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
|
@ -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)
|
|
|
@ -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().'
|
|
Loading…
Reference in New Issue