diff --git a/Tools/idle/RemoteInterp.py b/Tools/idle/RemoteInterp.py new file mode 100644 index 00000000000..3ad574b01d2 --- /dev/null +++ b/Tools/idle/RemoteInterp.py @@ -0,0 +1,343 @@ +import select +import socket +import struct +import sys +import types + +VERBOSE = None + +class SocketProtocol: + """A simple protocol for sending strings across a socket""" + BUF_SIZE = 8192 + + def __init__(self, sock): + self.sock = sock + self._buffer = '' + self._closed = 0 + + def close(self): + self._closed = 1 + self.sock.close() + + def send(self, buf): + """Encode buf and write it on the socket""" + if VERBOSE: + VERBOSE.write('send %d:%s\n' % (len(buf), `buf`)) + self.sock.send('%d:%s' % (len(buf), buf)) + + def receive(self, timeout=0): + """Get next complete string from socket or return None + + Raise EOFError on EOF + """ + buf = self._read_from_buffer() + if buf is not None: + return buf + recvbuf = self._read_from_socket(timeout) + if recvbuf is None: + return None + if recvbuf == '' and self._buffer == '': + raise EOFError + if VERBOSE: + VERBOSE.write('recv %s\n' % `recvbuf`) + self._buffer = self._buffer + recvbuf + r = self._read_from_buffer() + return r + + def _read_from_socket(self, timeout): + """Does not block""" + if self._closed: + return '' + if timeout is not None: + r, w, x = select.select([self.sock], [], [], timeout) + if timeout is None or r: + return self.sock.recv(self.BUF_SIZE) + else: + return None + + def _read_from_buffer(self): + buf = self._buffer + i = buf.find(':') + if i == -1: + return None + buflen = int(buf[:i]) + enclen = i + 1 + buflen + if len(buf) >= enclen: + s = buf[i+1:enclen] + self._buffer = buf[enclen:] + return s + else: + self._buffer = buf + return None + +# helpers for registerHandler method below + +def get_methods(obj): + methods = [] + for name in dir(obj): + attr = getattr(obj, name) + if callable(attr): + methods.append(name) + if type(obj) == types.InstanceType: + methods = methods + get_methods(obj.__class__) + if type(obj) == types.ClassType: + for super in obj.__bases__: + methods = methods + get_methods(super) + return methods + +class CommandProtocol: + def __init__(self, sockp): + self.sockp = sockp + self.seqno = 0 + self.handlers = {} + + def close(self): + self.sockp.close() + self.handlers.clear() + + def registerHandler(self, handler): + """A Handler is an object with handle_XXX methods""" + for methname in get_methods(handler): + if methname[:7] == "handle_": + name = methname[7:] + self.handlers[name] = getattr(handler, methname) + + def send(self, cmd, arg='', seqno=None): + if arg: + msg = "%s %s" % (cmd, arg) + else: + msg = cmd + if seqno is None: + seqno = self.get_seqno() + msgbuf = self.encode_seqno(seqno) + msg + self.sockp.send(msgbuf) + if cmd == "reply": + return + reply = self.sockp.receive(timeout=None) + r_cmd, r_arg, r_seqno = self._decode_msg(reply) + assert r_seqno == seqno and r_cmd == "reply", "bad reply" + return r_arg + + def _decode_msg(self, msg): + seqno = self.decode_seqno(msg[:self.SEQNO_ENC_LEN]) + msg = msg[self.SEQNO_ENC_LEN:] + parts = msg.split(" ", 2) + if len(parts) == 1: + cmd = msg + arg = '' + else: + cmd = parts[0] + arg = parts[1] + return cmd, arg, seqno + + def dispatch(self): + msg = self.sockp.receive() + if msg is None: + return + cmd, arg, seqno = self._decode_msg(msg) + self._current_reply = seqno + h = self.handlers.get(cmd, self.default_handler) + try: + r = h(arg) + except TypeError, msg: + raise TypeError, "handle_%s: %s" % (cmd, msg) + if self._current_reply is None: + if r is not None: + sys.stderr.write("ignoring %s return value type %s\n" % \ + (cmd, type(r).__name__)) + return + if r is None: + r = '' + if type(r) != types.StringType: + raise ValueError, "invalid return type for %s" % cmd + self.send("reply", r, seqno=seqno) + + def reply(self, arg=''): + """Send a reply immediately + + otherwise reply will be sent when handler returns + """ + self.send("reply", arg, self._current_reply) + self._current_reply = None + + def default_handler(self, arg): + sys.stderr.write("WARNING: unhandled message %s\n" % arg) + return '' + + SEQNO_ENC_LEN = 4 + + def get_seqno(self): + seqno = self.seqno + self.seqno = seqno + 1 + return seqno + + def encode_seqno(self, seqno): + return struct.pack("I", seqno) + + def decode_seqno(self, buf): + return struct.unpack("I", buf)[0] + + +class StdioRedirector: + """Redirect sys.std{in,out,err} to a set of file-like objects""" + + def __init__(self, stdin, stdout, stderr): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + + def redirect(self): + self.save() + sys.stdin = self.stdin + sys.stdout = self.stdout + sys.stderr = self.stderr + + def save(self): + self._stdin = sys.stdin + self._stdout = sys.stdout + self._stderr = sys.stderr + + def restore(self): + sys.stdin = self._stdin + sys.stdout = self._stdout + sys.stderr = self._stderr + +class IOWrapper: + """Send output from a file-like object across a SocketProtocol + + XXX Should this be more tightly integrated with the CommandProtocol? + """ + + def __init__(self, name, cmdp): + self.name = name + self.cmdp = cmdp + self.buffer = [] + +class InputWrapper(IOWrapper): + def write(self, buf): + # XXX what should this do on Windows? + raise IOError, (9, '[Errno 9] Bad file descriptor') + + def read(self, arg=None): + if arg is not None: + if arg <= 0: + return '' + else: + arg = 0 + return self.cmdp.send(self.name, "read,%s" % arg) + + def readline(self): + return self.cmdp.send(self.name, "readline") + +class OutputWrapper(IOWrapper): + def write(self, buf): + self.cmdp.send(self.name, buf) + + def read(self, arg=None): + return '' + +class RemoteInterp: + def __init__(self, sock): + self._sock = SocketProtocol(sock) + self._cmd = CommandProtocol(self._sock) + self._cmd.registerHandler(self) + + def run(self): + try: + while 1: + self._cmd.dispatch() + except EOFError: + pass + + def handle_execfile(self, arg): + self._cmd.reply() + io = StdioRedirector(InputWrapper("stdin", self._cmd), + OutputWrapper("stdout", self._cmd), + OutputWrapper("stderr", self._cmd)) + io.redirect() + execfile(arg, {'__name__':'__main__'}) + io.restore() + self._cmd.send("terminated") + + def handle_quit(self, arg): + self._cmd.reply() + self._cmd.close() + +def startRemoteInterp(id): + import os + # UNIX domain sockets are simpler for starters + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind("/var/tmp/ri.%s" % id) + try: + sock.listen(1) + cli, addr = sock.accept() + rinterp = RemoteInterp(cli) + rinterp.run() + finally: + os.unlink("/var/tmp/ri.%s" % id) + +class RIClient: + """Client of the remote interpreter""" + def __init__(self, sock): + self._sock = SocketProtocol(sock) + self._cmd = CommandProtocol(self._sock) + self._cmd.registerHandler(self) + + def execfile(self, file): + self._cmd.send("execfile", file) + + def run(self): + try: + while 1: + self._cmd.dispatch() + except EOFError: + pass + + def handle_stdout(self, buf): + sys.stdout.write(buf) +## sys.stdout.flush() + + def handle_stderr(self, buf): + sys.stderr.write(buf) + + def handle_stdin(self, arg): + if arg == "readline": + return sys.stdin.readline() + i = arg.find(",") + 1 + bytes = int(arg[i:]) + if bytes == 0: + return sys.stdin.read() + else: + return sys.stdin.read(bytes) + + def handle_terminated(self, arg): + self._cmd.reply() + self._cmd.send("quit") + self._cmd.close() + +def riExec(id, file): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect("/var/tmp/ri.%s" % id) + cli = RIClient(sock) + cli.execfile(file) + cli.run() + +if __name__ == "__main__": + import sys + import getopt + + SERVER = 1 + opts, args = getopt.getopt(sys.argv[1:], 'cv') + for o, v in opts: + if o == '-c': + SERVER = 0 + elif o == '-v': + VERBOSE = sys.stderr + id = args[0] + + if SERVER: + startRemoteInterp(id) + else: + file = args[1] + riExec(id, file) +