--- /dev/null
+// 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 < 1) || (response=='"donotprintme"') || (response=='donotprintme')) {
+ 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 = 4;
+
+ 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();
+ console.trace();
+ return {stack: 0, result: "JS Error: " + err};
+ }
+ },
+
+ // Calls eval on the input string when ready.
+ _evaluator: function(tokens) {
+ isAssignment = tokens.length>=2 && tokens[0].type=="name" && tokens[1].type=="operator" && tokens[1].value=="=";
+
+ this._currentCommand += " " + this._massageTokens(tokens);
+ if(this._shouldEvaluateCommand(tokens)) {
+ xos = new OpenCloud();
+ 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 if (typeof(result) === 'function') {
+ throw('result is a function. did you mean to call it?');
+ } else {
+ result = $htmlFormat(result);
+ }
+ this._resetCurrentCommand();
+ if (isAssignment) {
+ return {stack: this._commandStack, result: ""};
+ } else {
+ 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>' + JSON.stringify(arguments[0]) + '</p>');
+ return "donotprintme";
+ },
+
+ /* MongoDB */
+ /* ________________________________________ */
+
+ // help command
+ _help: function() {
+ return PTAG('HELP') +
+ PTAG('xos list xos API object types') +
+ PTAG('xos.slices list methods to can call on slices') +
+ PTAG('xos.slices.all() get all slices') +
+ PTAG('xos.slices.filter({key: "value"}) filter using dictionary') +
+ PTAG('xos.slices.get({key: "value"}) get using dictionary')
+
+ },
+
+ _tutorial: function() {
+ this._tutorialPtr = 0;
+ return PTAG("This is a self-guided tutorial on the xos shell.") +
+ PTAG("The tutorial is simple, more or less a few basic commands to try.") +
+ PTAG("To go directly to any part tutorial, enter one of the commands t0, t1, t2...t10") +
+ PTAG("Otherwise, use 'next' and 'back'. Start by typing 'next' and pressing enter.");
+ },
+
+ // go to the next step in the tutorial.
+ _next: function() {
+ if(this._tutorialPtr < this._tutorialMax) {
+ return this['_t' + (this._tutorialPtr + 1)]();
+ }
+ else {
+ return "You've reached the end of the tutorial. To go to the beginning, type 'tutorial'";
+ }
+ },
+
+ // go to the previous step in the tutorial.
+ _back: function() {
+ if(this._tutorialPtr > 1) {
+ return this['_t' + (this._tutorialPtr - 1)]();
+ }
+ else {
+ return this._tutorial();
+ }
+ },
+
+ _t1: function() {
+ this._tutorialPtr = 1;
+ return PTAG('1. JavaScript Shell') +
+ PTAG('The first thing to notice is that the MongoDB shell is JavaScript-based.') +
+ PTAG('So you can do things like:') +
+ PTAG(' a = 5; ') +
+ PTAG(' a * 10; ') +
+ PTAG(' print(a); ') +
+ PTAG(" for(i=0; i<10; i++) { print('hello'); }; ") +
+ PTAG("Try a few JS commands; when you're ready to move on, enter 'next'");
+
+ },
+
+ _t2: function() {
+ this._tutorialPtr = 2;
+ return PTAG('2. List some objects') +
+ PTAG('Try these:') +
+ PTAG(' xos.slices.all();') +
+ PTAG(' xos.slivers.all();') +
+ PTAG(' xos.sites.all();') +
+ PTAG(' xos.nodes.all();');
+
+ },
+
+ _t3: function() {
+ this._tutorialPtr = 3;
+ return PTAG('3. Filter some objects') +
+ PTAG('Try these:') +
+ PTAG(' xos.slices.get({name: "HyperCache"});');
+ PTAG(' xos.nodes.filter({site_id: xos.sites.get({name: "Princeton"})["id"]});');
+
+ },
+
+ _t4: function() {
+ this._tutorialPtr = 4;
+ return PTAG('4. Available xos objects and methods') +
+ PTAG('Try these:') +
+ PTAG(' xos;') +
+ PTAG(' xos.nodes;');
+
+ },
+
+ _getCommand: function(tokens) {
+ if(tokens[0] && ArrayInclude(MongoKeywords,(tokens[0].value + '').toLowerCase())) {
+ switch(tokens[0].value.toLowerCase()) {
+ case 'help':
+ return this._help;
+
+ case 'tutorial':
+ return this._tutorial;
+ case 'next':
+ return this._next;
+ case 'back':
+ return this._back;
+ case 't0':
+ return this._tutorial;
+ case 't1':
+ return this._t1;
+ case 't2':
+ return this._t2;
+ case 't3':
+ return this._t3;
+ case 't4':
+ return this._t4;
+ }
+ }
+ }
+};
+
+function replaceAll(find, replace, str) {
+ return str.replace(new RegExp(find, 'g'), replace);\r
+}
+
+/* stackoverflow: http://stackoverflow.com/questions/4810841/how-can-i-pretty-print-json-using-javascript */
+function syntaxHighlight(json) {
+ if ( json.hasOwnProperty("__str__")) {
+ return syntaxHighlight(json.__str__());
+ }
+ if (typeof json != 'string') {\r
+ json = JSON.stringify(json, undefined, "\t");\r
+ }\r
+ json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\r
+ return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {\r
+ var cls = 'terminal_number';\r
+ if (/^"/.test(match)) {\r
+ if (/:$/.test(match)) {\r
+ cls = 'terminal_key';\r
+ } else {\r
+ cls = 'terminal_string';\r
+ }\r
+ } else if (/true|false/.test(match)) {\r
+ cls = 'terminal_boolean';\r
+ } else if (/null/.test(match)) {\r
+ cls = 'terminal_null';\r
+ }\r
+ return '<span class="' + cls + '">' + match + '</span>';\r
+ });\r
+}
+
+$htmlFormat = function(obj) {
+ //JSON.stringify(obj,undefined,2)
+ result=replaceAll("\t"," ",replaceAll("\n","<br>",syntaxHighlight(obj))); //tojson(obj, ' ', ' ', true);
+ return result;
+}
+
+function startTerminal() {
+ var mongo = new MongoHandler();
+ var terminal = new ReadLine({htmlForInput: DefaultInputHtml,
+ handler: mongo._process,
+ scoper: mongo});
+ $("#terminal_help1").show();
+ $("#terminal_help2").show();
+ $("#terminal_wait").hide();
+
+ $("#terminal").bind('click', function() { $(".readLine.active").focus(); });
+};
+
+$(document).ready(function() {
+ updateOpenCloud(onLoaded = startTerminal);
+});