#!/usr/bin/python3 # Main interpreter entry for webpython import io, select, sys, os, threading, code import pickle, struct, builtins, json #, ressource from queue import Queue from argparse import ArgumentParser # hard limit to 64M #try: # resource.setrlimit(resource.RLIMIT_AS, (1<<26, 1<<26)) #except ValueError: # tried to raise it # pass # output limit 16MiB output_capacity = 16*1024*1024 # adapted from IDLE (PyShell.py) class PseudoFile(io.TextIOBase): def __init__(self, shell, name): self.shell = shell self._name = name @property def encoding(self): return "UTF-8" @property def name(self): return '<%s>' % self._name def isatty(self): return True class PseudoInputFile(PseudoFile): def __init__(self, shell, name): PseudoFile.__init__(self, shell, name) self._line_buffer = '' def readable(self): return True def read(self, size=-1): if self.closed: raise ValueError("read from closed file") if size is None: size = -1 elif not isinstance(size, int): raise TypeError('must be int, not ' + type(size).__name__) result = self._line_buffer self._line_buffer = '' if size < 0: while True: line = self.shell.readline() if not line: break result += line else: while len(result) < size: line = self.shell.readline() if not line: break result += line self._line_buffer = result[size:] result = result[:size] return result def readline(self, size=-1): if self.closed: raise ValueError("read from closed file") if size is None: size = -1 elif not isinstance(size, int): raise TypeError('must be int, not ' + type(size).__name__) line = self._line_buffer or self.shell.readline() if size < 0: size = len(line) self._line_buffer = line[size:] return line[:size] def close(self): self.shell.close() class PseudoOutputFile(PseudoFile): def writable(self): return True def write(self, s): if self.closed: raise ValueError("write to closed file") if not isinstance(s, str): raise TypeError('must be str, not ' + type(s).__name__) return self.shell.write(s, self._name) # RPC proxy orig_stdin = sys.stdin orig_stdout = sys.stdout orig_stderr = sys.stderr class Shell: def __init__(self): self.stdin = io.FileIO(0) self.buf = b'' self.canvas = [] self.messages = [] self.capacity = output_capacity # PseudoFile interaction #def readline(self): # self.sendpickle({'cmd':'readline', # 'stream':'stdin', # }) # return self.inputq.get() def write(self, data, name): self.sendpickle({'cmd':'write', 'stream':name, 'data':data }) def input(self, prompt=''): self.sendpickle({'cmd':'input', 'stream':'stdin', 'data':prompt}) result = self.receivemsg() return result['data'] # internal def sendpickle(self, data): data = json.dumps(data) + "\n\r" self.capacity -= len(data) if self.capacity < 0: data = json.dumps({'cmd':'stop', 'timedout':True}, 2) orig_stdout.write(data) raise SystemExit orig_stdout.write(data) def receivepickle(self): msg = json.loads(orig_stdin.readline()) if msg['cmd'] == 'canvasevent': self.canvas.append(msg) else: self.messages.append(msg) def receivemsg(self): while not self.messages: self.receivepickle() return self.messages.pop() def receivecanvas(self): while not self.canvas: self.receivepickle() return self.canvas.pop(0) # Hide 0/1 from sys shell = Shell() sys.__stdin__ = sys.stdin = PseudoInputFile(shell, 'stdin') sys.__stdout__ = sys.stdout = PseudoOutputFile(shell, 'stdout') #sys.__stderr__ = sys.stderr = PseudoOutputFile(shell, 'stderr') builtins.input = shell.input #iothread = threading.Thread(target=shell.run) #iothread.start() if __name__ == '__main__': parser = ArgumentParser(description='A python interpreter that generates json commands based on the standard I/O streams.') parser.add_argument('-f', '--filename', type=str, required=True, default='exercise.py', help='Python file to be interpreted.') args = parser.parse_args() filepath = os.path.join("/", "workspace", args.filename) with open(filepath, "r", encoding='utf-8') as f: script = f.read() c = compile(script, args.filename, 'exec') exec(c, {}) # work-around for docker not terminating properly shell.sendpickle({'cmd':'exit'})