Merge branch 'democode' into 'master'
commit
b97040cccc
@ -1,19 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en-us">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
||||||
<title>WebPerl Code Demo</title>
|
|
||||||
|
|
||||||
<iframe name="perlrunner" sandbox="allow-scripts"
|
|
||||||
src="perlrunner.html" style="display: none;"></iframe>
|
|
||||||
|
|
||||||
<iframe sandbox="allow-scripts" src="perleditor.html#i1f=Foo%0ABar%0AQuz&i1n=in1.txt&script=use warnings;%0Ause strict;%0A%0Awhile (<>) {%0A s/[aeiou]/_/gi;%0A}&cmdline=perl script.pl in1.txt"
|
|
||||||
style="border:1px solid black;width:100%;height:20em;"></iframe>
|
|
||||||
|
|
||||||
<iframe sandbox="allow-scripts" src="perleditor.html"
|
|
||||||
style="border:1px solid black;width:100%;height:20em;"></iframe>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,271 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en-us">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
||||||
<title>WebPerl Perl Editor</title>
|
|
||||||
|
|
||||||
<!-- ##### WebPerl - http://webperl.zero-g.net #####
|
|
||||||
|
|
||||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
|
||||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
|
||||||
Berlin, Germany, http://www.igb-berlin.de
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the same terms as Perl 5 itself: either the GNU General Public
|
|
||||||
License as published by the Free Software Foundation (either version 1,
|
|
||||||
or, at your option, any later version), or the "Artistic License" which
|
|
||||||
comes with Perl 5.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but
|
|
||||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
See the licenses for details.
|
|
||||||
|
|
||||||
You should have received a copy of the licenses along with this program.
|
|
||||||
If not, see http://perldoc.perl.org/index-licence.html
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css" integrity="sha256-oSrCnRYXvHG31SBifqP2PM1uje7SJUyX0nTwO2RJV54=" crossorigin="anonymous" />
|
|
||||||
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.css" integrity="sha256-I8NyGs4wjbMuBSUE40o55W6k6P7tu/7G28/JGUUYCIs=" crossorigin="anonymous" />
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0.4em;
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
font-family: Calibri, Ubuntu, "Droid Sans", Tahoma, Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
pre,textarea,code,.code,.filename,.CodeMirror {
|
|
||||||
font-family: Consolas, "Ubuntu Mono", "Droid Sans Mono", "Lucida Console", "Courier New", Courier, monospace;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.CodeMirror {
|
|
||||||
border: 1px solid grey;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.CodeMirror-scroll {
|
|
||||||
max-height: 10em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.js" integrity="sha256-uRIJ6Wfou5cTtmcCvQNA9lvsYl8sUbZXxnfG+P79ssY=" crossorigin="anonymous"></script>
|
|
||||||
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/mode/perl/perl.min.js" integrity="sha256-Uu9QBfi8gU6J/MzQunal8ewmY+i/BbCkBrcAXA5bcCM=" crossorigin="anonymous"></script>
|
|
||||||
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
|
|
||||||
<script>
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
function makeCM (textarea,plain,ro) {
|
|
||||||
return CodeMirror.fromTextArea( textarea[0], {
|
|
||||||
viewportMargin: Infinity, // so browser's search works, not good for long documents though
|
|
||||||
lineNumbers:true, indentWithTabs:true,
|
|
||||||
tabSize:4, indentUnit:4,
|
|
||||||
mode: plain?"text/plain":"perl",
|
|
||||||
readOnly: ro?true:false,
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
var mergeStdOutErr = 0;
|
|
||||||
|
|
||||||
var cm_std = { 1:null, 2:null }; // 1=stdout, 2=stderr
|
|
||||||
function stdOutput (which, data) { // 1=stdout, 2=stderr
|
|
||||||
if (mergeStdOutErr) which = 1;
|
|
||||||
if (!cm_std[which]) {
|
|
||||||
var div = $(which==1?'#stdout':'#stderr');
|
|
||||||
div.show();
|
|
||||||
cm_std[which] = makeCM($('textarea',div),1,1);
|
|
||||||
div.data('CodeMirrorInstance', cm_std[which]);
|
|
||||||
}
|
|
||||||
if (data && data.length)
|
|
||||||
cm_std[which].setValue( cm_std[which].getValue() + data );
|
|
||||||
}
|
|
||||||
function clearStdOutput () {
|
|
||||||
if (cm_std["1"]) cm_std["1"].setValue("");
|
|
||||||
if (cm_std["2"]) cm_std["2"].setValue("");
|
|
||||||
}
|
|
||||||
|
|
||||||
var perlRunner;
|
|
||||||
|
|
||||||
window.addEventListener('message', function (event) {
|
|
||||||
var data = event.data;
|
|
||||||
//console.log("editor got message", data); //DB
|
|
||||||
if (data["perlRunnerState"]) {
|
|
||||||
var disableBtn = true;
|
|
||||||
if ( data.perlRunnerState=="Ready" ) {
|
|
||||||
perlRunner = event.source;
|
|
||||||
disableBtn = false;
|
|
||||||
}
|
|
||||||
else if ( data.perlRunnerState=="Ended" ) {
|
|
||||||
perlRunner = null;
|
|
||||||
findPerlRunner();
|
|
||||||
}
|
|
||||||
$('#runperl').prop("disabled",disableBtn);
|
|
||||||
}
|
|
||||||
else if (data["perlOutput"]) {
|
|
||||||
stdOutput(data.perlOutput.chan, data.perlOutput.data);
|
|
||||||
}
|
|
||||||
else console.warn("Perl Editor ignoring unknown message:",data);
|
|
||||||
});
|
|
||||||
|
|
||||||
function findPerlRunner () {
|
|
||||||
var pollId;
|
|
||||||
var pollUntil = Date.now() + 10*1000; // milliseconds
|
|
||||||
pollId = window.setInterval( function () {
|
|
||||||
if (perlRunner) {
|
|
||||||
window.clearInterval(pollId);
|
|
||||||
}
|
|
||||||
else if ( Date.now()>pollUntil ) {
|
|
||||||
window.clearInterval(pollId);
|
|
||||||
console.error("Perl Editor could not contact Perl Runner"); //TODO: report error to user via UI (same for other console.error calls in this script)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (window.parent && window.parent.frames["perlrunner"])
|
|
||||||
window.parent.frames["perlrunner"].postMessage(
|
|
||||||
{perlRunnerRegisterClient:1}, '*');
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseCmdLine(str) {
|
|
||||||
var re = /"((?:\\"|\\\\|[^"])*)"|'((?:\\'|\\\\|[^'])*)'|(\S+)/g;
|
|
||||||
var argv = [];
|
|
||||||
var match;
|
|
||||||
while ((match = re.exec(str)) !== null) {
|
|
||||||
if (typeof match[1] != 'undefined') argv.push(match[1].replace(/\\\\/g,"\\").replace(/\\"/g,"\""));
|
|
||||||
else if (typeof match[2] != 'undefined') argv.push(match[2].replace(/\\\\/g,'\\').replace(/\\'/g,'\''));
|
|
||||||
else if (typeof match[3] != 'undefined') argv.push(match[3]);
|
|
||||||
}
|
|
||||||
return argv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseParams(str) { // Thanks to https://stackoverflow.com/a/26849194
|
|
||||||
if (!str || !str.length) return {};
|
|
||||||
return str.split('&').reduce(function (params, param) {
|
|
||||||
var paramSplit = param.split('=').map(function (value) {
|
|
||||||
return decodeURIComponent(value.replace(/\+/g, ' '));
|
|
||||||
});
|
|
||||||
params[paramSplit[0]] = paramSplit[1];
|
|
||||||
return params;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initialize (hash) {
|
|
||||||
console.log("hash", hash); //DB
|
|
||||||
// script
|
|
||||||
if (hash["script"] || hash["scripturl"]) {
|
|
||||||
var script = $('#script');
|
|
||||||
script.show();
|
|
||||||
var cm = makeCM($('#perlcode'));
|
|
||||||
script.data('CodeMirrorInstance', cm);
|
|
||||||
if (hash["script"])
|
|
||||||
cm.setValue(hash.script);
|
|
||||||
else if (hash["scripturl"]) {
|
|
||||||
//TODO: fetch URL
|
|
||||||
}
|
|
||||||
else throw "internal error: this shouldn't happen";
|
|
||||||
if (hash["scriptfn"])
|
|
||||||
$('.filename',script).text(hash.scriptfn);
|
|
||||||
}
|
|
||||||
if (hash["cmdline"]) {
|
|
||||||
$('#argv').val(hash.cmdline);
|
|
||||||
}
|
|
||||||
// input files
|
|
||||||
$('.inputs').remove();
|
|
||||||
for (var i=1;;i++) {
|
|
||||||
if (!( hash["i"+i+"f"] || hash["i"+i+"u"] )) break;
|
|
||||||
var fn = hash["i"+i+"n"] || "input"+i+".txt";
|
|
||||||
var div = $('<div/>',{class:"codewithfn inputs"});
|
|
||||||
div.append( $('<div/>',{class:"filename",text:fn}) );
|
|
||||||
var ta = $('<textarea/>').appendTo(div);
|
|
||||||
$('#inputhere').before(div);
|
|
||||||
var cm = makeCM(ta,1,0);
|
|
||||||
div.data('CodeMirrorInstance', cm);
|
|
||||||
if (hash["i"+i+"f"]) {
|
|
||||||
cm.setValue(hash["i"+i+"f"]);
|
|
||||||
}
|
|
||||||
else if (hash["i"+i+"u"]) {
|
|
||||||
//TODO: fetch URLs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// stdout/stderr
|
|
||||||
if (hash["mergestdouterr"]) {
|
|
||||||
mergeStdOutErr = 1;
|
|
||||||
$('#stdout>.filename').text("STDOUT+STDERR");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mergeStdOutErr = 0;
|
|
||||||
$('#stdout>.filename').text("STDOUT");
|
|
||||||
}
|
|
||||||
clearStdOutput();
|
|
||||||
// output files
|
|
||||||
for (var i=1;;i++) {
|
|
||||||
if (hash["o"+i+"n"]) {
|
|
||||||
var div = $('<div/>',{class:"codewithfn outputs"});
|
|
||||||
div.append( $('<div/>',{class:"filename",text:hash["o"+i+"n"]}) );
|
|
||||||
var ta = $('<textarea/>').appendTo(div);
|
|
||||||
$('#outputhere').before(div);
|
|
||||||
var cm = makeCM(ta,1,1);
|
|
||||||
div.data('CodeMirrorInstance', cm);
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
|
|
||||||
initialize(parseParams(window.location.hash.substr(1)));
|
|
||||||
|
|
||||||
var btn_runperl = $('#runperl');
|
|
||||||
btn_runperl.click( function () {
|
|
||||||
clearStdOutput();
|
|
||||||
|
|
||||||
var argv = parseCmdLine($('#argv').val());
|
|
||||||
if (argv.length<1 || argv[0]!="perl") {
|
|
||||||
console.error("invalid command line");
|
|
||||||
return }
|
|
||||||
else argv.shift();
|
|
||||||
|
|
||||||
var runperl = {
|
|
||||||
//TODO: input files
|
|
||||||
argv: argv,
|
|
||||||
//TODO: script: perl_cm.getValue(),
|
|
||||||
};
|
|
||||||
|
|
||||||
btn_runperl.prop("disabled",true);
|
|
||||||
perlRunner.postMessage({ runPerl: runperl },'*');
|
|
||||||
});
|
|
||||||
btn_runperl.prop("disabled",true);
|
|
||||||
|
|
||||||
findPerlRunner();
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="inputhere" style="display:none;"></div>
|
|
||||||
|
|
||||||
<div id="script" class="codewithfn" style="display:none;">
|
|
||||||
<div class="filename">script.pl</div>
|
|
||||||
<textarea id="perlcode"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button id="runperl"><code>perl</code> ►</button>
|
|
||||||
<input type="text" id="argv" class="code" size="60" value='perl' />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="stdout" class="codewithfn" style="display:none;">
|
|
||||||
<div class="filename">STDOUT</div>
|
|
||||||
<textarea></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="stderr" class="codewithfn" style="display:none;">
|
|
||||||
<div class="filename">STDERR</div>
|
|
||||||
<textarea></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="outputhere" style="display:none;"></div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en-us">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
||||||
<title>WebPerl Perl Runner</title>
|
|
||||||
|
|
||||||
<!-- ##### WebPerl - http://webperl.zero-g.net #####
|
|
||||||
|
|
||||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
|
||||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
|
||||||
Berlin, Germany, http://www.igb-berlin.de
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the same terms as Perl 5 itself: either the GNU General Public
|
|
||||||
License as published by the Free Software Foundation (either version 1,
|
|
||||||
or, at your option, any later version), or the "Artistic License" which
|
|
||||||
comes with Perl 5.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but
|
|
||||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
See the licenses for details.
|
|
||||||
|
|
||||||
You should have received a copy of the licenses along with this program.
|
|
||||||
If not, see http://perldoc.perl.org/index-licence.html
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Possible To-Do for Later: This whole thing could probably also be
|
|
||||||
accomplished with a Web Worker, but that would probably require a
|
|
||||||
stripped-down version of webperl.js (that loads things without
|
|
||||||
using window.* and especially document.*
|
|
||||||
https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker
|
|
||||||
https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts
|
|
||||||
-->
|
|
||||||
|
|
||||||
<script src="../webperl.js"></script>
|
|
||||||
<!--script src="https://webperlcdn.zero-g.net/v0.07-beta/webperl.js"
|
|
||||||
integrity="sha256-jL8SB7St5ou4+hb0frK0k6VCQXsWQ1wolDrdU7i4juc" crossorigin="anonymous"></script-->
|
|
||||||
<script>
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
Perl.noMountIdbfs=true; // we're sandboxed
|
|
||||||
Perl.endAfterMain=true; // act like command-line perl
|
|
||||||
|
|
||||||
var knownClients = [];
|
|
||||||
var currentClient; // which client we're running Perl for, also keeps state
|
|
||||||
|
|
||||||
Perl.addStateChangeListener(function (from,to) {
|
|
||||||
if (from==to) return; // won't be needed as of v0.09-beta
|
|
||||||
for(var i=0; i<knownClients.length; i++)
|
|
||||||
knownClients[i].postMessage({ perlRunnerState: Perl.state },'*');
|
|
||||||
if (to=="Ended") {
|
|
||||||
if (currentClient) {
|
|
||||||
for (var c=1;c<=2;c++) // flush buffers
|
|
||||||
if (stdbuf[c].length)
|
|
||||||
currentClient.postMessage({ perlOutput: { chan:c, data:stdbuf[c] } },'*');
|
|
||||||
//TODO: how to handle nonzero exit codes and communicate them back to the client
|
|
||||||
//TODO: post output files
|
|
||||||
}
|
|
||||||
else console.error("state change to Ended with no current client?");
|
|
||||||
window.location.reload(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var stdbuf = [null,'',''];
|
|
||||||
Perl.output = function (str,chan) {
|
|
||||||
stdbuf[chan] += str;
|
|
||||||
var pos = stdbuf[chan].lastIndexOf("\n");
|
|
||||||
if (pos>-1) {
|
|
||||||
var line = stdbuf[chan].slice(0,pos);
|
|
||||||
if (currentClient)
|
|
||||||
currentClient.postMessage({ perlOutput: { chan:chan, data:line+"\n" } },'*');
|
|
||||||
else
|
|
||||||
console.warn("output on",chan==1?"STDOUT":"STDERR",
|
|
||||||
"with no client?",line);
|
|
||||||
stdbuf[chan] = stdbuf[chan].slice(pos+1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Perl.init(function () {
|
|
||||||
FS.currentPath = ENV.HOME; // NOTE: https://github.com/kripken/emscripten/issues/5873
|
|
||||||
window.addEventListener('message', function (event) {
|
|
||||||
var data = event.data;
|
|
||||||
var result;
|
|
||||||
if (data["perlRunnerRegisterClient"]) {
|
|
||||||
result = { perlRunnerState: Perl.state };
|
|
||||||
if (!knownClients.includes(event.source))
|
|
||||||
knownClients.push(event.source);
|
|
||||||
}
|
|
||||||
else if (data["runPerl"]) {
|
|
||||||
if (!knownClients.includes(event.source))
|
|
||||||
knownClients.push(event.source);
|
|
||||||
if (currentClient) {
|
|
||||||
//TODO Later: a way to communicate these errors back to client?
|
|
||||||
console.error("Attempt to run Perl from",event.source,
|
|
||||||
"but am already running Perl for",currentClient);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Perl.state!="Ready") {
|
|
||||||
console.error("Attempt to run Perl in state",Perl.state,
|
|
||||||
"from",event.source);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentClient = event.source;
|
|
||||||
|
|
||||||
var scriptFn = data.runPerl["scriptName"]
|
|
||||||
? data.runPerl.scriptName : 'script.pl';
|
|
||||||
Module['thisProgram'] = './'+scriptFn;
|
|
||||||
FS.writeFile( FS.joinPath([FS.cwd(), scriptFn]),
|
|
||||||
data.runPerl.script );
|
|
||||||
|
|
||||||
//TODO: input files
|
|
||||||
|
|
||||||
var argv = data.runPerl["argv"] ? data.runPerl.argv : [scriptFn];
|
|
||||||
Perl.start(argv);
|
|
||||||
}
|
|
||||||
else console.warn("Perl Runner ignoring unknown message:", data);
|
|
||||||
//console.log("runner got message", data, "result", result); //DB
|
|
||||||
if (result)
|
|
||||||
event.source.postMessage(result,
|
|
||||||
event.origin==null || event.origin=="null" ? '*' : event.origin);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -0,0 +1,166 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-us">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>WebPerl Code Demo</title>
|
||||||
|
|
||||||
|
<!-- ##### WebPerl - http://webperl.zero-g.net #####
|
||||||
|
|
||||||
|
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||||
|
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||||
|
Berlin, Germany, http://www.igb-berlin.de
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the same terms as Perl 5 itself: either the GNU General Public
|
||||||
|
License as published by the Free Software Foundation (either version 1,
|
||||||
|
or, at your option, any later version), or the "Artistic License" which
|
||||||
|
comes with Perl 5.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the licenses for details.
|
||||||
|
|
||||||
|
You should have received a copy of the licenses along with this program.
|
||||||
|
If not, see http://perldoc.perl.org/index-licence.html
|
||||||
|
##### -->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
font-family: Calibri, Ubuntu, "Droid Sans", Tahoma, Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
pre,textarea,code {
|
||||||
|
font-family: Consolas, "Ubuntu Mono", "Droid Sans Mono", "Lucida Console", "Courier New", Courier, monospace;
|
||||||
|
}
|
||||||
|
iframe.perleditor {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid black;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 50em;
|
||||||
|
margin: 0.2em 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This page demonstrates the embeddable
|
||||||
|
<strong><a href="http://webperl.zero-g.net" target="_blank">🕸️🐪 WebPerl</a>
|
||||||
|
Code Demo Editor</strong>, which can be embedded using <code><iframe></code> elements, including
|
||||||
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox" target="_blank">sandboxing</a>.
|
||||||
|
The documentation is contained in the source of this page, please use
|
||||||
|
the "View Source" function of your browser to view it, or have a look at
|
||||||
|
<a href="https://github.com/haukex/webperl/tree/master/web/democode"
|
||||||
|
target="_blank">the project sources on GitHub</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- First, you have to include the following hidden IFrame, which
|
||||||
|
loads the "Perl runner". This is (currently) necessary because this
|
||||||
|
IFrame needs to re-load itself in order to re-run Perl. This IFrame
|
||||||
|
*must* have the "name='perlrunner'" attribute and must be embedded at
|
||||||
|
the same level as the Perl editor IFrame(s). The frames communicate
|
||||||
|
via the "window.postMessage()" mechanism, which is safe for
|
||||||
|
cross-origin communications and sandboxing. Currently, in order to
|
||||||
|
conserve memory, a single runner serves multiple "clients", that is,
|
||||||
|
the "editor" IFrames below.
|
||||||
|
|
||||||
|
It is also possible to link to perleditor.html directly: if it
|
||||||
|
detects that it is not running in an IFrame, it will load the runner
|
||||||
|
on its own (after a very brief delay).
|
||||||
|
-->
|
||||||
|
|
||||||
|
<iframe name="perlrunner" sandbox="allow-scripts" src="perlrunner.html" style="display:none;"></iframe>
|
||||||
|
|
||||||
|
<p>This is a simple example of running a oneliner:</p>
|
||||||
|
|
||||||
|
<!-- The following is a basic example showing a single input file and
|
||||||
|
Perl oneliner.
|
||||||
|
|
||||||
|
All files are currently always encoded as UTF-8, which is why the
|
||||||
|
"-CSD" switch is used below. This is not strictly necessary when the
|
||||||
|
input files are pure ASCII, but it is important to remember that Perl
|
||||||
|
does *not* default to UTF-8. Reading/writing binary data via the
|
||||||
|
editor and runner is currently *not* supported.
|
||||||
|
|
||||||
|
Standard input/output redirection is currently not supported. It is
|
||||||
|
also currently not supported to supply STDIN directly to the script,
|
||||||
|
the workaround is to use input files, supply the filenames on the
|
||||||
|
command line, and use Perl's magic ARGV operator "<>". Support for
|
||||||
|
redirections may be added in a future version.
|
||||||
|
|
||||||
|
The JavaScript shown below is not strictly necessary, it is also
|
||||||
|
possible to specify a "src='...'" attribute directly in the IFrame
|
||||||
|
tag, for example using the "Copy Frame URL" link shown in the editor.
|
||||||
|
|
||||||
|
Note that implementing an automatic resize of the IFrame to fit its
|
||||||
|
contents is nontrivial when sandboxing is enabled, which is why a
|
||||||
|
fixed height is used below.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<iframe id="perl1" sandbox="allow-scripts" class="perleditor" style="height:20em;"></iframe>
|
||||||
|
<script>
|
||||||
|
document.getElementById('perl1').src =
|
||||||
|
"perleditor.html#" + encodeURIComponent(JSON.stringify( {
|
||||||
|
inputs: [ { fn:"in.txt", text:"Foo\nBar\nQuz" } ],
|
||||||
|
cmdline: "perl -CSD -pe 's/[aeiou]/_/g' in.txt",
|
||||||
|
} ));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>This example includes several files:</p>
|
||||||
|
|
||||||
|
<!-- The following example demonstrates (almost) all of the possible
|
||||||
|
options that can be passed to the editor.
|
||||||
|
|
||||||
|
The "cmdline" supports only very basic quoting constructs.
|
||||||
|
|
||||||
|
You may specify the text of a script via "script", or, alternatively,
|
||||||
|
a "script_url" from which the script is to be fetched - however, be
|
||||||
|
aware that cross-origin restrictions may limit your ability to fetch
|
||||||
|
URLs from other origins. You can specify the script's filename with
|
||||||
|
"script_fn".
|
||||||
|
|
||||||
|
Input files ("inputs") are specified as an array of objects; the
|
||||||
|
properties of the object are similar to the script: filenames are
|
||||||
|
specified with "fn", and the text of the file via "text", or
|
||||||
|
alternatively, you may specify a "url" from which the content is to
|
||||||
|
be fetched.
|
||||||
|
|
||||||
|
The output files ("outputs") are an array of filenames. After the
|
||||||
|
script finishes, the "Perl runner" will attempt to read these files
|
||||||
|
and display them to the user. It is also possible to specify output
|
||||||
|
files with the same name as an input file, for example if Perl's "-i"
|
||||||
|
option was used.
|
||||||
|
|
||||||
|
The current working directory of Perl defaults to the "home"
|
||||||
|
directory in Emscripten's virtual file system, currently
|
||||||
|
"/home/web_user", and all filenames are relative to this directory.
|
||||||
|
You may also specify absolute filenames such as "/tmp/foo.txt".
|
||||||
|
However, note that intermediate directories are currently not
|
||||||
|
automatically created, so if you specify files with nonexistent
|
||||||
|
directories like "/tmp/foo/bar.txt" or the relative "foo/bar.txt",
|
||||||
|
this will not work.
|
||||||
|
|
||||||
|
Additional options: Setting "mergeStdOutErr" to a true value causes
|
||||||
|
STDOUT and STDERR output to be output together, similar to the way
|
||||||
|
they would be on the console.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<iframe id="perl2" sandbox="allow-scripts" class="perleditor" style="height:42em;"></iframe>
|
||||||
|
<script>
|
||||||
|
document.getElementById('perl2').src =
|
||||||
|
"perleditor.html#" + encodeURIComponent(JSON.stringify( {
|
||||||
|
cmdline: "perl devoweler.pl mytext.txt other.txt",
|
||||||
|
script: "use warnings;\nuse strict;\n\nopen my $vfh, '>', 'vowels.txt' or die $!;\n"
|
||||||
|
+"while (<>) {\n\tprint $vfh $1 while s/([aeiou])/_/i;\n\tprint;\n}\nclose $vfh;",
|
||||||
|
script_fn: "devoweler.pl",
|
||||||
|
inputs: [
|
||||||
|
{ fn: "mytext.txt", text: "Foo\nBar\nQuz\n" },
|
||||||
|
{ fn: "other.txt", text: "Hello, World!" },
|
||||||
|
],
|
||||||
|
outputs: [ "vowels.txt" ],
|
||||||
|
} ));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0.4em;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
font-family: Calibri, Ubuntu, "Droid Sans", Tahoma, Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
pre,textarea,code,.code,.filename,.CodeMirror {
|
||||||
|
font-family: Consolas, "Ubuntu Mono", "Droid Sans Mono", "Lucida Console", "Courier New", Courier, monospace;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
border: 1px solid lightgrey;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
max-height: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codewithfn {
|
||||||
|
margin-top: 0.4em;
|
||||||
|
}
|
||||||
|
.fnfuncs {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.filename {
|
||||||
|
display: inline-block;
|
||||||
|
border: 0;
|
||||||
|
padding: 1px;
|
||||||
|
min-width: 1em;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
.filefuncs {
|
||||||
|
display: inline-block;
|
||||||
|
padding-top: 2px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.2em;
|
||||||
|
}
|
||||||
|
.fakelink {
|
||||||
|
color: darkblue;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.badfilename {
|
||||||
|
background-color: rgba(255,200,200,255);
|
||||||
|
/* also has a placeholder text */
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#perlctrl {
|
||||||
|
margin-top: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#misctools {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid grey;
|
||||||
|
padding: 1px 0.8em 1px 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
#runnerstate {
|
||||||
|
margin-top: 0.2em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
#runnererrors {
|
||||||
|
background-color: rgba(255,200,200,255);
|
||||||
|
margin-top: 0.3em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
padding: 0.1em 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputhere, #outputhere {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
@ -0,0 +1,470 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-us">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>WebPerl Perl Editor</title>
|
||||||
|
|
||||||
|
<!-- ##### WebPerl - http://webperl.zero-g.net #####
|
||||||
|
|
||||||
|
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||||
|
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||||
|
Berlin, Germany, http://www.igb-berlin.de
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the same terms as Perl 5 itself: either the GNU General Public
|
||||||
|
License as published by the Free Software Foundation (either version 1,
|
||||||
|
or, at your option, any later version), or the "Artistic License" which
|
||||||
|
comes with Perl 5.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the licenses for details.
|
||||||
|
|
||||||
|
You should have received a copy of the licenses along with this program.
|
||||||
|
If not, see http://perldoc.perl.org/index-licence.html
|
||||||
|
##### -->
|
||||||
|
|
||||||
|
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css" integrity="sha256-oSrCnRYXvHG31SBifqP2PM1uje7SJUyX0nTwO2RJV54=" crossorigin="anonymous" />
|
||||||
|
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.css" integrity="sha256-I8NyGs4wjbMuBSUE40o55W6k6P7tu/7G28/JGUUYCIs=" crossorigin="anonymous" />
|
||||||
|
<link rel="stylesheet" href="perleditor.css" />
|
||||||
|
|
||||||
|
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.js" integrity="sha256-uRIJ6Wfou5cTtmcCvQNA9lvsYl8sUbZXxnfG+P79ssY=" crossorigin="anonymous"></script>
|
||||||
|
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/mode/perl/perl.min.js" integrity="sha256-Uu9QBfi8gU6J/MzQunal8ewmY+i/BbCkBrcAXA5bcCM=" crossorigin="anonymous"></script>
|
||||||
|
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var mergeStdOutErr = 0; // Possible To-Do for Later: could make an options hash
|
||||||
|
var perlRunner; // the Perl runner iframe found by findPerlRunner()
|
||||||
|
var buttonBlockers = {}; // for updateButtonState()
|
||||||
|
var lastExitStatus; // for runnerState()
|
||||||
|
var loadedRunnerIframe = false; // for findPerlRunner()
|
||||||
|
|
||||||
|
function makeCM (textarea,plain,ro) {
|
||||||
|
return CodeMirror.fromTextArea( textarea[0], {
|
||||||
|
viewportMargin: Infinity, // so browser's search works, not good for long documents though
|
||||||
|
lineNumbers:true, indentWithTabs:true,
|
||||||
|
tabSize:4, indentUnit:4,
|
||||||
|
mode: plain?"text/plain":"perl",
|
||||||
|
readOnly: ro?true:false,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
function runnerState (text) {
|
||||||
|
$('#runnerstate').text( text
|
||||||
|
+ (lastExitStatus ? ' (last exit status was '+lastExitStatus+')'
|
||||||
|
: '') );
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButtonState () {
|
||||||
|
$('#runperl').prop("disabled",
|
||||||
|
Object.keys(buttonBlockers).length>0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
function stdOutput (which, data) { // which: 1=stdout, 2=stderr
|
||||||
|
if (mergeStdOutErr) which = 1;
|
||||||
|
var div = $(which==1?'#stdout':'#stderr');
|
||||||
|
div.show();
|
||||||
|
var cm = div.data('CodeMirrorInstance');
|
||||||
|
if (!cm) {
|
||||||
|
cm = makeCM($('textarea',div),1,1);
|
||||||
|
div.data('CodeMirrorInstance', cm);
|
||||||
|
}
|
||||||
|
if (data && data.length)
|
||||||
|
cm.setValue( cm.getValue() + data );
|
||||||
|
}
|
||||||
|
function clearStdOutput () {
|
||||||
|
$('#stdout,#stderr').each(function (i) {
|
||||||
|
var div = $(this);
|
||||||
|
var cm = div.data('CodeMirrorInstance');
|
||||||
|
if (cm) cm.setValue('');
|
||||||
|
div.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPerlRunner () {
|
||||||
|
// assume calling this function means the runner isn't available
|
||||||
|
buttonBlockers.runnerState = 1;
|
||||||
|
updateButtonState();
|
||||||
|
// poll for perlRunner, which gets set by the message listener
|
||||||
|
var warnAt = Date.now() + 15*1000; // milliseconds
|
||||||
|
var loadIframe = Date.now() + 3*1000; // milliseconds
|
||||||
|
var pollId;
|
||||||
|
pollId = window.setInterval( function () {
|
||||||
|
if (perlRunner)
|
||||||
|
window.clearInterval(pollId);
|
||||||
|
else if (!loadedRunnerIframe && self===top && Date.now()>loadIframe) {
|
||||||
|
console.debug("Perl Editor is attempting to load Perl Runner...");
|
||||||
|
/* This is a special case: We appear to be the toplevel window,
|
||||||
|
* and are unable to contact the runner within a fixed amount of time.
|
||||||
|
* So we assume that someone has linked directly to this page instead
|
||||||
|
* of loading it in an IFrame, so we'll load the runner ourselves. */
|
||||||
|
$('<iframe/>',{name:"perlrunner",sandbox:"allow-scripts",
|
||||||
|
src:"perlrunner.html",style:"display:none;"})
|
||||||
|
.appendTo('body');
|
||||||
|
loadedRunnerIframe = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (window.parent && window.parent.frames["perlrunner"])
|
||||||
|
window.parent.frames["perlrunner"].postMessage(
|
||||||
|
{perlRunnerDiscovery:1}, '*');
|
||||||
|
if ( Date.now()>warnAt ) {
|
||||||
|
$('#runnererrors>pre').text("Perl does not appear to have loaded yet, still waiting...");
|
||||||
|
$('#runnererrors').show();
|
||||||
|
warnAt = Date.now() + 5*1000; // milliseconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', function (event) {
|
||||||
|
var data = event.data;
|
||||||
|
if (data["perlRunnerState"]) {
|
||||||
|
if ( data.perlRunnerState=="Ready" ) {
|
||||||
|
perlRunner = event.source;
|
||||||
|
delete buttonBlockers.runnerState;
|
||||||
|
updateButtonState();
|
||||||
|
}
|
||||||
|
else if ( data.perlRunnerState=="Ended" ) {
|
||||||
|
if ('exitStatus' in data)
|
||||||
|
lastExitStatus = ''+data.exitStatus;
|
||||||
|
// we know the runner will reload itself now
|
||||||
|
perlRunner = null;
|
||||||
|
findPerlRunner();
|
||||||
|
}
|
||||||
|
runnerState("Perl is "+data.perlRunnerState);
|
||||||
|
}
|
||||||
|
else if (data["perlOutput"])
|
||||||
|
stdOutput(data.perlOutput.chan, data.perlOutput.data);
|
||||||
|
else if (data["perlOutputFiles"]) {
|
||||||
|
data.perlOutputFiles.forEach(function (outp) {
|
||||||
|
setupOutputFile(outp.fn, outp.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (data["perlRunnerError"]) {
|
||||||
|
$('#runnererrors').show();
|
||||||
|
$('#runnererrors>pre').append(data.perlRunnerError+"\n");
|
||||||
|
}
|
||||||
|
else console.warn("Perl Editor ignoring unknown message:",data);
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseCmdLine(str) {
|
||||||
|
// not the prettiest code but it works
|
||||||
|
var re = /"((?:\\"|\\\\|[^"])*)"|'((?:\\'|\\\\|[^'])*)'|(\S+)/g;
|
||||||
|
var argv = [];
|
||||||
|
var match;
|
||||||
|
while ((match = re.exec(str)) !== null) {
|
||||||
|
if (typeof match[1] != 'undefined') argv.push(match[1].replace(/\\\\/g,"\\").replace(/\\"/g,"\""));
|
||||||
|
else if (typeof match[2] != 'undefined') argv.push(match[2].replace(/\\\\/g,'\\').replace(/\\'/g,'\''));
|
||||||
|
else if (typeof match[3] != 'undefined') argv.push(match[3]);
|
||||||
|
}
|
||||||
|
return argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchUrl(url,cm) { // fetch the contents of a URL into a CodeMirror instance
|
||||||
|
cm.setValue("Fetching URL\n"+url+"\nPlease wait...");
|
||||||
|
buttonBlockers["fetchUrls"]++;
|
||||||
|
updateButtonState();
|
||||||
|
$.get(url, function (data) {
|
||||||
|
cm.setValue(data);
|
||||||
|
},'text').fail(function (jqXHR,textStatus,errorThrown) {
|
||||||
|
cm.setValue("Fetching URL\n"+url+"\nFailed!\n"+textStatus+"\n"+errorThrown);
|
||||||
|
}).always(function () {
|
||||||
|
buttonBlockers.fetchUrls--;
|
||||||
|
if (!buttonBlockers.fetchUrls)
|
||||||
|
delete buttonBlockers.fetchUrls;
|
||||||
|
updateButtonState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCodeWithFn (fn,targ,ro,isscript) {
|
||||||
|
var div = $('<div/>',{class:"codewithfn"});
|
||||||
|
|
||||||
|
var fnfuncs = $('<div/>',{class:"fnfuncs"}).appendTo(div);
|
||||||
|
|
||||||
|
var filename = $('<input/>',{class:"filename code",type:"text",
|
||||||
|
placeholder:"Enter a filename!"})
|
||||||
|
.appendTo(fnfuncs);
|
||||||
|
filename.val(fn);
|
||||||
|
// autosize the filename text box via a hidden span
|
||||||
|
var fn_width = $('<span/>',
|
||||||
|
{class:"code",style:"display:none;white-space:pre;"}
|
||||||
|
).insertAfter(filename);
|
||||||
|
filename.on('input', function () {
|
||||||
|
var newfn = filename.val();
|
||||||
|
fn_width.text( newfn );
|
||||||
|
filename.width( fn_width.width()+10 );
|
||||||
|
if (newfn.length)
|
||||||
|
filename.removeClass("badfilename");
|
||||||
|
else
|
||||||
|
filename.addClass("badfilename");
|
||||||
|
});
|
||||||
|
/* we need to trigger this handler once when the input
|
||||||
|
* field is added to the document, we do this below */
|
||||||
|
|
||||||
|
var filefuncs = $('<div/>',{class:"filefuncs text"})
|
||||||
|
.appendTo(fnfuncs);
|
||||||
|
|
||||||
|
var conf = $('<span/>', {})
|
||||||
|
.append(
|
||||||
|
" ",
|
||||||
|
"Are you sure?",
|
||||||
|
" ",
|
||||||
|
$('<span/>',{class:"fakelink",text:"Yes"})
|
||||||
|
.click(function () {
|
||||||
|
div.remove();
|
||||||
|
if (isscript) $('#addscript').show();
|
||||||
|
}),
|
||||||
|
" ",
|
||||||
|
$('<span/>',{class:"fakelink",text:"Cancel"})
|
||||||
|
.click(function () { conf.hide(); }),
|
||||||
|
);
|
||||||
|
$('<span/>',{class:"fakelink",text:"Delete"})
|
||||||
|
.appendTo(filefuncs).click(function () {
|
||||||
|
conf.show();
|
||||||
|
});
|
||||||
|
conf.hide();
|
||||||
|
conf.appendTo(filefuncs);
|
||||||
|
|
||||||
|
var ta = $('<textarea/>').appendTo(div);
|
||||||
|
targ.before(div);
|
||||||
|
filename.trigger('input'); // see above
|
||||||
|
var cm = makeCM(ta, !(isscript||fn.match(/\.pl$/i)), ro);
|
||||||
|
div.data('CodeMirrorInstance', cm);
|
||||||
|
return {div:div,ta:ta,cm:cm};
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickNewFilename (inNotOut) {
|
||||||
|
var x = inNotOut ? 'input' : 'output';
|
||||||
|
for (var i=1; i<1000; i++) {
|
||||||
|
var fn = x+i+".txt";
|
||||||
|
var found = $('div.'+x+'s .filename')
|
||||||
|
.filter(function(){ return $(this).val() == fn });
|
||||||
|
if (!found.length)
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
$('#runnererrors>pre').text('Too many '+x+' files');
|
||||||
|
$('#runnererrors').show();
|
||||||
|
throw 'Too many '+x+' files';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupOutputFile (fn, text) {
|
||||||
|
var cm;
|
||||||
|
if (fn) {
|
||||||
|
var founddiv = $('div.outputs')
|
||||||
|
.filter(function(){ return $('.filename',this).val() == fn });
|
||||||
|
if (founddiv.length)
|
||||||
|
cm = founddiv.data('CodeMirrorInstance');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fn = pickNewFilename(false);
|
||||||
|
if (!cm) {
|
||||||
|
var cfn = makeCodeWithFn(fn, $('#outputhere'), 1);
|
||||||
|
cfn.div.addClass("outputs");
|
||||||
|
cm = cfn.cm;
|
||||||
|
}
|
||||||
|
cm.setValue( text ? text : '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupInputFile (inp) {
|
||||||
|
var fn = inp["fn"] ? inp.fn : pickNewFilename(true);
|
||||||
|
var cfn = makeCodeWithFn(fn, $('#inputhere'), 0);
|
||||||
|
cfn.div.addClass("inputs");
|
||||||
|
if (inp["text"])
|
||||||
|
cfn.cm.setValue(inp.text);
|
||||||
|
else if (inp["url"])
|
||||||
|
fetchUrl(inp.url, cfn.cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileData () {
|
||||||
|
var filedata = {};
|
||||||
|
// script
|
||||||
|
var scriptdiv = $('#script');
|
||||||
|
if (scriptdiv.is(':visible')) {
|
||||||
|
filedata.script = scriptdiv.data('CodeMirrorInstance').getValue();
|
||||||
|
filedata.script_fn = scriptdiv.find('.filename').val();
|
||||||
|
}
|
||||||
|
// inputs
|
||||||
|
$('.inputs').each(function () {
|
||||||
|
var div = $(this);
|
||||||
|
var fn = $('.filename',div).val();
|
||||||
|
var text = div.data('CodeMirrorInstance').getValue();
|
||||||
|
if (!filedata["inputs"]) filedata.inputs = [];
|
||||||
|
filedata.inputs.push( { fn:fn, text:text } );
|
||||||
|
});
|
||||||
|
// outputs
|
||||||
|
$('.outputs').each(function () {
|
||||||
|
var fn = $(this).find('.filename').val();
|
||||||
|
if (!filedata["outputs"]) filedata.outputs = [];
|
||||||
|
filedata.outputs.push(fn);
|
||||||
|
});
|
||||||
|
return filedata;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
var hashdata = window.location.hash.substr(1);
|
||||||
|
var hash = hashdata.length>0 ? JSON.parse(decodeURIComponent(hashdata)) : {};
|
||||||
|
|
||||||
|
// script
|
||||||
|
var addscript = $('#addscript');
|
||||||
|
if ( hash["script"] || hash["script_url"] ) {
|
||||||
|
var fn = hash["script_fn"] ? hash.script_fn : 'script.pl';
|
||||||
|
var cfn = makeCodeWithFn(fn, $('#perlctrl'), 0, 1);
|
||||||
|
cfn.div.attr("id", "script");
|
||||||
|
if (hash["script"])
|
||||||
|
cfn.cm.setValue(hash.script);
|
||||||
|
else if (hash["script_url"])
|
||||||
|
fetchUrl(hash.script_url, cfn.cm);
|
||||||
|
addscript.hide();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
addscript.show();
|
||||||
|
addscript.click(function () {
|
||||||
|
addscript.hide();
|
||||||
|
if ($('#script').length) return;
|
||||||
|
var cfn = makeCodeWithFn('script.pl', $('#perlctrl'), 0, 1);
|
||||||
|
cfn.div.attr("id", "script");
|
||||||
|
cfn.cm.setValue("use warnings;\nuse strict;\n\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// command line
|
||||||
|
var argv_inp = $('#argv');
|
||||||
|
var argv_autosize = $('<span/>',
|
||||||
|
{class:"code",style:"display:none;white-space:pre;"}
|
||||||
|
).insertAfter(argv_inp);
|
||||||
|
argv_inp.on('input', function () {
|
||||||
|
argv_autosize.text( argv_inp.val() );
|
||||||
|
argv_inp.width( argv_autosize.width()+10 );
|
||||||
|
});
|
||||||
|
if (hash["cmdline"])
|
||||||
|
argv_inp.val(hash.cmdline);
|
||||||
|
argv_inp.trigger('input');
|
||||||
|
|
||||||
|
// input files
|
||||||
|
$('.inputs').remove();
|
||||||
|
if ( hash["inputs"] ) hash.inputs.forEach(function(inp) {
|
||||||
|
setupInputFile(inp);
|
||||||
|
});
|
||||||
|
$('#addinput').click(function () {
|
||||||
|
setupInputFile( {} );
|
||||||
|
});
|
||||||
|
|
||||||
|
// stdout/stderr
|
||||||
|
if (hash["mergeStdOutErr"]) {
|
||||||
|
mergeStdOutErr = 1;
|
||||||
|
$('#stdout .filename').val("STDOUT+STDERR");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mergeStdOutErr = 0;
|
||||||
|
$('#stdout .filename').val("STDOUT");
|
||||||
|
}
|
||||||
|
clearStdOutput();
|
||||||
|
|
||||||
|
// output files
|
||||||
|
$('.outputs').remove();
|
||||||
|
if ( hash["outputs"] ) hash.outputs.forEach(function(outp) {
|
||||||
|
setupOutputFile(outp);
|
||||||
|
});
|
||||||
|
$('#addoutput').click(function () {
|
||||||
|
setupOutputFile();
|
||||||
|
});
|
||||||
|
|
||||||
|
// "run perl" button
|
||||||
|
$('#runperl').click( function () {
|
||||||
|
clearStdOutput();
|
||||||
|
// command-line args
|
||||||
|
var argv = parseCmdLine(argv_inp.val());
|
||||||
|
if (argv.length<1 || argv[0]!="perl") {
|
||||||
|
$('#runnererrors>pre').text('Invalid command line, command must be "perl"');
|
||||||
|
$('#runnererrors').show();
|
||||||
|
return;
|
||||||
|
} // else
|
||||||
|
argv.shift();
|
||||||
|
$('#runnererrors>pre').text('');
|
||||||
|
$('#runnererrors').hide();
|
||||||
|
var rp_data = getFileData();
|
||||||
|
rp_data.argv = argv;
|
||||||
|
// send message to runner
|
||||||
|
buttonBlockers.runnerState = 1;
|
||||||
|
updateButtonState();
|
||||||
|
lastExitStatus = null;
|
||||||
|
runnerState("Requesting Perl Run...");
|
||||||
|
perlRunner.postMessage({ runPerl: rp_data }, '*');
|
||||||
|
});
|
||||||
|
|
||||||
|
// "copy frame url" function
|
||||||
|
$('#copyurl').click(function () {
|
||||||
|
var pageurl = $('#pageurl');
|
||||||
|
var data = getFileData();
|
||||||
|
data.cmdline = $('#argv').val();
|
||||||
|
var loc = new URL(window.location);
|
||||||
|
loc.hash = encodeURIComponent(JSON.stringify(data));
|
||||||
|
pageurl.val(loc);
|
||||||
|
pageurl.show();
|
||||||
|
pageurl[0].select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
pageurl.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
// start looking for the Perl runner
|
||||||
|
findPerlRunner();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="inputhere" style="display:none;"></div>
|
||||||
|
|
||||||
|
<div id="perlctrl">
|
||||||
|
<button id="runperl"><code>perl</code> ►</button>
|
||||||
|
<input type="text" id="argv" class="code" value='perl' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="runnerstate" class="text">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="runnererrors" style="display:none;">
|
||||||
|
<pre></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="stdout" class="codewithfn" style="display:none;">
|
||||||
|
<input type="text" class="filename code" readonly="readonly" value="STDOUT" size="14" />
|
||||||
|
<textarea></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="stderr" class="codewithfn" style="display:none;">
|
||||||
|
<input type="text" class="filename code" readonly="readonly" value="STDERR" size="14" />
|
||||||
|
<textarea></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="outputhere" style="display:none;"></div>
|
||||||
|
|
||||||
|
<div class="text">
|
||||||
|
<textarea id="pageurl" style="display:none;"></textarea>
|
||||||
|
<div id="misctools">
|
||||||
|
Tools
|
||||||
|
|
||||||
|
<span id="addscript" class="fakelink" style="display:none;">Add Script</span>
|
||||||
|
|
||||||
|
<span id="addinput" class="fakelink">Add Input File</span>
|
||||||
|
|
||||||
|
<span id="addoutput" class="fakelink">Add Output File</span>
|
||||||
|
|
||||||
|
<span id="copyurl" class="fakelink">Copy Frame URL</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text" id="footer">
|
||||||
|
Powered by <a href="http://webperl.zero-g.net/" target="_blank">🕸️🐪 WebPerl</a> (beta)
|
||||||
|
<!-- Link with target="_blank" may not work in a sandboxed iframe, so provide URL: -->
|
||||||
|
<code>http://webperl.zero-g.net/</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,177 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-us">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>WebPerl Perl Runner</title>
|
||||||
|
|
||||||
|
<!-- ##### WebPerl - http://webperl.zero-g.net #####
|
||||||
|
|
||||||
|
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||||
|
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||||
|
Berlin, Germany, http://www.igb-berlin.de
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the same terms as Perl 5 itself: either the GNU General Public
|
||||||
|
License as published by the Free Software Foundation (either version 1,
|
||||||
|
or, at your option, any later version), or the "Artistic License" which
|
||||||
|
comes with Perl 5.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the licenses for details.
|
||||||
|
|
||||||
|
You should have received a copy of the licenses along with this program.
|
||||||
|
If not, see http://perldoc.perl.org/index-licence.html
|
||||||
|
##### -->
|
||||||
|
|
||||||
|
<!-- Possible To-Do for Later: This whole thing could probably also be
|
||||||
|
accomplished with a Web Worker, but that would probably require a
|
||||||
|
stripped-down version of webperl.js (that loads things without
|
||||||
|
using window.* and especially document.*
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts
|
||||||
|
|
||||||
|
Of course, at some point I should investigate how difficult it really
|
||||||
|
is to re-start an Emscripten program...
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script src="../webperl.js"></script>
|
||||||
|
<!--script src="https://webperlcdn.zero-g.net/v0.07-beta/webperl.js"
|
||||||
|
integrity="sha256-jL8SB7St5ou4+hb0frK0k6VCQXsWQ1wolDrdU7i4juc=" crossorigin="anonymous"></script-->
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Perl.noMountIdbfs=true; // we're sandboxed
|
||||||
|
Perl.endAfterMain=true; // act like command-line perl
|
||||||
|
|
||||||
|
var knownClients = [];
|
||||||
|
var currentClient; // which client we're running Perl for, also keeps state
|
||||||
|
var curOutputFiles;
|
||||||
|
var stdbuf = [null,'',''];
|
||||||
|
|
||||||
|
function reportErr (err) {
|
||||||
|
if (currentClient)
|
||||||
|
currentClient.postMessage({ perlRunnerError: err },'*');
|
||||||
|
else
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Perl.addStateChangeListener(function (from,to) {
|
||||||
|
if (from==to) return; // won't be needed as of v0.09-beta
|
||||||
|
if (to=="Ended" && currentClient) {
|
||||||
|
for (var chan=1;chan<=2;chan++) // flush buffers
|
||||||
|
if (stdbuf[chan].length) {
|
||||||
|
currentClient.postMessage({ perlOutput: { chan:chan, data:stdbuf[chan] } },'*');
|
||||||
|
stdbuf[chan] = '';
|
||||||
|
}
|
||||||
|
currentClient.postMessage({ perlRunnerState: Perl.state,
|
||||||
|
exitStatus: Perl.exitStatus },'*');
|
||||||
|
for(var i=0; i<knownClients.length; i++)
|
||||||
|
if (knownClients[i]!=currentClient)
|
||||||
|
knownClients[i].postMessage({ perlRunnerState: Perl.state },'*');
|
||||||
|
if (curOutputFiles) {
|
||||||
|
var ofs = curOutputFiles.map(function (file) {
|
||||||
|
//TODO Later: Support binary files as well?
|
||||||
|
// {encoding:"binary"} => readFile returns Uint8Array
|
||||||
|
// Should then also provide the same support on FS.writeFile() as well
|
||||||
|
var of = { fn: file };
|
||||||
|
if (!file) return of;
|
||||||
|
try {
|
||||||
|
of.text = FS.readFile(file, {encoding:"utf8"});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
reportErr("couldn't read "+file+" because "+e);
|
||||||
|
}
|
||||||
|
return of;
|
||||||
|
});
|
||||||
|
currentClient.postMessage({ perlOutputFiles: ofs },'*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for(var i=0; i<knownClients.length; i++)
|
||||||
|
knownClients[i].postMessage({ perlRunnerState: Perl.state },'*');
|
||||||
|
}
|
||||||
|
if (to=="Ended") {
|
||||||
|
if (!currentClient)
|
||||||
|
console.error("Internal Error: Perl state change to Ended with no client");
|
||||||
|
window.location.reload(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Perl.output = function (str,chan) {
|
||||||
|
stdbuf[chan] += str;
|
||||||
|
var pos = stdbuf[chan].lastIndexOf("\n");
|
||||||
|
if (pos<0) return;
|
||||||
|
var lines = stdbuf[chan].slice(0,pos+1);
|
||||||
|
if (currentClient)
|
||||||
|
currentClient.postMessage({ perlOutput: { chan:chan, data:lines } },'*');
|
||||||
|
else
|
||||||
|
console.error("Internal Error: Output on",chan==1?"STDOUT":"STDERR","with no client:",lines);
|
||||||
|
stdbuf[chan] = stdbuf[chan].slice(pos+1);
|
||||||
|
};
|
||||||
|
|
||||||
|
function saveFile (fn, data) {
|
||||||
|
if (fn.substring(0,1)!='/') // if relative, make absolute
|
||||||
|
fn = FS.joinPath([FS.cwd(), fn]);
|
||||||
|
try {
|
||||||
|
FS.writeFile(fn, data);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
reportErr("couldn't write "+fn+" because "+e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', function (event) {
|
||||||
|
if (event.data["perlRunnerDiscovery"]) {
|
||||||
|
if (!knownClients.includes(event.source))
|
||||||
|
knownClients.push(event.source);
|
||||||
|
event.source.postMessage({ perlRunnerState: Perl.state },'*');
|
||||||
|
}
|
||||||
|
else if (event.data["runPerl"]) {
|
||||||
|
if (!knownClients.includes(event.source))
|
||||||
|
knownClients.push(event.source);
|
||||||
|
// check state
|
||||||
|
if (currentClient && currentClient !== event.source) {
|
||||||
|
console.error("Attempt to run Perl from",event.source,
|
||||||
|
"but am already running Perl for",currentClient);
|
||||||
|
reportErr("Attempt to run Perl (from "+event.origin
|
||||||
|
+") but am already running Perl for someone else (see JavaScript console)");
|
||||||
|
return;
|
||||||
|
} // else
|
||||||
|
currentClient = event.source;
|
||||||
|
if (Perl.state!="Ready") {
|
||||||
|
reportErr("Attempt to run Perl in state "+Perl.state);
|
||||||
|
return;
|
||||||
|
} // else
|
||||||
|
// set up files and run perl
|
||||||
|
var rp = event.data.runPerl;
|
||||||
|
//TODO: we don't check for overlaps in filenames between script+input files (maybe the editor should do that)
|
||||||
|
// one solution would be to just have the script be an input file (code mirror syntax highlighting based on filename?)
|
||||||
|
// note overlaps of output filenames with input files is ok
|
||||||
|
// we also don't check for duplicate filenames
|
||||||
|
if (rp["script"])
|
||||||
|
saveFile(rp["script_fn"] ? rp.script_fn : 'script.pl', rp.script);
|
||||||
|
//TODO Later: can we support STDIN? (probably need to look at webperl.js)
|
||||||
|
if (rp["inputs"])
|
||||||
|
rp.inputs.forEach(function (inp) {
|
||||||
|
if (!inp.fn) return;
|
||||||
|
saveFile(inp.fn, inp.text);
|
||||||
|
});
|
||||||
|
curOutputFiles = rp["outputs"];
|
||||||
|
Perl.start( rp["argv"] ? rp.argv : [] );
|
||||||
|
}
|
||||||
|
else console.warn("Perl Runner ignoring unknown message:", event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
Perl.init(function () {
|
||||||
|
Module['thisProgram'] = 'perl';
|
||||||
|
FS.currentPath = ENV.HOME; // NOTE: https://github.com/kripken/emscripten/issues/5873
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue