blob: 62a8a0a9c0afc68590b893478c82eca8ad748fee [file] [log] [blame]
Scott Baker999edc62014-06-18 15:40:56 -07001// TryMongo
2//
3// Copyright (c) 2009 Kyle Banker
4// Licensed under the MIT Licence.
5// http://www.opensource.org/licenses/mit-license.php
6
7// Readline class to handle line input.
8var ReadLine = function(options) {
9 this.options = options || {};
10 this.htmlForInput = this.options.htmlForInput;
11 this.inputHandler = this.options.handler || this.mockHandler;
12 this.scoper = this.options.scoper;
13 this.terminal = $(this.options.terminalId || "#terminal");
14 this.lineClass = this.options.lineClass || '.readLine';
15 this.history = [];
16 this.historyPtr = 0;
17
18 this.initialize();
19};
20
21ReadLine.prototype = {
22
23 initialize: function() {
24 this.addInputLine();
25 },
26
27 // Enter a new input line with proper behavior.
28 addInputLine: function(stackLevel) {
29 stackLevel = stackLevel || 0;
30 this.terminal.append(this.htmlForInput(stackLevel));
31 var ctx = this;
32 ctx.activeLine = $(this.lineClass + '.active');
33
34 // Bind key events for entering and navigting history.
35 ctx.activeLine.bind("keydown", function(ev) {
36 switch (ev.keyCode) {
37 case EnterKeyCode:
38 ctx.processInput(this.value);
39 break;
40 case UpArrowKeyCode:
41 ctx.getCommand('previous');
42 break;
43 case DownArrowKeyCode:
44 ctx.getCommand('next');
45 break;
46 }
47 });
48
49 this.activeLine.focus();
50 },
51
52 // Returns the 'next' or 'previous' command in this history.
53 getCommand: function(direction) {
54 if(this.history.length === 0) {
55 return;
56 }
57 this.adjustHistoryPointer(direction);
58 this.activeLine[0].value = this.history[this.historyPtr];
59 $(this.activeLine[0]).focus();
60 //this.activeLine[0].value = this.activeLine[0].value;
61 },
62
63 // Moves the history pointer to the 'next' or 'previous' position.
64 adjustHistoryPointer: function(direction) {
65 if(direction == 'previous') {
66 if(this.historyPtr - 1 >= 0) {
67 this.historyPtr -= 1;
68 }
69 }
70 else {
71 if(this.historyPtr + 1 < this.history.length) {
72 this.historyPtr += 1;
73 }
74 }
75 },
76
77 // Return the handler's response.
78 processInput: function(value) {
79 var response = this.inputHandler.apply(this.scoper, [value]);
80 this.insertResponse(response.result);
81
82 // Save to the command history...
83 if((lineValue = value.trim()) !== "") {
84 this.history.push(lineValue);
85 this.historyPtr = this.history.length;
86 }
87
88 // deactivate the line...
89 this.activeLine.value = "";
90 this.activeLine.attr({disabled: true});
91 this.activeLine.removeClass('active');
92
93 // and add add a new command line.
94 this.addInputLine(response.stack);
95 },
96
97 insertResponse: function(response) {
Scott Baker2698e6a2014-06-20 00:09:00 -070098 if((response.length < 1) || (response=='"donotprintme"') || (response=='donotprintme')) {
Scott Baker999edc62014-06-18 15:40:56 -070099 this.activeLine.parent().append("<p class='response'></p>");
100 }
101 else {
102 this.activeLine.parent().append("<p class='response'>" + response + "</p>");
103 }
104 },
105
106 // Simply return the entered string if the user hasn't specified a smarter handler.
107 mockHandler: function(inputString) {
108 return function() {
109 this._process = function() { return inputString; };
110 };
111 }
112};
113
114var MongoHandler = function() {
115 this._currentCommand = "";
116 this._rawCommand = "";
117 this._commandStack = 0;
118 this._tutorialPtr = 0;
Scott Baker2c3d5a82014-06-19 16:51:16 -0700119 this._tutorialMax = 4;
Scott Baker999edc62014-06-18 15:40:56 -0700120
121 this._mongo = {};
122 this._mongo.test = [];
123 this.collections = [];
124};
125
126MongoHandler.prototype = {
127
128 _process: function(inputString, errorCheck) {
129 this._rawCommand += ' ' + inputString;
130
Scott Bakerbf1610d2014-06-18 18:22:03 -0700131 try {
Scott Baker999edc62014-06-18 15:40:56 -0700132 inputString += ' '; // fixes certain bugs with the tokenizer.
133 var tokens = inputString.tokens();
134 var mongoFunc = this._getCommand(tokens);
135 if(this._commandStack === 0 && inputString.match(/^\s*$/)) {
136 return {stack: 0, result: ''};
137 }
138 else if(this._commandStack === 0 && mongoFunc) {
139 this._resetCurrentCommand();
140 return {stack: 0, result: mongoFunc.apply(this, [tokens])};
141 }
142 else {
143 return this._evaluator(tokens);
144 }
Scott Bakerbf1610d2014-06-18 18:22:03 -0700145 }
Scott Baker999edc62014-06-18 15:40:56 -0700146
Scott Bakerbf1610d2014-06-18 18:22:03 -0700147 catch(err) {
148 this._resetCurrentCommand();
149 console.trace();
150 return {stack: 0, result: "JS Error: " + err};
151 }
Scott Baker999edc62014-06-18 15:40:56 -0700152 },
153
154 // Calls eval on the input string when ready.
155 _evaluator: function(tokens) {
Scott Bakerbf1610d2014-06-18 18:22:03 -0700156 isAssignment = tokens.length>=2 && tokens[0].type=="name" && tokens[1].type=="operator" && tokens[1].value=="=";
157
Scott Baker999edc62014-06-18 15:40:56 -0700158 this._currentCommand += " " + this._massageTokens(tokens);
159 if(this._shouldEvaluateCommand(tokens)) {
Scott Baker930d86e2014-06-23 15:55:18 -0700160 xos = new OpenCloud();
Scott Baker999edc62014-06-18 15:40:56 -0700161 print = this.print;
162
163 // So this eval statement is the heart of the REPL.
164 var result = eval(this._currentCommand.trim());
165 if(result === undefined) {
166 throw('result is undefined');
Scott Baker930d86e2014-06-23 15:55:18 -0700167 } else if (typeof(result) === 'function') {
168 throw('result is a function. did you mean to call it?');
Scott Baker999edc62014-06-18 15:40:56 -0700169 } else {
170 result = $htmlFormat(result);
171 }
172 this._resetCurrentCommand();
Scott Bakerbf1610d2014-06-18 18:22:03 -0700173 if (isAssignment) {
174 return {stack: this._commandStack, result: ""};
175 } else {
176 return {stack: this._commandStack, result: result};
177 }
Scott Baker999edc62014-06-18 15:40:56 -0700178 }
179
180 else {
181 return {stack: this._commandStack, result: ""};
182 }
183 },
184
185 _resetCurrentCommand: function() {
186 this._currentCommand = '';
187 this._rawCommand = '';
188 },
189
190 // Evaluate only when we've exited any blocks.
191 _shouldEvaluateCommand: function(tokens) {
192 for(var i=0; i < tokens.length; i++) {
193 var token = tokens[i];
194 if(token.type == 'operator') {
195 if(token.value == '(' || token.value == '{') {
196 this._commandStack += 1;
197 }
198 else if(token.value == ')' || token.value == '}') {
199 this._commandStack -= 1;
200 }
201 }
202 }
203
204 if(this._commandStack === 0) {
205 return true;
206 }
207 else {
208 return false;
209 }
210 },
211
212 _massageTokens: function(tokens) {
213 for(var i=0; i < tokens.length; i++) {
214 if(tokens[i].type == 'name') {
215 if(tokens[i].value == 'var') {
216 tokens[i].value = '';
217 }
218 }
219 }
220 return this._collectTokens(tokens);
221 },
222
223 // Collects tokens into a string, placing spaces between variables.
224 // This methods is called after we scope the vars.
225 _collectTokens: function(tokens) {
226 var result = "";
227 for(var i=0; i < tokens.length; i++) {
228 if(tokens[i].type == "name" && tokens[i+1] && tokens[i+1].type == 'name') {
229 result += tokens[i].value + ' ';
230 }
231 else if (tokens[i].type == 'string') {
232 result += "'" + tokens[i].value + "'";
233 }
234 else {
235 result += tokens[i].value;
236 }
237 }
238 return result;
239 },
240
241 // print output to the screen, e.g., in a loop
242 // TODO: remove dependency here
243 print: function() {
Scott Bakerbf1610d2014-06-18 18:22:03 -0700244 $('.readLine.active').parent().append('<p>' + JSON.stringify(arguments[0]) + '</p>');
245 return "donotprintme";
Scott Baker999edc62014-06-18 15:40:56 -0700246 },
247
248 /* MongoDB */
249 /* ________________________________________ */
250
251 // help command
252 _help: function() {
Scott Bakerbf1610d2014-06-18 18:22:03 -0700253 return PTAG('HELP') +
Scott Baker930d86e2014-06-23 15:55:18 -0700254 PTAG('xos list xos API object types') +
255 PTAG('xos.slices list methods to can call on slices') +
256 PTAG('xos.slices.all() get all slices') +
257 PTAG('xos.slices.filter({key: "value"}) filter using dictionary') +
258 PTAG('xos.slices.get({key: "value"}) get using dictionary')
Scott Baker999edc62014-06-18 15:40:56 -0700259
260 },
261
Scott Bakerbf1610d2014-06-18 18:22:03 -0700262 _tutorial: function() {
Scott Baker0e9c8892014-06-19 10:54:33 -0700263 this._tutorialPtr = 0;
Scott Baker930d86e2014-06-23 15:55:18 -0700264 return PTAG("This is a self-guided tutorial on the xos shell.") +
Scott Baker0e9c8892014-06-19 10:54:33 -0700265 PTAG("The tutorial is simple, more or less a few basic commands to try.") +
266 PTAG("To go directly to any part tutorial, enter one of the commands t0, t1, t2...t10") +
267 PTAG("Otherwise, use 'next' and 'back'. Start by typing 'next' and pressing enter.");
Scott Bakerbf1610d2014-06-18 18:22:03 -0700268 },
269
270 // go to the next step in the tutorial.
Scott Baker0e9c8892014-06-19 10:54:33 -0700271 _next: function() {
272 if(this._tutorialPtr < this._tutorialMax) {
273 return this['_t' + (this._tutorialPtr + 1)]();
274 }
275 else {
276 return "You've reached the end of the tutorial. To go to the beginning, type 'tutorial'";
277 }
278 },
279
280 // go to the previous step in the tutorial.
281 _back: function() {
282 if(this._tutorialPtr > 1) {
283 return this['_t' + (this._tutorialPtr - 1)]();
284 }
285 else {
286 return this._tutorial();
287 }
Scott Bakerbf1610d2014-06-18 18:22:03 -0700288 },
289
290 _t1: function() {
Scott Baker0e9c8892014-06-19 10:54:33 -0700291 this._tutorialPtr = 1;
292 return PTAG('1. JavaScript Shell') +
293 PTAG('The first thing to notice is that the MongoDB shell is JavaScript-based.') +
294 PTAG('So you can do things like:') +
295 PTAG(' a = 5; ') +
296 PTAG(' a * 10; ') +
297 PTAG(' print(a); ') +
298 PTAG(" for(i=0; i<10; i++) { print('hello'); }; ") +
299 PTAG("Try a few JS commands; when you're ready to move on, enter 'next'");
300
Scott Bakerbf1610d2014-06-18 18:22:03 -0700301 },
302
303 _t2: function() {
Scott Baker0e9c8892014-06-19 10:54:33 -0700304 this._tutorialPtr = 2;
Scott Baker2c3d5a82014-06-19 16:51:16 -0700305 return PTAG('2. List some objects') +
306 PTAG('Try these:') +
Scott Baker930d86e2014-06-23 15:55:18 -0700307 PTAG(' xos.slices.all();') +
308 PTAG(' xos.slivers.all();') +
309 PTAG(' xos.sites.all();') +
310 PTAG(' xos.nodes.all();');
Scott Baker2c3d5a82014-06-19 16:51:16 -0700311
312 },
313
314 _t3: function() {
315 this._tutorialPtr = 3;
316 return PTAG('3. Filter some objects') +
317 PTAG('Try these:') +
Scott Baker930d86e2014-06-23 15:55:18 -0700318 PTAG(' xos.slices.get({name: "HyperCache"});');
319 PTAG(' xos.nodes.filter({site_id: xos.sites.get({name: "Princeton"})["id"]});');
Scott Baker2c3d5a82014-06-19 16:51:16 -0700320
321 },
322
323 _t4: function() {
324 this._tutorialPtr = 4;
Scott Baker930d86e2014-06-23 15:55:18 -0700325 return PTAG('4. Available xos objects and methods') +
Scott Baker2c3d5a82014-06-19 16:51:16 -0700326 PTAG('Try these:') +
Scott Baker930d86e2014-06-23 15:55:18 -0700327 PTAG(' xos;') +
328 PTAG(' xos.nodes;');
Scott Baker0e9c8892014-06-19 10:54:33 -0700329
Scott Bakerbf1610d2014-06-18 18:22:03 -0700330 },
331
Scott Baker999edc62014-06-18 15:40:56 -0700332 _getCommand: function(tokens) {
Scott Baker38e2cea2014-06-18 19:02:37 -0700333 if(tokens[0] && ArrayInclude(MongoKeywords,(tokens[0].value + '').toLowerCase())) {
Scott Baker999edc62014-06-18 15:40:56 -0700334 switch(tokens[0].value.toLowerCase()) {
335 case 'help':
336 return this._help;
Scott Bakerbf1610d2014-06-18 18:22:03 -0700337
338 case 'tutorial':
Scott Baker0e9c8892014-06-19 10:54:33 -0700339 return this._tutorial;
340 case 'next':
341 return this._next;
342 case 'back':
343 return this._back;
344 case 't0':
345 return this._tutorial;
346 case 't1':
Scott Bakerbf1610d2014-06-18 18:22:03 -0700347 return this._t1;
348 case 't2':
349 return this._t2;
Scott Baker2c3d5a82014-06-19 16:51:16 -0700350 case 't3':
351 return this._t3;
352 case 't4':
353 return this._t4;
Scott Baker999edc62014-06-18 15:40:56 -0700354 }
355 }
356 }
357};
358
Scott Baker1b708782014-06-20 00:05:17 -0700359function replaceAll(find, replace, str) {
360 return str.replace(new RegExp(find, 'g'), replace);
361}
362
363/* stackoverflow: http://stackoverflow.com/questions/4810841/how-can-i-pretty-print-json-using-javascript */
364function syntaxHighlight(json) {
365 if ( json.hasOwnProperty("__str__")) {
366 return syntaxHighlight(json.__str__());
367 }
368 if (typeof json != 'string') {
369 json = JSON.stringify(json, undefined, "\t");
370 }
371 json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
372 return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
373 var cls = 'terminal_number';
374 if (/^"/.test(match)) {
375 if (/:$/.test(match)) {
376 cls = 'terminal_key';
377 } else {
378 cls = 'terminal_string';
379 }
380 } else if (/true|false/.test(match)) {
381 cls = 'terminal_boolean';
382 } else if (/null/.test(match)) {
383 cls = 'terminal_null';
384 }
385 return '<span class="' + cls + '">' + match + '</span>';
386 });
387}
388
Scott Baker999edc62014-06-18 15:40:56 -0700389$htmlFormat = function(obj) {
Scott Baker1b708782014-06-20 00:05:17 -0700390 //JSON.stringify(obj,undefined,2)
391 result=replaceAll("\t","&nbsp;",replaceAll("\n","<br>",syntaxHighlight(obj))); //tojson(obj, ' ', ' ', true);
Scott Bakerbf1610d2014-06-18 18:22:03 -0700392 return result;
Scott Baker999edc62014-06-18 15:40:56 -0700393}
394
Scott Bakerbf1610d2014-06-18 18:22:03 -0700395function startTerminal() {
Scott Baker999edc62014-06-18 15:40:56 -0700396 var mongo = new MongoHandler();
397 var terminal = new ReadLine({htmlForInput: DefaultInputHtml,
398 handler: mongo._process,
399 scoper: mongo});
Scott Bakerbf1610d2014-06-18 18:22:03 -0700400 $("#terminal_help1").show();
401 $("#terminal_help2").show();
402 $("#terminal_wait").hide();
Scott Baker79bb6782014-06-19 14:32:52 -0700403
404 $("#terminal").bind('click', function() { $(".readLine.active").focus(); });
Scott Bakerbf1610d2014-06-18 18:22:03 -0700405};
406
407$(document).ready(function() {
408 updateOpenCloud(onLoaded = startTerminal);
Scott Baker999edc62014-06-18 15:40:56 -0700409});