Merge remote-tracking branch 'origin/master' into statistics

This commit is contained in:
Maximilian Grundke
2015-10-29 10:24:30 +01:00
17 changed files with 65 additions and 4531 deletions

View File

@@ -50,6 +50,7 @@ GEM
bootstrap-will_paginate (0.0.10) bootstrap-will_paginate (0.0.10)
will_paginate will_paginate
builder (3.2.2) builder (3.2.2)
byebug (6.0.2)
capistrano (3.3.5) capistrano (3.3.5)
capistrano-stats (~> 1.1.0) capistrano-stats (~> 1.1.0)
i18n i18n
@@ -108,6 +109,7 @@ GEM
json json
erubis (2.7.0) erubis (2.7.0)
eventmachine (1.0.8) eventmachine (1.0.8)
eventmachine (1.0.8-java)
excon (0.45.2) excon (0.45.2)
execjs (2.5.2) execjs (2.5.2)
factory_girl (4.5.0) factory_girl (4.5.0)
@@ -321,6 +323,8 @@ GEM
websocket (1.2.1) websocket (1.2.1)
websocket-driver (0.6.2) websocket-driver (0.6.2)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-driver (0.6.2-java)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2) websocket-extensions (0.1.2)
will_paginate (3.0.7) will_paginate (3.0.7)
xpath (2.0.0) xpath (2.0.0)
@@ -337,6 +341,7 @@ DEPENDENCIES
better_errors better_errors
binding_of_caller binding_of_caller
bootstrap-will_paginate bootstrap-will_paginate
byebug
capistrano (~> 3.3.0) capistrano (~> 3.3.0)
capistrano-rails capistrano-rails
capistrano-rvm capistrano-rvm
@@ -387,3 +392,6 @@ DEPENDENCIES
uglifier (>= 1.3.0) uglifier (>= 1.3.0)
web-console (~> 2.0) web-console (~> 2.0)
will_paginate (~> 3.0) will_paginate (~> 3.0)
BUNDLED WITH
1.10.6

View File

@@ -28,15 +28,16 @@ Execute: vagrant box add ubuntu/trusty64
Execute: vagrant up Execute: vagrant up
Install docker environments Install docker environments
export DOCKER_HOST=tcp://192.168.23.75:2375 export DOCKER_HOST=tcp://192.168.23.75:2375
docker pull openhpi/docker_java
docker pull openhpi/docker_ruby
docker pull openhpi/docker_python
(The following images need to be moved to openhpi/docker_[coffee|sqlite|etc.] if they are required at some point.
docker pull jprberlin/ubuntu-coffee docker pull jprberlin/ubuntu-coffee
docker pull jprberlin/ubuntu-java
docker pull jprberlin/ubuntu-sqlite docker pull jprberlin/ubuntu-sqlite
docker pull jprberlin/ubuntu-sinatra docker pull jprberlin/ubuntu-sinatra
docker pull jprberlin/ubuntu-ruby
docker pull jprberlin/ubuntu-python
docker pull jprberlin/ubuntu-node
docker pull jprberlin/ubuntu-html docker pull jprberlin/ubuntu-html
docker pull jprberlin/ubuntu-jruby docker pull jprberlin/ubuntu-jruby)
cd repoPath cd repoPath
bundle install bundle install

View File

