First commit
This commit is contained in:
commit
853afd8c57
4 changed files with 586 additions and 0 deletions
53
blash.css
Normal file
53
blash.css
Normal file
|
@ -0,0 +1,53 @@
|
|||
body
|
||||
{
|
||||
background-color : black;
|
||||
color : #888;
|
||||
font-family : Terminus, courier, monospace, fixed;
|
||||
font-size : 13px;
|
||||
}
|
||||
|
||||
input.promptInput
|
||||
{
|
||||
background-color : black;
|
||||
border : 0;
|
||||
color : #888;
|
||||
font-family : Terminus, courier, monospace, fixed;
|
||||
font-size : 13px;
|
||||
}
|
||||
|
||||
div#blashWindow
|
||||
{
|
||||
width : 600px;
|
||||
height : 400px;
|
||||
margin : auto;
|
||||
padding : 10px;
|
||||
border : 1px solid #888;
|
||||
overflow : auto;
|
||||
}
|
||||
|
||||
a.bannerLink
|
||||
{
|
||||
color : white;
|
||||
}
|
||||
|
||||
span.directory
|
||||
{
|
||||
color : #2553ee;
|
||||
}
|
||||
|
||||
.file
|
||||
{
|
||||
color : #4f4;
|
||||
}
|
||||
|
||||
span.syntax
|
||||
{
|
||||
color : white;
|
||||
font-weight : bold;
|
||||
}
|
||||
|
||||
span.brief
|
||||
{
|
||||
color : green;
|
||||
}
|
||||
|
193
blash.js
Normal file
193
blash.js
Normal file
|
@ -0,0 +1,193 @@
|
|||
var shell = null;
|
||||
|
||||
function blash ()
|
||||
{
|
||||
/************ ATTRIBUTES **************/
|
||||
/** Object containing the parsed JSON configuration object */
|
||||
this.json = {};
|
||||
|
||||
/** Shell window object */
|
||||
this.window = document.getElementById ( "blashWindow" );
|
||||
|
||||
/** Escape sequences to be parsed in the prompt text */
|
||||
this.promptSequences = new Array();
|
||||
|
||||
/** Array containing the history of given commands */
|
||||
this.history = new Array();
|
||||
|
||||
/** Index to the current history element */
|
||||
this.history_index = -1;
|
||||
|
||||
/** Current path */
|
||||
this.path = '/';
|
||||
|
||||
/** Element containing the output of the command */
|
||||
this.cmdOut = document.getElementById ( "blashCmdOut" );
|
||||
|
||||
/** Element containing the text of the prompt */
|
||||
this.promptText = document.getElementById ( "promptText" );
|
||||
|
||||
/** Input field used as prompt */
|
||||
this.prompt = document.getElementsByName ( "blashPrompt" )[0];
|
||||
|
||||
/** Counter of the open <span> tags when replacing the colours in the command prompt */
|
||||
this.__open_spans = 0;
|
||||
/**************************************/
|
||||
|
||||
this.prompt.focus();
|
||||
|
||||
var json_config = window.location.href;
|
||||
json_config = json_config.replace ( /\/([a-zA-Z\.]+)$/, '/blash.json' );
|
||||
|
||||
var http = new XMLHttpRequest();
|
||||
http.open ( "GET", json_config, true );
|
||||
|
||||
http.onreadystatechange = function ()
|
||||
{
|
||||
if ( http.readyState == 4 && http.status == 200 )
|
||||
{
|
||||
shell.json = eval ( '(' + http.responseText + ')' );
|
||||
|
||||
shell.promptText.innerHTML = ( shell.json.promptText ) ? shell.json.promptText : "[%n@%m %W] $ ";
|
||||
shell.promptText.innerHTML = shell.unescapePrompt ( promptText.innerHTML, shell.json.promptSequences );
|
||||
|
||||
if ( shell.json.banner.length > 0 )
|
||||
{
|
||||
var banner = document.createElement ( "span" );
|
||||
banner.setAttribute ( "id", "banner" );
|
||||
banner.innerHTML = shell.json.banner;
|
||||
shell.window.insertBefore ( banner, shell.promptText );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http.send ( null );
|
||||
|
||||
this.getKey = function ( e )
|
||||
{
|
||||
var evt = ( window.event ) ? window.event : e;
|
||||
var key = ( evt.charCode ) ? evt.charCode : evt.keyCode;
|
||||
|
||||
if ( key == 13 || key == 10 )
|
||||
{
|
||||
if ( this.prompt.value.length != 0 )
|
||||
{
|
||||
this.prompt.value.match ( /^([^\s]+)\s*(.*)$/ );
|
||||
var cmd = RegExp.$1;
|
||||
var arg = RegExp.$2;
|
||||
var cmd_found = false;
|
||||
this.history.push ( this.prompt.value );
|
||||
this.history_index = -1;
|
||||
|
||||
for ( i=0; i < this.json.commands.length && !cmd_found; i++ )
|
||||
{
|
||||
if ( this.json.commands[i].name == cmd )
|
||||
{
|
||||
cmd_found = true;
|
||||
var out = this.json.commands[i].action ( arg );
|
||||
|
||||
if ( out.length > 0 )
|
||||
{
|
||||
this.cmdOut.innerHTML = out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !cmd_found )
|
||||
{
|
||||
this.cmdOut.innerHTML = this.json.shellName + ": command not found: " + cmd + '<br/>';
|
||||
}
|
||||
|
||||
var value = this.prompt.value;
|
||||
var out = this.cmdOut.innerHTML;
|
||||
|
||||
this.window.removeChild ( this.prompt );
|
||||
this.window.removeChild ( this.cmdOut );
|
||||
this.window.innerHTML += value + '<br/>' + out + this.promptText.innerHTML;
|
||||
|
||||
this.prompt = document.createElement ( 'input' );
|
||||
this.prompt.setAttribute ( 'name', 'blashPrompt' );
|
||||
this.prompt.setAttribute ( 'type', 'text' );
|
||||
this.prompt.setAttribute ( 'class', 'promptInput' );
|
||||
this.prompt.setAttribute ( 'autocomplete', 'off' );
|
||||
this.prompt.setAttribute ( 'onkeyup', 'shell.getKey ( event )' );
|
||||
|
||||
this.cmdOut = document.createElement ( 'div' );
|
||||
this.cmdOut.setAttribute ( 'id', 'blashCmdOut' );
|
||||
this.cmdOut.setAttribute ( 'class', 'blashCmdOut' );
|
||||
this.cmdOut.innerHTML = '<br/>';
|
||||
|
||||
this.window.appendChild ( this.prompt );
|
||||
this.window.appendChild ( this.cmdOut );
|
||||
this.prompt.focus();
|
||||
}
|
||||
} else if ( key == 38 || key == 40 ) {
|
||||
if ( key == 38 )
|
||||
{
|
||||
if ( this.history_index < 0 )
|
||||
{
|
||||
this.history_index = this.history.length - 1;
|
||||
this.prompt.value = this.history[ this.history_index ];
|
||||
} else if ( this.history_index == 0 ) {
|
||||
// We're already on the first history element
|
||||
} else {
|
||||
this.history_index--;
|
||||
this.prompt.value = this.history[ this.history_index ];
|
||||
}
|
||||
} else if ( key == 40 ) {
|
||||
if ( this.history_index < 0 )
|
||||
{
|
||||
// We're already on the last element, don't do anything
|
||||
} else if ( this.history_index == this.history.length - 1 ) {
|
||||
this.prompt.value = '';
|
||||
} else {
|
||||
this.history_index++;
|
||||
this.prompt.value = this.history[ this.history_index ];
|
||||
}
|
||||
}
|
||||
|
||||
this.prompt.focus();
|
||||
}
|
||||
}
|
||||
|
||||
this.unescapePrompt = function ( prompt, sequences )
|
||||
{
|
||||
var re = new RegExp ( "([^\]?)#\{([0-9]+)\}" );
|
||||
|
||||
while ( prompt.match ( re ))
|
||||
{
|
||||
if ( this.__open_spans > 0 )
|
||||
{
|
||||
prompt = prompt.replace ( re, RegExp.$1 + "</span><span style=\"color: #" + RegExp.$2 + "\">" );
|
||||
} else {
|
||||
prompt = prompt.replace ( re, RegExp.$1 + "<span style=\"color: #" + RegExp.$2 + "\">" );
|
||||
this.__open_spans++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( this.__open_spans > 0 )
|
||||
{
|
||||
prompt = prompt + "</span>";
|
||||
}
|
||||
|
||||
for ( i=0; i < sequences.length; i++ )
|
||||
{
|
||||
prompt = this.unescapePromptSequence ( prompt, sequences[i].sequence, sequences[i].text(), sequences[i].default_text );
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
this.unescapePromptSequence = function ( prompt, sequence, text, default_text )
|
||||
{
|
||||
var re = new RegExp ( "([^\]?)" + sequence, "g" );
|
||||
|
||||
if ( prompt.match ( re ))
|
||||
{
|
||||
prompt = prompt.replace ( re, (( text ) ? RegExp.$1 + text : RegExp.$1 + default_text ));
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
}
|
||||
|
324
blash.json
Normal file
324
blash.json
Normal file
|
@ -0,0 +1,324 @@
|
|||
{
|
||||
"banner" : "blash version 0.1<br/>" +
|
||||
"Copyright (C) 2010 BlackLight <blacklight@autistici.org>" +
|
||||
"<br/>Licence GPLv3+: GNU GPL version 3 or later " +
|
||||
"<<a class=\"bannerLink\" href=\"http://gnu.org/licences/gpl.html\">" +
|
||||
"http://gnu.org/licences/gpl.html</a>><br/><br/>" +
|
||||
"This is free software; you are free to change and " +
|
||||
"redistribuite it.<br/>There is NO WARRANTY, to the " +
|
||||
"extent permitted by law.<br/>" +
|
||||
"Type '<span class=\"brief\">man blash</span>' for help on usage and available commands<br/><br/>",
|
||||
|
||||
"user" : "blacklight",
|
||||
"machine" : "localhost",
|
||||
"shellName" : "blash",
|
||||
"basepath" : "/",
|
||||
"promptText" : "[#{800}%n#{888}@#{800}%m#{888} %W] $ ",
|
||||
"promptSequences" : [
|
||||
{
|
||||
"sequence" : "%n",
|
||||
"default_text" : "blacklight",
|
||||
"text" : function () {
|
||||
return shell.json.user;
|
||||
},
|
||||
},
|
||||
{
|
||||
"sequence" : "%m",
|
||||
"default_text" : "localhost",
|
||||
"text" : function () {
|
||||
return shell.json.machine;
|
||||
},
|
||||
},
|
||||
{
|
||||
"sequence" : "%W",
|
||||
"default_text" : "~",
|
||||
"text" : function () {
|
||||
return shell.json.basepath;
|
||||
},
|
||||
}
|
||||
],
|
||||
|
||||
"directories" : [
|
||||
{
|
||||
"path" : "/",
|
||||
"type" : "directory",
|
||||
},
|
||||
{
|
||||
"path" : "/home",
|
||||
"type" : "directory",
|
||||
},
|
||||
{
|
||||
"path" : "/home/blacklight",
|
||||
"type" : "directory",
|
||||
},
|
||||
{
|
||||
"path" : "/etc",
|
||||
"type" : "directory",
|
||||
},
|
||||
{
|
||||
"path" : "/initrd",
|
||||
"type" : "file",
|
||||
"href" : "http://www.google.com",
|
||||
},
|
||||
],
|
||||
|
||||
"commands" : [
|
||||
{
|
||||
"name" : "pwd",
|
||||
|
||||
"info" : {
|
||||
"syntax" : "pwd",
|
||||
"brief" : "Print name of current/working directory",
|
||||
},
|
||||
|
||||
"action" : function ( arg ) {
|
||||
if ( arg )
|
||||
{
|
||||
if ( arg.length > 0 )
|
||||
{
|
||||
return this.name + ": Too many arguments<br/>";
|
||||
} else {
|
||||
return shell.path + '<br/>';
|
||||
}
|
||||
} else {
|
||||
return shell.path + '<br/>';
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "clear",
|
||||
|
||||
"info" : {
|
||||
"syntax" : "clear",
|
||||
"brief" : "Clear the terminal screen",
|
||||
},
|
||||
|
||||
"action" : function ( arg ) {
|
||||
var out = '';
|
||||
|
||||
shell.window.innerHTML =
|
||||
'<span id="promptText" class="promptText"></span>' +
|
||||
'<input type="text" class="promptInput" name="blashPrompt" autocomplete="off" onkeyup="shell.getKey ( event )"/>' +
|
||||
'<span id="blashCmdOut" class="blashCmdOut"/></span>';
|
||||
|
||||
return out;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "man",
|
||||
|
||||
"info" : {
|
||||
"syntax" : "man <page>",
|
||||
"brief" : "Display the manual page for the command 'page'",
|
||||
},
|
||||
|
||||
"action" : function ( arg ) {
|
||||
var out = '';
|
||||
|
||||
if ( arg == undefined || arg.length == 0 )
|
||||
{
|
||||
return "What manual page do you want?<br/>\n";
|
||||
}
|
||||
|
||||
var cmd = shell.json.commands;
|
||||
|
||||
if ( arg == 'blash' )
|
||||
{
|
||||
var commands = new Array();
|
||||
out = '<span class="brief">Type "<span class="syntax">man <command></span>" for a more detailed help on these commands</span><br/><br/>';
|
||||
|
||||
for ( var i=0; i < cmd.length; i++ )
|
||||
{
|
||||
commands.push ( cmd[i].info.syntax );
|
||||
}
|
||||
|
||||
commands.sort();
|
||||
|
||||
for ( var i in commands )
|
||||
{
|
||||
out += '<span class="syntax">' + commands[i] + '</span><br/>';
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
var found = false;
|
||||
|
||||
for ( var i=0; i < cmd.length && !found; i++ )
|
||||
{
|
||||
if ( arg == cmd[i].name )
|
||||
{
|
||||
if ( cmd[i].info.syntax.length > 0 && cmd[i].info.brief.length > 0 )
|
||||
{
|
||||
found = true;
|
||||
|
||||
out =
|
||||
'<span class="syntax">' + cmd[i].info.syntax + '</span><br/><br/>' +
|
||||
'<span class="brief">' + cmd[i].info.brief + '</span><br/>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !found )
|
||||
{
|
||||
return "No manual entry for " + arg + "<br/>\n";
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "ls",
|
||||
|
||||
"info" : {
|
||||
"syntax" : "ls [path]",
|
||||
"brief" : "List directory contents",
|
||||
},
|
||||
|
||||
"action" : function ( arg )
|
||||
{
|
||||
var dirs = new Array();
|
||||
var out = '';
|
||||
var exists = false;
|
||||
|
||||
if ( !arg || arg.length == 0 )
|
||||
{
|
||||
var re = new RegExp ( '^' + shell.path + '[^/]+$' );
|
||||
} else if ( arg && arg.length > 0 ) {
|
||||
var re = null;
|
||||
|
||||
if ( arg.match ( /^\// ))
|
||||
{
|
||||
re = new RegExp ( '^' + arg + '/[^/]+$' );
|
||||
} else {
|
||||
re = new RegExp ( '^' + shell.path +
|
||||
(( shell.path == '/' ) ? '' : '/' ) +
|
||||
arg + '/[^/]+$' );
|
||||
}
|
||||
}
|
||||
|
||||
for ( var i=0; i < shell.json.directories.length; i++ )
|
||||
{
|
||||
var dir = shell.json.directories[i];
|
||||
|
||||
if ( dir.path.match ( re ))
|
||||
{
|
||||
exists = true;
|
||||
dir.path.match ( /\/([^\/]+)$/ );
|
||||
dirs.push ({
|
||||
"path" : RegExp.$1,
|
||||
"type" : dir.type,
|
||||
"href" : dir.href,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ( dirs.length > 0 )
|
||||
{
|
||||
var ordered = false;
|
||||
|
||||
// Directories go first
|
||||
while ( !ordered )
|
||||
{
|
||||
ordered = true;
|
||||
|
||||
for ( var i=0; i < dirs.length-1; i++ )
|
||||
{
|
||||
for ( var j=i+1; j < dirs.length; j++ )
|
||||
{
|
||||
if ( dirs[i].type == 'file' && dirs[j].type == 'directory' )
|
||||
{
|
||||
var tmp = dirs[i];
|
||||
dirs[i] = dirs[j];
|
||||
dirs[j] = tmp;
|
||||
ordered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ordered = false;
|
||||
|
||||
// Sort the names
|
||||
while ( !ordered )
|
||||
{
|
||||
ordered = true;
|
||||
|
||||
for ( var i=0; i < dirs.length-1; i++ )
|
||||
{
|
||||
for ( var j=i+1; j < dirs.length; j++ )
|
||||
{
|
||||
if ( dirs[i].type == dirs[j].type && dirs[i].path > dirs[j].path )
|
||||
{
|
||||
var tmp = dirs[i];
|
||||
dirs[i] = dirs[j];
|
||||
dirs[j] = tmp;
|
||||
ordered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( var i in dirs )
|
||||
{
|
||||
if ( dirs[i] )
|
||||
{
|
||||
if ( dirs[i].path.length > 0 )
|
||||
{
|
||||
if ( dirs[i].type == 'directory' )
|
||||
{
|
||||
out += '<span class="directory">' + dirs[i].path + '</span>/<br/>';
|
||||
} else {
|
||||
if ( dirs[i].href && dirs[i].href.length > 0 )
|
||||
{
|
||||
out += '<a href="' + dirs[i].href + '" class="file" target="_new">' + dirs[i].path + '</a>*<br/>';
|
||||
} else {
|
||||
out += '<span class="file">' + dirs[i].path + '</span><br/>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !exists )
|
||||
{
|
||||
var re = null;
|
||||
|
||||
if ( arg.match ( /^\// ))
|
||||
{
|
||||
re = new RegExp ( '^' + arg );
|
||||
} else {
|
||||
re = new RegExp ( '^' + shell.path +
|
||||
(( shell.path == '/' ) ? '' : '/' ) + arg );
|
||||
}
|
||||
|
||||
for ( var i=0; i < shell.json.directories.length; i++ )
|
||||
{
|
||||
var dir = shell.json.directories[i];
|
||||
|
||||
if ( dir.path.match ( re ))
|
||||
{
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !exists )
|
||||
{
|
||||
out = this.name + ": cannot access " + arg +
|
||||
": No such file or directory<br/>";
|
||||
} else {
|
||||
out = '';
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
|
16
index.html
Normal file
16
index.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Blash - An AJAX interactive shell emulator for web browsing</title>
|
||||
<script type="text/javascript" language="javascript" src="blash.js"></script>
|
||||
<link rel="stylesheet" href="blash.css" type="text/css">
|
||||
</head>
|
||||
|
||||
<body onload="shell = new blash()">
|
||||
<div id="blashWindow" onmouseup="shell.prompt.focus()">
|
||||
<span id="promptText" class="promptText"></span>
|
||||
<input type="text" class="promptInput" name="blashPrompt" autocomplete="off" onkeyup="shell.getKey ( event )"/>
|
||||
<span id="blashCmdOut" class="blashCmdOut"/></span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in a new issue