opencloud shell prototype; wip
diff --git a/planetstack/core/dashboard/shell/constants.js b/planetstack/core/dashboard/shell/constants.js
new file mode 100644
index 0000000..c7498d8
--- /dev/null
+++ b/planetstack/core/dashboard/shell/constants.js
@@ -0,0 +1,39 @@
+// TryMongo
+//
+// Copyright (c) 2009 Kyle Banker
+// Licensed under the MIT Licence.
+// http://www.opensource.org/licenses/mit-license.php
+
+var DefaultInputHtml = function(stack) {
+ var linePrompt = "";
+ if(stack == 0) {
+ linePrompt += "<span class='prompt'> ></span>";
+ }
+ else {
+ for(var i=0; i <= stack; i++) {
+ linePrompt += "<span class='prompt'>.</span>";
+ }
+ }
+ return "<div class='line'>" +
+ linePrompt +
+ "<input type='text' class='readLine active' />" +
+ "</div>";
+}
+
+var EnterKeyCode = 13;
+var UpArrowKeyCode = 38;
+var DownArrowKeyCode = 40;
+
+var PTAG = function(str) {
+ return "<pre>" + str + "</pre>";
+}
+
+var BR = function() {
+ return "<br/>";
+}
+
+var JavascriptKeywords = ['abstract', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum', 'export', 'extends', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', 'with', 'alert', 'date', 'eval'];
+
+var JavascriptClassNames = ['Array', 'String', 'Object']
+
+var MongoKeywords = ['help'];
diff --git a/planetstack/core/dashboard/shell/object_id.js b/planetstack/core/dashboard/shell/object_id.js
new file mode 100644
index 0000000..15cbbb9
--- /dev/null
+++ b/planetstack/core/dashboard/shell/object_id.js
@@ -0,0 +1,12 @@
+var ObjectIdCounter = 0;
+
+var ObjectId = function() {
+ this.counter = (ObjectIdCounter += 1);
+ this.str = this.counter;
+ this.initialize();
+ return this.counter;
+};
+
+ObjectId.prototype.initialize = function() {
+ return this.counter;
+}
diff --git a/planetstack/core/dashboard/shell/opencloud_shell.css b/planetstack/core/dashboard/shell/opencloud_shell.css
new file mode 100644
index 0000000..1d64961
--- /dev/null
+++ b/planetstack/core/dashboard/shell/opencloud_shell.css
@@ -0,0 +1,42 @@
+#terminal {
+ width: 960px;
+ margin:50px auto 50px auto;
+ border: 1px solid black;
+ height: 400px;
+ background: black;
+ overflow: scroll;
+}
+
+#terminal div.line {
+ width: 940px;
+ margin-bottom: 5px;
+}
+
+#terminal input {
+ width: 875px;
+ margin: 1px 0 1px 10px;
+ border: none;
+ display: inline;
+ padding: 2px;
+ background: black;
+ color: #45FF17;
+ font-size: 18px;
+ font-family: Monaco, monospace;
+}
+
+#terminal p, #terminal pre {
+ margin: 2px;
+ color: #45FF17;
+ font-size: 18px;
+ font-family: Monaco, serif;
+}
+
+#terminal a {
+ color: #6495ED;
+}
+
+#terminal span.prompt {
+ color: #45FF17;
+ font-size: 16px;
+ margin-left: 2px;
+}
diff --git a/planetstack/core/dashboard/shell/opencloud_shell.js b/planetstack/core/dashboard/shell/opencloud_shell.js
new file mode 100644
index 0000000..634788a
--- /dev/null
+++ b/planetstack/core/dashboard/shell/opencloud_shell.js
@@ -0,0 +1,268 @@
+// TryMongo
+//
+// Copyright (c) 2009 Kyle Banker
+// Licensed under the MIT Licence.
+// http://www.opensource.org/licenses/mit-license.php
+
+// Readline class to handle line input.
+var ReadLine = function(options) {
+ this.options = options || {};
+ this.htmlForInput = this.options.htmlForInput;
+ this.inputHandler = this.options.handler || this.mockHandler;
+ this.scoper = this.options.scoper;
+ this.terminal = $(this.options.terminalId || "#terminal");
+ this.lineClass = this.options.lineClass || '.readLine';
+ this.history = [];
+ this.historyPtr = 0;
+
+ this.initialize();
+};
+
+ReadLine.prototype = {
+
+ initialize: function() {
+ this.addInputLine();
+ },
+
+ // Enter a new input line with proper behavior.
+ addInputLine: function(stackLevel) {
+ stackLevel = stackLevel || 0;
+ this.terminal.append(this.htmlForInput(stackLevel));
+ var ctx = this;
+ ctx.activeLine = $(this.lineClass + '.active');
+
+ // Bind key events for entering and navigting history.
+ ctx.activeLine.bind("keydown", function(ev) {
+ switch (ev.keyCode) {
+ case EnterKeyCode:
+ ctx.processInput(this.value);
+ break;
+ case UpArrowKeyCode:
+ ctx.getCommand('previous');
+ break;
+ case DownArrowKeyCode:
+ ctx.getCommand('next');
+ break;
+ }
+ });
+
+ this.activeLine.focus();
+ },
+
+ // Returns the 'next' or 'previous' command in this history.
+ getCommand: function(direction) {
+ if(this.history.length === 0) {
+ return;
+ }
+ this.adjustHistoryPointer(direction);
+ this.activeLine[0].value = this.history[this.historyPtr];
+ $(this.activeLine[0]).focus();
+ //this.activeLine[0].value = this.activeLine[0].value;
+ },
+
+ // Moves the history pointer to the 'next' or 'previous' position.
+ adjustHistoryPointer: function(direction) {
+ if(direction == 'previous') {
+ if(this.historyPtr - 1 >= 0) {
+ this.historyPtr -= 1;
+ }
+ }
+ else {
+ if(this.historyPtr + 1 < this.history.length) {
+ this.historyPtr += 1;
+ }
+ }
+ },
+
+ // Return the handler's response.
+ processInput: function(value) {
+ var response = this.inputHandler.apply(this.scoper, [value]);
+ this.insertResponse(response.result);
+
+ // Save to the command history...
+ if((lineValue = value.trim()) !== "") {
+ this.history.push(lineValue);
+ this.historyPtr = this.history.length;
+ }
+
+ // deactivate the line...
+ this.activeLine.value = "";
+ this.activeLine.attr({disabled: true});
+ this.activeLine.removeClass('active');
+
+ // and add add a new command line.
+ this.addInputLine(response.stack);
+ },
+
+ insertResponse: function(response) {
+ if(response.length < 3) {
+ this.activeLine.parent().append("<p class='response'></p>");
+ }
+ else {
+ this.activeLine.parent().append("<p class='response'>" + response + "</p>");
+ }
+ },
+
+ // Simply return the entered string if the user hasn't specified a smarter handler.
+ mockHandler: function(inputString) {
+ return function() {
+ this._process = function() { return inputString; };
+ };
+ }
+};
+
+var MongoHandler = function() {
+ this._currentCommand = "";
+ this._rawCommand = "";
+ this._commandStack = 0;
+ this._tutorialPtr = 0;
+ this._tutorialMax = 10;
+
+ this._mongo = {};
+ this._mongo.test = [];
+ this.collections = [];
+};
+
+MongoHandler.prototype = {
+
+ _process: function(inputString, errorCheck) {
+ this._rawCommand += ' ' + inputString;
+
+// try {
+ inputString += ' '; // fixes certain bugs with the tokenizer.
+ var tokens = inputString.tokens();
+ var mongoFunc = this._getCommand(tokens);
+ if(this._commandStack === 0 && inputString.match(/^\s*$/)) {
+ return {stack: 0, result: ''};
+ }
+ else if(this._commandStack === 0 && mongoFunc) {
+ this._resetCurrentCommand();
+ return {stack: 0, result: mongoFunc.apply(this, [tokens])};
+ }
+ else {
+ return this._evaluator(tokens);
+ }
+// }
+
+// catch(err) {
+// this._resetCurrentCommand();
+// return {stack: 0, result: "JS Error: " + err};
+// }
+ },
+
+ // Calls eval on the input string when ready.
+ _evaluator: function(tokens) {
+ this._currentCommand += " " + this._massageTokens(tokens);
+ if(this._shouldEvaluateCommand(tokens)) {
+ db = "scott";
+ print = this.print;
+
+ // So this eval statement is the heart of the REPL.
+ var result = eval(this._currentCommand.trim());
+ if(result === undefined) {
+ throw('result is undefined');
+ } else {
+ result = $htmlFormat(result);
+ }
+ this._resetCurrentCommand();
+ console.log(result);
+ return {stack: this._commandStack, result: result};
+ }
+
+ else {
+ return {stack: this._commandStack, result: ""};
+ }
+ },
+
+ _resetCurrentCommand: function() {
+ this._currentCommand = '';
+ this._rawCommand = '';
+ },
+
+ // Evaluate only when we've exited any blocks.
+ _shouldEvaluateCommand: function(tokens) {
+ for(var i=0; i < tokens.length; i++) {
+ var token = tokens[i];
+ if(token.type == 'operator') {
+ if(token.value == '(' || token.value == '{') {
+ this._commandStack += 1;
+ }
+ else if(token.value == ')' || token.value == '}') {
+ this._commandStack -= 1;
+ }
+ }
+ }
+
+ if(this._commandStack === 0) {
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+
+ _massageTokens: function(tokens) {
+ for(var i=0; i < tokens.length; i++) {
+ if(tokens[i].type == 'name') {
+ if(tokens[i].value == 'var') {
+ tokens[i].value = '';
+ }
+ }
+ }
+ return this._collectTokens(tokens);
+ },
+
+ // Collects tokens into a string, placing spaces between variables.
+ // This methods is called after we scope the vars.
+ _collectTokens: function(tokens) {
+ var result = "";
+ for(var i=0; i < tokens.length; i++) {
+ if(tokens[i].type == "name" && tokens[i+1] && tokens[i+1].type == 'name') {
+ result += tokens[i].value + ' ';
+ }
+ else if (tokens[i].type == 'string') {
+ result += "'" + tokens[i].value + "'";
+ }
+ else {
+ result += tokens[i].value;
+ }
+ }
+ return result;
+ },
+
+ // print output to the screen, e.g., in a loop
+ // TODO: remove dependency here
+ print: function() {
+ $('.readLine.active').parent().append('<p>' + arguments[0] + '</p>');
+ return "";
+ },
+
+ /* MongoDB */
+ /* ________________________________________ */
+
+ // help command
+ _help: function() {
+ return PTAG('HELP');
+
+ },
+
+ _getCommand: function(tokens) {
+ if(tokens[0] && MongoKeywords.include((tokens[0].value + '').toLowerCase())) {
+ switch(tokens[0].value.toLowerCase()) {
+ case 'help':
+ return this._help;
+ }
+ }
+ }
+};
+
+$htmlFormat = function(obj) {
+ return tojson(obj, ' ', ' ', true);
+}
+
+$(document).ready(function() {
+ var mongo = new MongoHandler();
+ var terminal = new ReadLine({htmlForInput: DefaultInputHtml,
+ handler: mongo._process,
+ scoper: mongo});
+});
diff --git a/planetstack/core/dashboard/shell/shell.html b/planetstack/core/dashboard/shell/shell.html
new file mode 100644
index 0000000..416bbd5
--- /dev/null
+++ b/planetstack/core/dashboard/shell/shell.html
@@ -0,0 +1,30 @@
+ <div id="terminal">
+ <p class="response">OpenCloud Shell</p>
+ <br />
+ <p>type "help" for help</p>
+ <p>type "tutorial" to start the tutorial</p>
+
+ </div>
+ <link rel="stylesheet" type="text/css" href="{% static 'opencloud_shell.css' %}" media="all">
+ <script src="{% static 'opencloud_shell.js' %}"></script>
+ <script src="{% static 'object_id.js' %}"></script>
+ <script src="{% static 'constants.js' %}"></script>
+ <script src="{% static 'utils.js' %}"></script>
+ <script src="{% static 'shell_utils.js' %}"></script>
+ <script src="{% static 'tokens.js' %}"></script>
+
+
+ <!-- script type="text/javascript" src="js/jquery-1.3.2.min.js"></script-->
+
+ <!-- script type="text/javascript" src="js/mongo.js"></script -->
+ <!-- script type="text/javascript" src="js/object_id.js"></script -->
+ <!-- script type="text/javascript" src="js/lib/collection.js"></script -->
+
+ <!-- script type="text/javascript" src="js/constants.js"></script -->
+ <!-- script type="text/javascript" src="js/connection.js"></script -->
+
+ <!-- script type="text/javascript" src="js/utils.js"></script -->
+ <!-- script type="text/javascript" src="js/shell_utils.js"></script-->
+ <!-- script type="text/javascript" src="js/tokens.js"></script -->
+
+
diff --git a/planetstack/core/dashboard/shell/shell_utils.js b/planetstack/core/dashboard/shell/shell_utils.js
new file mode 100644
index 0000000..79b9565
--- /dev/null
+++ b/planetstack/core/dashboard/shell/shell_utils.js
@@ -0,0 +1,545 @@
+DB = function() {
+}
+
+print = function(msg) {
+ //console.log(msg);
+}
+
+
+friendlyEqual = function( a , b ){
+ if ( a == b )
+ return true;
+
+ if ( tojson( a ) == tojson( b ) )
+ return true;
+
+ return false;
+}
+
+
+doassert = function( msg ){
+ print( "assert: " + msg );
+ throw msg;
+}
+
+assert = function( b , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+
+ if ( b )
+ return;
+
+ doassert( "assert failed : " + msg );
+}
+
+assert.eq = function( a , b , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+
+ if ( a == b )
+ return;
+
+ if ( ( a != null && b != null ) && friendlyEqual( a , b ) )
+ return;
+
+ doassert( "[" + tojson( a ) + "] != [" + tojson( b ) + "] are not equal : " + msg );
+}
+
+assert.neq = function( a , b , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+ if ( a != b )
+ return;
+
+ doassert( "[" + a + "] != [" + b + "] are equal : " + msg );
+}
+
+assert.soon = function( f, msg, timeout, interval ) {
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+
+ var start = new Date();
+ timeout = timeout || 30000;
+ interval = interval || 200;
+ var last;
+ while( 1 ) {
+
+ if ( typeof( f ) == "string" ){
+ if ( eval( f ) )
+ return;
+ }
+ else {
+ if ( f() )
+ return;
+ }
+
+ if ( ( new Date() ).getTime() - start.getTime() > timeout )
+ doassert( "assert.soon failed: " + f + ", msg:" + msg );
+ sleep( interval );
+ }
+}
+
+assert.throws = function( func , params , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+ try {
+ func.apply( null , params );
+ }
+ catch ( e ){
+ return e;
+ }
+
+ doassert( "did not throw exception: " + msg );
+}
+
+assert.commandWorked = function( res , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+
+ if ( res.ok == 1 )
+ return;
+
+ doassert( "command failed: " + tojson( res ) + " : " + msg );
+}
+
+assert.commandFailed = function( res , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+
+ if ( res.ok == 0 )
+ return;
+
+ doassert( "command worked when it should have failed: " + tojson( res ) + " : " + msg );
+}
+
+assert.isnull = function( what , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+
+ if ( what == null )
+ return;
+
+ doassert( "supposed to null (" + ( msg || "" ) + ") was: " + tojson( what ) );
+}
+
+assert.lt = function( a , b , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+
+ if ( a < b )
+ return;
+ doassert( a + " is not less than " + b + " : " + msg );
+}
+
+assert.gt = function( a , b , msg ){
+ if ( assert._debug && msg ) print( "in assert for: " + msg );
+
+ if ( a > b )
+ return;
+ doassert( a + " is not greater than " + b + " : " + msg );
+}
+
+Object.extend = function( dst , src , deep ){
+ for ( var k in src ){
+ var v = src[k];
+ if ( deep && typeof(v) == "object" ){
+ v = Object.extend( typeof ( v.length ) == "number" ? [] : {} , v , true );
+ }
+ dst[k] = v;
+ }
+ return dst;
+}
+
+argumentsToArray = function( a ){
+ var arr = [];
+ for ( var i=0; i<a.length; i++ )
+ arr[i] = a[i];
+ return arr;
+}
+
+isString = function( x ){
+ return typeof( x ) == "string";
+}
+
+isNumber = function(x){
+ return typeof( x ) == "number";
+}
+
+isObject = function( x ){
+ return typeof( x ) == "object";
+}
+
+String.prototype.trim = function() {
+ return this.replace(/^\s+|\s+$/g,"");
+}
+String.prototype.ltrim = function() {
+ return this.replace(/^\s+/,"");
+}
+String.prototype.rtrim = function() {
+ return this.replace(/\s+$/,"");
+}
+
+Date.timeFunc = function( theFunc , numTimes ){
+
+ var start = new Date();
+
+ numTimes = numTimes || 1;
+ for ( var i=0; i<numTimes; i++ ){
+ theFunc.apply( null , argumentsToArray( arguments ).slice( 2 ) );
+ }
+
+ return (new Date()).getTime() - start.getTime();
+}
+
+Date.prototype.tojson = function(){
+ return "\"" + this.toString() + "\"";
+}
+
+RegExp.prototype.tojson = RegExp.prototype.toString;
+
+Array.contains = function( a , x ){
+ for ( var i=0; i<a.length; i++ ){
+ if ( a[i] == x )
+ return true;
+ }
+ return false;
+}
+
+Array.unique = function( a ){
+ var u = [];
+ for ( var i=0; i<a.length; i++){
+ var o = a[i];
+ if ( ! Array.contains( u , o ) ){
+ u.push( o );
+ }
+ }
+ return u;
+}
+
+Array.shuffle = function( arr ){
+ for ( var i=0; i<arr.length-1; i++ ){
+ var pos = i+Math.floor(Math.random()*(arr.length-i));
+ var save = arr[i];
+ arr[i] = arr[pos];
+ arr[pos] = save;
+ }
+ return arr;
+}
+
+
+Array.tojson = function( a , indent , x , html){
+ if (!indent)
+ indent = "";
+ var spacer = "";
+ if(html) {
+ spacer = "<br/>";
+ indent = " "
+ }
+
+ var s = spacer + "[ " + spacer;
+ indent += " ";
+ for ( var i=0; i<a.length; i++){
+ s += indent + tojson( a[i], indent );
+ if ( i < a.length - 1 ){
+ s += "," + spacer;
+ }
+ }
+ if ( a.length == 0 ) {
+ s += indent;
+ }
+
+ indent = indent.substring(1);
+ s += spacer + " "+"]";
+ return s;
+}
+
+Array.fetchRefs = function( arr , coll ){
+ var n = [];
+ for ( var i=0; i<arr.length; i ++){
+ var z = arr[i];
+ if ( coll && coll != z.getCollection() )
+ continue;
+ n.push( z.fetch() );
+ }
+
+ return n;
+}
+
+Array.sum = function( arr ){
+ if ( arr.length == 0 )
+ return null;
+ var s = arr[0];
+ for ( var i=1; i<arr.length; i++ )
+ s += arr[i];
+ return s;
+}
+
+Array.avg = function( arr ){
+ if ( arr.length == 0 )
+ return null;
+ return Array.sum( arr ) / arr.length;
+}
+
+Array.stdDev = function( arr ){
+ var avg = Array.avg( arr );
+ var sum = 0;
+
+ for ( var i=0; i<arr.length; i++ ){
+ sum += Math.pow( arr[i] - avg , 2 );
+ }
+
+ return Math.sqrt( sum / arr.length );
+}
+
+if ( ! ObjectId.prototype )
+ ObjectId.prototype = {}
+
+ObjectId.prototype.toString = function(){
+ return this.str;
+}
+
+ObjectId.prototype.tojson = function(){
+ return "ObjectId(\"" + this.str + "\")";
+}
+
+ObjectId.prototype.isObjectId = true;
+
+tojson = function( x, indent , nolint , html){
+ if ( x == null )
+ return "null";
+
+ if ( x == undefined )
+ return "undefined";
+
+ if (!indent)
+ indent = "";
+
+ switch ( typeof x ){
+
+ case "string": {
+ var s = "\"";
+ for ( var i=0; i<x.length; i++ ){
+ if ( x[i] == '"' ){
+ s += "\\\"";
+ }
+ else
+ s += x[i];
+ }
+ return s + "\"";
+ }
+
+ case "number":
+ case "boolean":
+ return "" + x;
+
+ case "object":{
+ var s = tojsonObject( x, indent , nolint , html);
+ if ( ( nolint == null || nolint == true ) && s.length < 80 && ( indent == null || indent.length == 0 ) ){
+ s = s.replace( /[\s\r\n ]+/gm , " " );
+ }
+ return s;
+ }
+
+ case "function":
+ return x.toString();
+
+
+ default:
+ throw "tojson can't handle type " + ( typeof x );
+ }
+
+}
+
+tojsonObject = function( x, indent , nolint , html){
+ if(html) {
+ var lineEnding = "<br/>";
+ var tabSpace = " ";
+ }
+ else {
+ var lineEnding = nolint ? " " : "\n";
+ var tabSpace = nolint ? "" : "\t";
+ }
+
+ assert.eq( ( typeof x ) , "object" , "tojsonObject needs object, not [" + ( typeof x ) + "]" );
+
+ if (!indent)
+ indent = "";
+
+ if ( typeof( x.tojson ) == "function" && x.tojson != tojson ) {
+ return x.tojson(indent,nolint,html);
+ }
+
+ if ( typeof( x.constructor.tojson ) == "function" && x.constructor.tojson != tojson ) {
+ return x.constructor.tojson( x, indent , nolint, html );
+ }
+
+ if ( x.toString() == "[object MaxKey]" )
+ return "{ $maxKey : 1 }";
+ if ( x.toString() == "[object MinKey]" )
+ return "{ $minKey : 1 }";
+
+ var s = "{" + lineEnding;
+
+ // push one level of indent
+ indent += tabSpace;
+
+ var total = 0;
+ for ( var k in x ) total++;
+ if ( total == 0 ) {
+ s += indent + lineEnding;
+ }
+
+ var keys = x;
+ if ( typeof( x._simpleKeys ) == "function" )
+ keys = x._simpleKeys();
+ var num = 1;
+ for ( var k in keys ){
+
+ var val = x[k];
+
+ s += indent + "\"" + k + "\" : " + tojson( val, indent , nolint );
+ if (num != total) {
+ s += ",";
+ num++;
+ }
+ s += lineEnding;
+ }
+
+ // pop one level of indent
+ indent = indent.substring(1);
+ return s + indent + "}";
+}
+
+shellPrint = function( x ){
+ it = x;
+ if ( x != undefined )
+ shellPrintHelper( x );
+}
+
+printjson = function(x){
+ print( tojson( x ) );
+}
+
+shellPrintHelper = function( x ){
+
+ if ( typeof( x ) == "undefined" ){
+
+ return;
+ }
+
+ if ( x == null ){
+ print( "null" );
+ return;
+ }
+
+ if ( typeof x != "object" )
+ return print( x );
+
+ var p = x.shellPrint;
+ if ( typeof p == "function" )
+ return x.shellPrint();
+
+ var p = x.tojson;
+ if ( typeof p == "function" )
+ print( x.tojson() );
+ else
+ print( tojson( x ) );
+}
+
+shellHelper = function( command , rest , shouldPrint ){
+ command = command.trim();
+ var args = rest.trim().replace(/;$/,"").split( "\s+" );
+
+ if ( ! shellHelper[command] )
+ throw "no command [" + command + "]";
+
+ var res = shellHelper[command].apply( null , args );
+ if ( shouldPrint ){
+ shellPrintHelper( res );
+ }
+ return res;
+}
+
+help = shellHelper.help = function(){
+ print( "HELP" );
+ print( "\t" + "show dbs show database names");
+ print( "\t" + "show collections show collections in current database");
+ print( "\t" + "show users show users in current database");
+ print( "\t" + "show profile show most recent system.profile entries with time >= 1ms");
+ print( "\t" + "use <db name> set curent database to <db name>" );
+ print( "\t" + "db.help() help on DB methods");
+ print( "\t" + "db.foo.help() help on collection methods");
+ print( "\t" + "db.foo.find() list objects in collection foo" );
+ print( "\t" + "db.foo.find( { a : 1 } ) list objects in foo where a == 1" );
+ print( "\t" + "it result of the last line evaluated; use to further iterate");
+}
+
+if ( typeof( Map ) == "undefined" ){
+ Map = function(){
+ this._data = {};
+ }
+}
+
+Map.hash = function( val ){
+ if ( ! val )
+ return val;
+
+ switch ( typeof( val ) ){
+ case 'string':
+ case 'number':
+ case 'date':
+ return val.toString();
+ case 'object':
+ case 'array':
+ var s = "";
+ for ( var k in val ){
+ s += k + val[k];
+ }
+ return s;
+ }
+
+ throw "can't hash : " + typeof( val );
+}
+
+Map.prototype.put = function( key , value ){
+ var o = this._get( key );
+ var old = o.value;
+ o.value = value;
+ return old;
+}
+
+Map.prototype.get = function( key ){
+ return this._get( key ).value;
+}
+
+Map.prototype._get = function( key ){
+ var h = Map.hash( key );
+ var a = this._data[h];
+ if ( ! a ){
+ a = [];
+ this._data[h] = a;
+ }
+
+ for ( var i=0; i<a.length; i++ ){
+ if ( friendlyEqual( key , a[i].key ) ){
+ return a[i];
+ }
+ }
+ var o = { key : key , value : null };
+ a.push( o );
+ return o;
+}
+
+Map.prototype.values = function(){
+ var all = [];
+ for ( var k in this._data ){
+ this._data[k].forEach( function(z){ all.push( z.value ); } );
+ }
+ return all;
+}
+
+if ( typeof( gc ) == "undefined" ){
+ gc = function(){
+ }
+}
+
+
+Math.sigFig = function( x , N ){
+ if ( ! N ){
+ N = 3;
+ }
+ var p = Math.pow( 10, N - Math.ceil( Math.log( Math.abs(x) ) / Math.log( 10 )) );
+ return Math.round(x*p)/p;
+}
+
diff --git a/planetstack/core/dashboard/shell/tokens.js b/planetstack/core/dashboard/shell/tokens.js
new file mode 100644
index 0000000..49c246e
--- /dev/null
+++ b/planetstack/core/dashboard/shell/tokens.js
@@ -0,0 +1,268 @@
+// tokens.js
+// 2009-05-17
+
+// (c) 2006 Douglas Crockford
+
+// Produce an array of simple token objects from a string.
+// A simple token object contains these members:
+// type: 'name', 'string', 'number', 'operator'
+// value: string or number value of the token
+// from: index of first character of the token
+// to: index of the last character + 1
+
+// Comments of the // type are ignored.
+
+// Operators are by default single characters. Multicharacter
+// operators can be made by supplying a string of prefix and
+// suffix characters.
+// characters. For example,
+// '<>+-&', '=>&:'
+// will match any of these:
+// <= >> >>> <> >= +: -: &: &&: &&
+
+
+
+String.prototype.tokens = function (prefix, suffix) {
+ var c; // The current character.
+ var from; // The index of the start of the token.
+ var i = 0; // The index of the current character.
+ var length = this.length;
+ var n; // The number value.
+ var q; // The quote character.
+ var str; // The string value.
+
+ var result = []; // An array to hold the results.
+
+ var make = function (type, value) {
+
+// Make a token object.
+
+ return {
+ type: type,
+ value: value,
+ from: from,
+ to: i
+ };
+ };
+
+// Begin tokenization. If the source string is empty, return nothing.
+
+ if (!this) {
+ return;
+ }
+
+// If prefix and suffix strings are not provided, supply defaults.
+
+ if (typeof prefix !== 'string') {
+ prefix = '<>+-&';
+ }
+ if (typeof suffix !== 'string') {
+ suffix = '=>&:';
+ }
+
+
+// Loop through this text, one character at a time.
+
+ c = this.charAt(i);
+ while (c) {
+ from = i;
+
+// Ignore whitespace.
+
+ if (c <= ' ') {
+ i += 1;
+ c = this.charAt(i);
+
+// name.
+
+ } else if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
+ str = c;
+ i += 1;
+ for (;;) {
+ c = this.charAt(i);
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') || c === '_') {
+ str += c;
+ i += 1;
+ } else {
+ break;
+ }
+ }
+ result.push(make('name', str));
+
+// number.
+
+// A number cannot start with a decimal point. It must start with a digit,
+// possibly '0'.
+
+ } else if (c >= '0' && c <= '9') {
+ str = c;
+ i += 1;
+
+// Look for more digits.
+
+ for (;;) {
+ c = this.charAt(i);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ i += 1;
+ str += c;
+ }
+
+// Look for a decimal fraction part.
+
+ if (c === '.') {
+ i += 1;
+ str += c;
+ for (;;) {
+ c = this.charAt(i);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ i += 1;
+ str += c;
+ }
+ }
+
+// Look for an exponent part.
+
+ if (c === 'e' || c === 'E') {
+ i += 1;
+ str += c;
+ c = this.charAt(i);
+ if (c === '-' || c === '+') {
+ i += 1;
+ str += c;
+ c = this.charAt(i);
+ }
+ if (c < '0' || c > '9') {
+ make('number', str).error("Bad exponent");
+ }
+ do {
+ i += 1;
+ str += c;
+ c = this.charAt(i);
+ } while (c >= '0' && c <= '9');
+ }
+
+// Make sure the next character is not a letter.
+
+ if (c >= 'a' && c <= 'z') {
+ str += c;
+ i += 1;
+ make('number', str).error("Bad number");
+ }
+
+// Convert the string value to a number. If it is finite, then it is a good
+// token.
+
+ n = +str;
+ if (isFinite(n)) {
+ result.push(make('number', n));
+ } else {
+ make('number', str).error("Bad number");
+ }
+
+// string
+
+ } else if (c === '\'' || c === '"') {
+ str = '';
+ q = c;
+ i += 1;
+ for (;;) {
+ c = this.charAt(i);
+ if (c < ' ') {
+ make('string', str).error(c === '\n' || c === '\r' || c === '' ?
+ "Unterminated string." :
+ "Control character in string.", make('', str));
+ }
+
+// Look for the closing quote.
+
+ if (c === q) {
+ break;
+ }
+
+// Look for escapement.
+
+ if (c === '\\') {
+ i += 1;
+ if (i >= length) {
+ make('string', str).error("Unterminated string");
+ }
+ c = this.charAt(i);
+ switch (c) {
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'u':
+ if (i >= length) {
+ make('string', str).error("Unterminated string");
+ }
+ c = parseInt(this.substr(i + 1, 4), 16);
+ if (!isFinite(c) || c < 0) {
+ make('string', str).error("Unterminated string");
+ }
+ c = String.fromCharCode(c);
+ i += 4;
+ break;
+ }
+ }
+ str += c;
+ i += 1;
+ }
+ i += 1;
+ result.push(make('string', str));
+ c = this.charAt(i);
+
+// comment.
+
+ } else if (c === '/' && this.charAt(i + 1) === '/') {
+ i += 1;
+ for (;;) {
+ c = this.charAt(i);
+ if (c === '\n' || c === '\r' || c === '') {
+ break;
+ }
+ i += 1;
+ }
+
+// combining
+
+ } else if (prefix.indexOf(c) >= 0) {
+ str = c;
+ i += 1;
+ while (i < length) {
+ c = this.charAt(i);
+ if (suffix.indexOf(c) < 0) {
+ break;
+ }
+ str += c;
+ i += 1;
+ }
+ result.push(make('operator', str));
+
+// single-character operator
+
+ } else {
+ i += 1;
+ result.push(make('operator', c));
+ c = this.charAt(i);
+ }
+ }
+ return result;
+};
+
diff --git a/planetstack/core/dashboard/shell/up.sh b/planetstack/core/dashboard/shell/up.sh
new file mode 100755
index 0000000..44af364
--- /dev/null
+++ b/planetstack/core/dashboard/shell/up.sh
@@ -0,0 +1,2 @@
+scp shell.html princeton_planetstack@node49.princeton.vicci.org:/opt/planetstack/templates/admin/dashboard/
+scp opencloud_shell.css object_id.js shell_utils.js utils.js tokens.js constants.js opencloud_shell.js princeton_planetstack@node49.princeton.vicci.org:/opt/planetstack/core/static/
diff --git a/planetstack/core/dashboard/shell/utils.js b/planetstack/core/dashboard/shell/utils.js
new file mode 100644
index 0000000..93aff08
--- /dev/null
+++ b/planetstack/core/dashboard/shell/utils.js
@@ -0,0 +1,70 @@
+// Try Mongo
+//
+// Copyright (c) 2009 Kyle Banker
+// Licensed under the MIT licence.
+// http://www.opensource.org/licenses/mit-license.php
+
+Array.prototype.include = function(value) {
+ for(var i=0; i < this.length; i++) {
+ if(this[i] == value) {
+ return this[i];
+ }
+ }
+ return false;
+};
+
+Array.prototype.empty = function() {
+ return (this.length == 0);
+};
+
+Function.prototype.bind = function() {
+ var __method = this, object = arguments[0], args = [];
+
+ for(i = 1; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+
+ return function() {
+ return __method.apply(object, args);
+ };
+};
+
+String.prototype.trim = function() {
+ return this.replace(/^\s+|\s+$/g,"");
+};
+
+// Prints javascript types as readable strings.
+Inspect = function(obj) {
+ if(typeof(obj) != 'object') {
+ return obj;
+ }
+
+ else if (obj instanceof Array) {
+ var objRep = [];
+ for(var prop in obj) {
+ if(obj.hasOwnProperty(prop)) {
+ objRep.push(obj[prop]);
+ }
+ }
+ return '[' + objRep.join(', ') + ']';
+ }
+
+ else {
+ var objRep = [];
+ for(var prop in obj) {
+ if(obj.hasOwnProperty(prop)) {
+ objRep.push(prop + ': ' + ((typeof(obj[prop]) == 'object') ? Inspect(obj[prop]) : obj[prop]));
+ }
+ }
+ return '{' + objRep.join(', ') + '}';
+ }
+};
+
+// Prints an array of javascript objects.
+CollectionInspect = function(coll) {
+ var str = '';
+ for(var i=0; i<coll.length; i++) {
+ str += Inspect(coll[i]) + '<br />';
+ }
+ return str;
+};