Apply line-based coloring for output

This commit is contained in:
Sebastian Serth
2022-09-02 16:38:41 +02:00
parent 3ec5263c31
commit 60dc8c3b7e
6 changed files with 59 additions and 64 deletions

View File

@ -78,7 +78,7 @@ var CodeOceanEditor = {
if ($('#output-' + index).isPresent()) {
return $('#output-' + index);
} else {
var element = $('<pre class="mb-2">').attr('id', 'output-' + index);
var element = $('<div class="mb-2 output-element">').attr('id', 'output-' + index);
$('#output').append(element);
return element;
}
@ -648,7 +648,7 @@ var CodeOceanEditor = {
augmentStacktraceInOutput: function () {
if (this.tracepositions_regex) {
$('#output>pre').each($.proxy(function(index, element) {
$('#output > .output-element').each($.proxy(function(index, element) {
element = $(element)
const text = _.escape(element.text());
@ -656,16 +656,11 @@ var CodeOceanEditor = {
let matches;
// Switch both lines below to enable the output of images and render <IMG/> tags.
// Also consider `printOutput` in evaluation.js
// let augmented_text = element.text();
let augmented_text = element.html();
while (matches = this.tracepositions_regex.exec(text)) {
const frame = $('div.frame[data-filename="' + matches[1] + '"]')
if (frame.length > 0) {
// augmented_text = augmented_text.replace(new RegExp(matches[0], 'g'), "<a href='#' data-file='" + matches[1] + "' data-line='" + matches[2] + "'>" + matches[0] + "</a>");
augmented_text = augmented_text.replace(new RegExp(_.unescape(matches[0]), 'g'), "<a href='#' data-file='" + matches[1] + "' data-line='" + matches[2] + "'>" + matches[0] + "</a>");
}
}

View File

@ -189,7 +189,7 @@ CodeOceanEditorEvaluation = {
},
clearOutput: function () {
$('#output pre').remove();
$('#output > .output-element').remove();
CodeOceanEditorTurtle.hideCanvas();
},
@ -207,50 +207,54 @@ CodeOceanEditorEvaluation = {
return;
}
if (output.stdout !== undefined && !output.stdout.startsWith("<img")) {
output.stdout = _.escape(output.stdout);
} else {
const doc = new DOMParser().parseFromString(output.stdout, "text/html");
const sanitizedStdout = this.sanitizeOutput(output.stdout);
const sanitizedStderr = this.sanitizeOutput(output.stderr);
const element = this.findOrCreateOutputElement(index);
const pre = $('<span>');
if (sanitizedStdout !== '') {
if (colorize) {
pre.addClass('text-success');
}
pre.append(sanitizedStdout)
}
if (sanitizedStderr !== '') {
if (colorize) {
pre.addClass('text-warning');
} else {
pre.append('StdErr: ');
}
pre.append(sanitizedStderr);
}
if (sanitizedStdout === '' && sanitizedStderr === '') {
if (colorize) {
pre.addClass('text-muted');
}
pre.text($('#output').data('message-no-output'))
}
element.append(pre);
},
sanitizeOutput: function (rawContent) {
let sanitizedContent = _.escape(rawContent).replace(this.nonPrintableRegEx, "");
if (rawContent !== undefined && rawContent.trim().startsWith("<img")) {
const doc = new DOMParser().parseFromString(rawContent, "text/html");
// Get the parsed element, it is automatically wrapped in a <html><body> document
const parsedElement = doc.firstChild.lastChild.firstChild;
const sanitized_img = document.createElement('img');
sanitized_img.src = parsedElement.src;
output.stdout = sanitized_img.outerHTML;
if (parsedElement.src.startsWith("data:image")) {
const sanitizedImg = document.createElement('img');
sanitizedImg.src = parsedElement.src;
sanitizedContent = sanitizedImg.outerHTML;
}
}
var element = this.findOrCreateOutputElement(index);
// Switch all four lines below to enable the output of images and render <IMG/> tags.
// Also consider `augmentStacktraceInOutput` in editor.js.erb
if (!colorize) {
if (output.stdout !== undefined && output.stdout !== '') {
output.stdout = output.stdout.replace(this.nonPrintableRegEx, "")
element.append(output.stdout)
//element.text(element.text() + output.stdout)
}
if (output.stderr !== undefined && output.stderr !== '') {
output.stderr = output.stderr.replace(this.nonPrintableRegEx, "")
element.append('StdErr: ' + output.stderr);
//element.text('StdErr: ' + element.text() + output.stderr);
}
} else if (output.stderr) {
output.stderr = output.stderr.replace(this.nonPrintableRegEx, "")
element.addClass('text-warning').append(output.stderr);
//element.addClass('text-warning').text(element.text() + output.stderr);
this.QaApiOutputBuffer.stderr += output.stderr;
} else if (output.stdout) {
output.stdout = output.stdout.replace(this.nonPrintableRegEx, "")
element.addClass('text-success').append(output.stdout);
//element.addClass('text-success').text(element.text() + output.stdout);
this.QaApiOutputBuffer.stdout += output.stdout;
} else {
element.addClass('text-muted').text($('#output').data('message-no-output'));
}
return sanitizedContent;
},
getDeadlineInformation: function(deadline, translation_key, otherwise) {

View File

@ -25,7 +25,7 @@ i.fa-solid, i.fa-regular, i.fa-solid {
margin-right: 0.5em;
}
pre {
pre, .output-element {
background-color: #FAFAFA;
margin: 0;
padding: .25rem!important;

View File

@ -77,20 +77,13 @@
overflow: auto;
}
#outputInformation {
#output {
max-height: 500px;
width: 100%;
#output {
white-space: pre;
font-family: var(--bs-font-monospace);
font-size: 14px;
.output-element {
overflow: auto;
margin: 2em 0;
p {
margin: 0.5em;
}
pre + pre {
margin-top: 1em;
}
}
}

View File

@ -73,6 +73,7 @@ class SubmissionsController < ApplicationController
end
end
# rubocop:disable Metrics/CyclomaticComplexity
def run
# These method-local socket variables are required in order to use one socket
# in the callbacks of the other socket. As the callbacks for the client socket
@ -167,7 +168,8 @@ class SubmissionsController < ApplicationController
@testrun[:status] = :failed
"\n#{t('exercises.implement.exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}"
end
send_and_store client_socket, {cmd: :write, stream: :stdout, data: "#{exit_statement}\n"}
stream = @testrun[:status] == :ok ? :stdout : :stderr
send_and_store client_socket, {cmd: :write, stream: stream, data: "#{exit_statement}\n"}
if exit_code == 137
send_and_store client_socket, {cmd: :status, status: :out_of_memory}
@testrun[:status] = :out_of_memory
@ -194,6 +196,7 @@ class SubmissionsController < ApplicationController
ensure
save_testrun_output 'run'
end
# rubocop:enable Metrics/CyclomaticComplexity:
def score
hijack do |tubesock|

View File

@ -79,7 +79,7 @@ div.d-grid id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-botto
.heading = t('exercises.implement.error_hints.heading')
ul.body.mb-0
#output
pre.overflow-scroll = t('exercises.implement.no_output_yet')
.output-element.overflow-scroll = t('exercises.implement.no_output_yet')
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled] && !@embed_options[:disable_hints] && !@embed_options[:hide_test_results]
#flowrHint.mb-2.card.text-white.bg-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab'
.card-header = t('exercises.implement.flowr.heading')