blob: 634788a35ef5dfe54338c30eb301f3fc6a6df3be [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) {
98 if(response.length < 3) {
99 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;
119 this._tutorialMax = 10;
120
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
131// try {
132 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 }
145// }
146
147// catch(err) {
148// this._resetCurrentCommand();
149// return {stack: 0, result: "JS Error: " + err};
150// }
151 },
152
153 // Calls eval on the input string when ready.
154 _evaluator: function(tokens) {
155 this._currentCommand += " " + this._massageTokens(tokens);
156 if(this._shouldEvaluateCommand(tokens)) {
157 db = "scott";
158 print = this.print;
159
160 // So this eval statement is the heart of the REPL.
161 var result = eval(this._currentCommand.trim());
162 if(result === undefined) {
163 throw('result is undefined');
164 } else {
165 result = $htmlFormat(result);
166 }
167 this._resetCurrentCommand();
168 console.log(result);
169 return {stack: this._commandStack, result: result};
170 }
171
172 else {
173 return {stack: this._commandStack, result: ""};
174 }
175 },
176
177 _resetCurrentCommand: function() {
178 this._currentCommand = '';
179 this._rawCommand = '';
180 },
181
182 // Evaluate only when we've exited any blocks.
183 _shouldEvaluateCommand: function(tokens) {
184 for(var i=0; i < tokens.length; i++) {
185 var token = tokens[i];
186 if(token.type == 'operator') {
187 if(token.value == '(' || token.value == '{') {
188 this._commandStack += 1;
189 }
190 else if(token.value == ')' || token.value == '}') {
191 this._commandStack -= 1;
192 }
193 }
194 }
195
196 if(this._commandStack === 0) {
197 return true;
198 }
199 else {
200 return false;
201 }
202 },
203
204 _massageTokens: function(tokens) {
205 for(var i=0; i < tokens.length; i++) {
206 if(tokens[i].type == 'name') {
207 if(tokens[i].value == 'var') {
208 tokens[i].value = '';
209 }
210 }
211 }
212 return this._collectTokens(tokens);
213 },
214
215 // Collects tokens into a string, placing spaces between variables.
216 // This methods is called after we scope the vars.
217 _collectTokens: function(tokens) {
218 var result = "";
219 for(var i=0; i < tokens.length; i++) {
220 if(tokens[i].type == "name" && tokens[i+1] && tokens[i+1].type == 'name') {
221 result += tokens[i].value + ' ';
222 }
223 else if (tokens[i].type == 'string') {
224 result += "'" + tokens[i].value + "'";
225 }
226 else {
227 result += tokens[i].value;
228 }
229 }
230 return result;
231 },
232
233 // print output to the screen, e.g., in a loop
234 // TODO: remove dependency here
235 print: function() {
236 $('.readLine.active').parent().append('<p>' + arguments[0] + '</p>');
237 return "";
238 },
239
240 /* MongoDB */
241 /* ________________________________________ */
242
243 // help command
244 _help: function() {
245 return PTAG('HELP');
246
247 },
248
249 _getCommand: function(tokens) {
250 if(tokens[0] && MongoKeywords.include((tokens[0].value + '').toLowerCase())) {
251 switch(tokens[0].value.toLowerCase()) {
252 case 'help':
253 return this._help;
254 }
255 }
256 }
257};
258
259$htmlFormat = function(obj) {
260 return tojson(obj, ' ', ' ', true);
261}
262
263$(document).ready(function() {
264 var mongo = new MongoHandler();
265 var terminal = new ReadLine({htmlForInput: DefaultInputHtml,
266 handler: mongo._process,
267 scoper: mongo});
268});