Merge remote-tracking branch 'origin/master' into statistics
This commit is contained in:
@ -50,6 +50,7 @@ GEM
|
||||
bootstrap-will_paginate (0.0.10)
|
||||
will_paginate
|
||||
builder (3.2.2)
|
||||
byebug (6.0.2)
|
||||
capistrano (3.3.5)
|
||||
capistrano-stats (~> 1.1.0)
|
||||
i18n
|
||||
@ -108,6 +109,7 @@ GEM
|
||||
json
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.8)
|
||||
eventmachine (1.0.8-java)
|
||||
excon (0.45.2)
|
||||
execjs (2.5.2)
|
||||
factory_girl (4.5.0)
|
||||
@ -321,6 +323,8 @@ GEM
|
||||
websocket (1.2.1)
|
||||
websocket-driver (0.6.2)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-driver (0.6.2-java)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
will_paginate (3.0.7)
|
||||
xpath (2.0.0)
|
||||
@ -337,6 +341,7 @@ DEPENDENCIES
|
||||
better_errors
|
||||
binding_of_caller
|
||||
bootstrap-will_paginate
|
||||
byebug
|
||||
capistrano (~> 3.3.0)
|
||||
capistrano-rails
|
||||
capistrano-rvm
|
||||
@ -387,3 +392,6 @@ DEPENDENCIES
|
||||
uglifier (>= 1.3.0)
|
||||
web-console (~> 2.0)
|
||||
will_paginate (~> 3.0)
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
||||
|
@ -28,15 +28,16 @@ Execute: vagrant box add ubuntu/trusty64
|
||||
Execute: vagrant up
|
||||
Install docker environments
|
||||
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-java
|
||||
docker pull jprberlin/ubuntu-sqlite
|
||||
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-jruby
|
||||
docker pull jprberlin/ubuntu-jruby)
|
||||
|
||||
cd repoPath
|
||||
bundle install
|
||||
|
@ -30,7 +30,7 @@ $(function() {
|
||||
numMessages = 0,
|
||||
turtlecanvas = $('#turtlecanvas'),
|
||||
prompt = $('#prompt'),
|
||||
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'status'],
|
||||
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'timeout', 'status'],
|
||||
streams = ['stdin', 'stdout', 'stderr'];
|
||||
|
||||
var ENTER_KEY_CODE = 13;
|
||||
@ -686,8 +686,8 @@ $(function() {
|
||||
};
|
||||
|
||||
var isBrowserSupported = function() {
|
||||
// todo event streams are no longer required with websockets
|
||||
return window.EventSource !== undefined;
|
||||
// eventsource tests for server send events (used for scoring), websockets is used for run
|
||||
return Modernizr.eventsource && Modernizr.websockets;
|
||||
};
|
||||
|
||||
var populatePanel = function(panel, result, index) {
|
||||
@ -1004,7 +1004,7 @@ $(function() {
|
||||
};
|
||||
|
||||
var showTimeoutMessage = function() {
|
||||
$.flash.danger({
|
||||
$.flash.info({
|
||||
icon: ['fa', 'fa-clock-o'],
|
||||
text: $('#editor').data('message-timeout')
|
||||
});
|
||||
@ -1059,14 +1059,6 @@ $(function() {
|
||||
running = false;
|
||||
toggleButtonStates();
|
||||
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
|
||||
@ -1125,7 +1117,7 @@ $(function() {
|
||||
};
|
||||
|
||||
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.onclose = function(evt) { /* expected at some point */ };
|
||||
websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); };
|
||||
@ -1160,7 +1152,7 @@ $(function() {
|
||||
}
|
||||
switch(msg.cmd) {
|
||||
case 'input':
|
||||
showPrompt();
|
||||
showPrompt(msg);
|
||||
break;
|
||||
case 'write':
|
||||
printWebsocketOutput(msg);
|
||||
@ -1176,6 +1168,10 @@ $(function() {
|
||||
case 'exit':
|
||||
killWebsocketAndContainer();
|
||||
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':
|
||||
showStatus(msg)
|
||||
break;
|
||||
@ -1249,11 +1245,13 @@ $(function() {
|
||||
executeWebsocketCommand(msg);
|
||||
};
|
||||
|
||||
var showPrompt = function() {
|
||||
if (prompt.isPresent() && prompt.hasClass('hidden')) {
|
||||
var showPrompt = function(msg) {
|
||||
var label = $('#prompt .input-group-addon');
|
||||
label.text(msg.data || label.data('prompt'));
|
||||
if (prompt.isPresent() && prompt.hasClass('hidden')) {
|
||||
prompt.removeClass('hidden');
|
||||
}
|
||||
prompt.focus();
|
||||
$('#prompt input').focus();
|
||||
}
|
||||
|
||||
var hidePrompt = function() {
|
||||
|
3
app/assets/javascripts/modernizr-custom.js
Normal file
3
app/assets/javascripts/modernizr-custom.js
Normal 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);
|
@ -90,6 +90,12 @@ class SubmissionsController < ApplicationController
|
||||
hijack do |tubesock|
|
||||
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])
|
||||
tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => result[:status]})
|
||||
|
||||
|
@ -3,6 +3,6 @@ class Comment < ActiveRecord::Base
|
||||
include Creation
|
||||
attr_accessor :username
|
||||
|
||||
belongs_to :file, class: CodeOcean::File
|
||||
belongs_to :file, class_name: 'CodeOcean::File'
|
||||
belongs_to :user, polymorphic: true
|
||||
end
|
||||
|
@ -2,7 +2,7 @@ module Context
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :files, as: :context, class: CodeOcean::File
|
||||
has_many :files, as: :context, class_name: 'CodeOcean::File'
|
||||
accepts_nested_attributes_for :files
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
class RequestForComment < ActiveRecord::Base
|
||||
belongs_to :exercise
|
||||
belongs_to :file, class: CodeOcean::File
|
||||
belongs_to :file, class_name: 'CodeOcean::File'
|
||||
belongs_to :user, polymorphic: true
|
||||
|
||||
before_create :set_requested_timestamp
|
||||
|
@ -39,7 +39,7 @@
|
||||
#output-col1
|
||||
// todo set to full width if turtle isnt used
|
||||
#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'
|
||||
span.input-group-btn
|
||||
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
||||
|
@ -22,6 +22,7 @@ production:
|
||||
interval: 30
|
||||
timeout: 60
|
||||
workspace_root: <%= Rails.root.join('tmp', 'files', Rails.env) %>
|
||||
ws_host: ws://localhost:4243
|
||||
|
||||
test:
|
||||
<<: *default
|
||||
|
@ -191,7 +191,7 @@ de:
|
||||
stop: Stoppen
|
||||
submit: Code zur Bewertung abgeben
|
||||
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:
|
||||
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:
|
||||
|
@ -191,7 +191,7 @@ en:
|
||||
stop: Stop
|
||||
submit: Submit Code For Assessment
|
||||
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:
|
||||
save: Your code is automatically saved whenever you download, run, or test it. Therefore, explicitly saving is rarely necessary.
|
||||
editor_file_tree:
|
||||
|
@ -12,6 +12,7 @@ class DockerClient
|
||||
|
||||
attr_reader :container
|
||||
attr_reader :socket
|
||||
attr_accessor :tubesock
|
||||
|
||||
def self.check_availability!
|
||||
Timeout.timeout(config[:connection_timeout]) { Docker.version }
|
||||
@ -61,7 +62,7 @@ class DockerClient
|
||||
def create_socket(container, stderr=false)
|
||||
# todo factor out query params
|
||||
# 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 = {'Origin' => 'http://localhost'}
|
||||
@ -138,8 +139,8 @@ class DockerClient
|
||||
def execute_command(command, before_execution_block, output_consuming_block)
|
||||
#tries ||= 0
|
||||
@container = DockerContainerPool.get_container(@execution_environment)
|
||||
@container.status = :executing
|
||||
if @container
|
||||
@container.status = :executing
|
||||
before_execution_block.try(:call)
|
||||
send_command(command, @container, &output_consuming_block)
|
||||
else
|
||||
@ -153,8 +154,8 @@ class DockerClient
|
||||
|
||||
def execute_websocket_command(command, before_execution_block, output_consuming_block)
|
||||
@container = DockerContainerPool.get_container(@execution_environment)
|
||||
@container.status = :executing
|
||||
if @container
|
||||
@container.status = :executing
|
||||
before_execution_block.try(:call)
|
||||
# todo catch exception if socket could not be created
|
||||
@socket ||= create_socket(@container)
|
||||
@ -171,17 +172,25 @@ class DockerClient
|
||||
We need to start a second thread to kill the websocket connection,
|
||||
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
|
||||
sleep(timeout)
|
||||
if container.status != :returned
|
||||
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)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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.
|
||||
(DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container)
|
||||
end
|
||||
|
13
provision.sh
13
provision.sh
@ -29,15 +29,10 @@ DOCKER_OPTS="-H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock"
|
||||
EOF
|
||||
service docker restart
|
||||
fi
|
||||
# XXX docker pull jprberlin/ubuntu-coffee
|
||||
docker pull jprberlin/ubuntu-java
|
||||
# XXX docker pull jprberlin/ubuntu-sqlite
|
||||
# XXX docker pull jprberlin/ubuntu-sinatra
|
||||
# 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
|
||||
|
||||
docker pull openhpi/docker_java
|
||||
docker pull openhpi/docker_ruby
|
||||
docker pull openhpi/docker_python
|
||||
|
||||
|
||||
# rvm
|
||||
|
@ -1,234 +1 @@
|
||||
import webpython, contextlib, io, json, sys, turtle, ast
|
||||
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()
|
||||
# moved to dockerfiles/ubuntu-python
|
||||
|
4079
webpython/turtle.py
4079
webpython/turtle.py
File diff suppressed because it is too large
Load Diff
@ -1,178 +1 @@
|
||||
#!/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'})
|
||||
# moved to dockerfiles/ubuntu-python/webpython.py
|
||||
|
Reference in New Issue
Block a user