opencloud shell prototype; wip
[plstackapi.git] / planetstack / core / dashboard / shell / opencloud_shell.js
1 // 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.
8 var 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
21 ReadLine.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
114 var 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
126 MongoHandler.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 });