@@ -30,7 +30,7 @@ $(function() {
numMessages = 0, numMessages = 0,
turtlecanvas = $('#turtlecanvas'), turtlecanvas = $('#turtlecanvas'),
prompt = $('#prompt'), prompt = $('#prompt'),
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'status'], commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'timeout', 'status'],
streams = ['stdin', 'stdout', 'stderr']; streams = ['stdin', 'stdout', 'stderr'];
var ENTER_KEY_CODE = 13; var ENTER_KEY_CODE = 13;
@@ -686,8 +686,8 @@ $(function() {
}; };
var isBrowserSupported = function() { var isBrowserSupported = function() {
// todo event streams are no longer required with websockets // eventsource tests for server send events (used for scoring), websockets is used for run
return window.EventSource !== undefined; return Modernizr.eventsource && Modernizr.websockets;
}; };
var populatePanel = function(panel, result, index) { var populatePanel = function(panel, result, index) {
@@ -1004,7 +1004,7 @@ $(function() {
}; };
var showTimeoutMessage = function() { var showTimeoutMessage = function() {
$.flash.danger({ $.flash.info({
icon: ['fa', 'fa-clock-o'], icon: ['fa', 'fa-clock-o'],
text: $('#editor').data('message-timeout') text: $('#editor').data('message-timeout')
}); });
@@ -1059,14 +1059,6 @@ $(function() {
running = false; running = false;
toggleButtonStates(); toggleButtonStates();
hidePrompt(); hidePrompt();
flashKillMessage();
}
var flashKillMessage = function() {
$.flash.info({
icon: ['fa', 'fa-clock-o'],
text: "Your program was stopped." // todo get data attribute
});
} }
// todo set this from websocket command, required to e.g. stop container // todo set this from websocket command, required to e.g. stop container
@@ -1125,7 +1117,7 @@ $(function() {
}; };
var initWebsocketConnection = function(url) { var initWebsocketConnection = function(url) {
websocket = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + url); websocket = new WebSocket('wss://' + window.location.hostname + ':' + window.location.port + url);
websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection
websocket.onclose = function(evt) { /* expected at some point */ }; websocket.onclose = function(evt) { /* expected at some point */ };
websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); }; websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); };
@@ -1160,7 +1152,7 @@ $(function() {
} }
switch(msg.cmd) { switch(msg.cmd) {
case 'input': case 'input':
showPrompt(); showPrompt(msg);
break; break;
case 'write': case 'write':
printWebsocketOutput(msg); printWebsocketOutput(msg);
@@ -1176,6 +1168,10 @@ $(function() {
case 'exit': case 'exit':
killWebsocketAndContainer(); killWebsocketAndContainer();
break; break;
case 'timeout':
// just show the timeout message here. Another exit command is sent by the rails backend when the socket to the docker container closes.
showTimeoutMessage();
break;
case 'status': case 'status':
showStatus(msg) showStatus(msg)
break; break;
@@ -1249,11 +1245,13 @@ $(function() {
executeWebsocketCommand(msg); executeWebsocketCommand(msg);
}; };
var showPrompt = function() { var showPrompt = function(msg) {
if (prompt.isPresent() && prompt.hasClass('hidden')) { var label = $('#prompt .input-group-addon');
label.text(msg.data || label.data('prompt'));
if (prompt.isPresent() && prompt.hasClass('hidden')) {
prompt.removeClass('hidden'); prompt.removeClass('hidden');
} }
prompt.focus(); $('#prompt input').focus();
} }
var hidePrompt = function() { var hidePrompt = function() {

View File

@@ -0,0 +1,3 @@
/*! modernizr 3.1.0 (Custom Build) | MIT *
* http://modernizr.com/download/?-eventsource-websockets !*/
!function(e,n,s){function o(e,n){return typeof e===n}function a(e){var n=l.className,s=Modernizr._config.classPrefix||"";if(f&&(n=n.baseVal),Modernizr._config.enableJSClass){var o=new RegExp("(^|\\s)"+s+"no-js(\\s|$)");n=n.replace(o,"$1"+s+"js$2")}Modernizr._config.enableClasses&&(n+=" "+s+e.join(" "+s),f?l.className.baseVal=n:l.className=n)}function t(){var e,n,s,a,t,l,f;for(var r in c){if(e=[],n=c[r],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(s=0;s<n.options.aliases.length;s++)e.push(n.options.aliases[s].toLowerCase());for(a=o(n.fn,"function")?n.fn():n.fn,t=0;t<e.length;t++)l=e[t],f=l.split("."),1===f.length?Modernizr[f[0]]=a:(!Modernizr[f[0]]||Modernizr[f[0]]instanceof Boolean||(Modernizr[f[0]]=new Boolean(Modernizr[f[0]])),Modernizr[f[0]][f[1]]=a),i.push((a?"":"no-")+f.join("-"))}}var i=[],l=n.documentElement,f="svg"===l.nodeName.toLowerCase(),c=[],r={_version:"3.1.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var s=this;setTimeout(function(){n(s[e])},0)},addTest:function(e,n,s){c.push({name:e,fn:n,options:s})},addAsyncTest:function(e){c.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=r,Modernizr=new Modernizr,Modernizr.addTest("websockets","WebSocket"in e&&2===e.WebSocket.CLOSING),Modernizr.addTest("eventsource","EventSource"in e),t(),a(i),delete r.addTest,delete r.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();e.Modernizr=Modernizr}(window,document);

View File

@@ -90,6 +90,12 @@ class SubmissionsController < ApplicationController
hijack do |tubesock| hijack do |tubesock|
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive? Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?
# socket is the socket into the container, tubesock is the socket to the client
# give the docker_client the tubesock object, so that it can send messages (timeout)
@docker_client.tubesock = tubesock
result = @docker_client.execute_run_command(@submission, params[:filename]) result = @docker_client.execute_run_command(@submission, params[:filename])
tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => result[:status]}) tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => result[:status]})

View File

@@ -3,6 +3,6 @@ class Comment < ActiveRecord::Base
include Creation include Creation
attr_accessor :username attr_accessor :username
belongs_to :file, class: CodeOcean::File belongs_to :file, class_name: 'CodeOcean::File'
belongs_to :user, polymorphic: true belongs_to :user, polymorphic: true
end end

View File

@@ -2,7 +2,7 @@ module Context
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
has_many :files, as: :context, class: CodeOcean::File has_many :files, as: :context, class_name: 'CodeOcean::File'
accepts_nested_attributes_for :files accepts_nested_attributes_for :files
end end

View File

@@ -1,6 +1,6 @@
class RequestForComment < ActiveRecord::Base class RequestForComment < ActiveRecord::Base
belongs_to :exercise belongs_to :exercise
belongs_to :file, class: CodeOcean::File belongs_to :file, class_name: 'CodeOcean::File'
belongs_to :user, polymorphic: true belongs_to :user, polymorphic: true
before_create :set_requested_timestamp before_create :set_requested_timestamp

View File

@@ -39,7 +39,7 @@
#output-col1 #output-col1
// todo set to full width if turtle isnt used // todo set to full width if turtle isnt used
#prompt.input-group.hidden #prompt.input-group.hidden
span.input-group-addon = t('exercises.editor.input') span.input-group-addon data-prompt=t('exercises.editor.input') = t('exercises.editor.input')
input#prompt-input.form-control type='text' input#prompt-input.form-control type='text'
span.input-group-btn span.input-group-btn
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send') button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')

View File

@@ -22,6 +22,7 @@ production:
interval: 30 interval: 30
timeout: 60 timeout: 60
workspace_root: <%= Rails.root.join('tmp', 'files', Rails.env) %> workspace_root: <%= Rails.root.join('tmp', 'files', Rails.env) %>
ws_host: ws://localhost:4243
test: test:
<<: *default <<: *default

View File

@@ -191,7 +191,7 @@ de:
stop: Stoppen stop: Stoppen
submit: Code zur Bewertung abgeben submit: Code zur Bewertung abgeben
test: Testen test: Testen
timeout: 'Ihr Code hat die erlaubte Ausführungszeit von %{permitted_execution_time} Sekunden überschritten.' timeout: 'Ausführung gestoppt. Ihr Code hat die erlaubte Ausführungszeit von %{permitted_execution_time} Sekunden überschritten.'
tooltips: tooltips:
save: Ihr Code wird automatisch gespeichert, wann immer Sie eine Datei herunterladen, ausführen oder testen. Explizites Speichern ist also selten notwendig. save: Ihr Code wird automatisch gespeichert, wann immer Sie eine Datei herunterladen, ausführen oder testen. Explizites Speichern ist also selten notwendig.
editor_file_tree: editor_file_tree:

View File

@@ -191,7 +191,7 @@ en:
stop: Stop stop: Stop
submit: Submit Code For Assessment submit: Submit Code For Assessment
test: Test test: Test
timeout: 'Your code exceeded the permitted execution time of %{permitted_execution_time} seconds.' timeout: 'Execution stopped. Your code exceeded the permitted execution time of %{permitted_execution_time} seconds.'
tooltips: tooltips:
save: Your code is automatically saved whenever you download, run, or test it. Therefore, explicitly saving is rarely necessary. save: Your code is automatically saved whenever you download, run, or test it. Therefore, explicitly saving is rarely necessary.
editor_file_tree: editor_file_tree:

View File

@@ -12,6 +12,7 @@ class DockerClient
attr_reader :container attr_reader :container
attr_reader :socket attr_reader :socket
attr_accessor :tubesock
def self.check_availability! def self.check_availability!
Timeout.timeout(config[:connection_timeout]) { Docker.version } Timeout.timeout(config[:connection_timeout]) { Docker.version }
@@ -61,7 +62,7 @@ class DockerClient
def create_socket(container, stderr=false) def create_socket(container, stderr=false)
# todo factor out query params # todo factor out query params
# todo separate stderr # todo separate stderr
query_params = 'logs=1&stream=1&' + (stderr ? 'stderr=1' : 'stdout=1&stdin=1') query_params = 'logs=0&stream=1&' + (stderr ? 'stderr=1' : 'stdout=1&stdin=1')
# Headers are required by Docker # Headers are required by Docker
headers = {'Origin' => 'http://localhost'} headers = {'Origin' => 'http://localhost'}
@@ -138,8 +139,8 @@ class DockerClient
def execute_command(command, before_execution_block, output_consuming_block) def execute_command(command, before_execution_block, output_consuming_block)
#tries ||= 0 #tries ||= 0
@container = DockerContainerPool.get_container(@execution_environment) @container = DockerContainerPool.get_container(@execution_environment)
@container.status = :executing
if @container if @container
@container.status = :executing
before_execution_block.try(:call) before_execution_block.try(:call)
send_command(command, @container, &output_consuming_block) send_command(command, @container, &output_consuming_block)
else else
@@ -153,8 +154,8 @@ class DockerClient
def execute_websocket_command(command, before_execution_block, output_consuming_block) def execute_websocket_command(command, before_execution_block, output_consuming_block)
@container = DockerContainerPool.get_container(@execution_environment) @container = DockerContainerPool.get_container(@execution_environment)
@container.status = :executing
if @container if @container
@container.status = :executing
before_execution_block.try(:call) before_execution_block.try(:call)
# todo catch exception if socket could not be created # todo catch exception if socket could not be created
@socket ||= create_socket(@container) @socket ||= create_socket(@container)
@@ -171,17 +172,25 @@ class DockerClient
We need to start a second thread to kill the websocket connection, We need to start a second thread to kill the websocket connection,
as it is impossible to determine whether further input is requested. as it is impossible to determine whether further input is requested.
""" """
Thread.new do @thread = Thread.new do
timeout = @execution_environment.permitted_execution_time.to_i # seconds timeout = @execution_environment.permitted_execution_time.to_i # seconds
sleep(timeout) sleep(timeout)
if container.status != :returned if container.status != :returned
Rails.logger.info("Killing container after timeout of " + timeout.to_s + " seconds.") Rails.logger.info("Killing container after timeout of " + timeout.to_s + " seconds.")
# send timeout to the tubesock socket
if(@tubesock)
@tubesock.send_data JSON.dump({'cmd' => 'timeout'})
end
kill_container(container) kill_container(container)
end end
end end
end end
def exit_container(container) def exit_container(container)
# exit the timeout thread if it is still alive
if(@thread && @thread.alive?)
@thread.exit
end
# if we use pooling and recylce the containers, put it back. otherwise, destroy it. # if we use pooling and recylce the containers, put it back. otherwise, destroy it.
(DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container) (DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container)
end end

View File

@@ -29,15 +29,10 @@ DOCKER_OPTS="-H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock"
EOF EOF
service docker restart service docker restart
fi fi
# XXX docker pull jprberlin/ubuntu-coffee
docker pull jprberlin/ubuntu-java docker pull openhpi/docker_java
# XXX docker pull jprberlin/ubuntu-sqlite docker pull openhpi/docker_ruby
# XXX docker pull jprberlin/ubuntu-sinatra docker pull openhpi/docker_python
# XXX docker pull jprberlin/ubuntu-ruby
# XXX docker pull jprberlin/ubuntu-python
docker pull jprberlin/ubuntu-node
# XXX docker pull jprberlin/ubuntu-html
# XXX docker pull jprberlin/ubuntu-jruby
# rvm # rvm

View File

@@ -1,234 +1 @@
import webpython, contextlib, io, json, sys, turtle, ast # moved to dockerfiles/ubuntu-python
from webpython import shell
turtle_operations = []
bindings = {}
class RecordingPen:
_pen = None
_screen = None
def __init__(self):
self.operations = turtle_operations
self._pos = (0,0)
turtle_operations.append(('__init__',()))
def reset(self):
turtle_operations.clear()
def onclick(self, fun, btn=1, add=None):
self.operations.append(('onclick', (fun,)))
def eventfun(event):
fun(event.x, event.y)
bindings['<Button-1>'] = eventfun
def goto(self, x, y):
self._pos = (x,y)
self.operations.append(('goto', (x,y)))
def pos(self):
self.operations.append(('pos', ()))
return self._pos
def __getattr__(self, method):
def func(*args):
self.operations.append((method, args))
return func
class FakeCanvas(turtle.WebCanvas):
def flushbatch(self):
pass
def get_width(self):
return 400
def get_height(self):
return 400
def delete(self, item):
pass
def css(self, key, value):
pass
fake_events = []
def mainloop():
while fake_events:
e = turtle.Event(fake_events.pop(0))
if e.type in bindings:
bindings[e.type](e)
turtle.Turtle = RecordingPen
turtle.WebCanvas = FakeCanvas
pen = turtle._getpen()
turtle.mainloop = mainloop
def filter_operations(name):
return [o for o in turtle_operations if o[0] == name]
@contextlib.contextmanager
def capture():
global captured_out
import sys
oldout,olderr = sys.stdout, sys.stderr
try:
out=[io.StringIO(), io.StringIO()]
captured_out = out
sys.stdout,sys.stderr = out
yield out
finally:
sys.stdout,sys.stderr = oldout, olderr
out[0] = out[0].getvalue()
out[1] = out[1].getvalue()
def get_source():
message = json.loads(sys.argv[1])
return message['data']
def get_ast():
s = get_source()
return ast.parse(s, "programm.py", "exec")
def has_bare_except():
for node in ast.walk(get_ast()):
if isinstance(node, ast.ExceptHandler):
if node.type is None:
return True
return False
def runcaptured(prefix='', tracing=None, variables=None, source=''):
#message = json.loads(sys.argv[1])
#source = prefix + message['data']
with open("programm.py", "w", encoding='utf-8') as f:
f.write(source)
c = compile(source, "programm.py", 'exec')
with capture() as out, trace(tracing):
if variables is None:
variables = {}
exec(c, variables)
return source, out[0], out[1], variables
def runfunc(func, *args, tracing=None):
with capture() as out, trace(tracing):
res = func(*args)
return out[0], out[1], res
def passed():
msg_in = json.loads(sys.argv[1])
msg_out = {'cmd':'passed'}
msg_out['lis_outcome_service_url'] = msg_in['lis_outcome_service_url']
msg_out['lis_result_sourcedid'] = msg_in['lis_result_sourcedid']
webpython.shell.sendpickle(msg_out)
def failed(msg):
msg_in = json.loads(sys.argv[1])
msg_out = {'cmd':'failed', 'data':'Dein Programm ist leider falsch:\n'+msg}
msg_out['lis_outcome_service_url'] = msg_in['lis_outcome_service_url']
msg_out['lis_result_sourcedid'] = msg_in['lis_result_sourcedid']
webpython.shell.sendpickle(msg_out)
def modified(variables, name, val):
if variables.get(name) != val:
msg_in = json.loads(sys.argv[1])
msg_out = {'cmd':'failed',
'data':('Bitte lösche Deine Zuweisung der Variable %s, '+
'damit wir Dein Programm überprüfen können.') % name}
msg_out['lis_outcome_service_url'] = msg_in['lis_outcome_service_url']
msg_out['lis_result_sourcedid'] = msg_in['lis_result_sourcedid']
webpython.shell.sendpickle(msg_out)
return True
return False
undefined = object()
def getvar(variables, name):
try:
return variables['name']
except KeyError:
name = name.lower()
for k,v in variables.items():
if k.lower() == name:
return v
return undefined
def _match(n1, n2):
if n1 == n2:
return True
if n1 is None or n2 is None:
return False
return n1.lower() == n2.lower()
class Call:
def __init__(self, name, args):
self.name = name
self.args = args
self.calls = []
self.current = None
def findcall(self, f):
if _match(self.name, f):
return self
for c in self.calls:
r = c.findcall(f)
if r:
return r
return None
def calling(self, caller, callee):
if _match(self.name, caller):
for c in self.calls:
if _match(c.name, callee):
return True
for c in self.calls:
if c.calling(caller, callee):
return True
return False
def countcalls(self, caller, callee):
calls = 0
if _match(self.name, caller):
for c in self.calls:
if _match(c.name, callee):
calls += 1
return calls
for c in self.calls:
r = c.countcalls(caller, callee)
if r > 0:
return r
return 0
class Tracing(Call):
def __init__(self):
Call.__init__(self, None, None)
def trace(self, frame, event, arg):
if event == 'call':
c = Call(frame.f_code.co_name, frame.f_locals.copy())
cur = self
while cur.current:
cur = cur.current
cur.calls.append(c)
cur.current = c
return self.trace
elif event in ('return', 'exception'):
cur = self
if not cur.current:
# XXX return without call? happens when invocation of top function fails
return
while cur.current.current:
cur = cur.current
cur.current = None
def start(self):
sys.settrace(self.trace)
def stop(self):
sys.settrace(None)
@contextlib.contextmanager
def trace(t):
try:
if t:
t.start()
yield
finally:
if t:
t.stop()

File diff suppressed because it is too large Load Diff

View File

@@ -1,178 +1 @@
#!/usr/bin/python3 # moved to dockerfiles/ubuntu-python/webpython.py
# 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'})