/* JSShell - Interactive JavaScript shell that runs inside a browser Copyright (C) 2004 Guido Wesdorp email johnny@debris.demon.nl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA $Id: jsshell.js,v 1.25 2004/08/11 02:29:52 johnny Exp $ */ // $Id: davclient.js,v 1.25 2004/08/11 02:29:52 johnny Exp $ VERSION = "1.0b"; LICENSE = "\n" + "JSShell - Interactive JavaScript shell that runs inside a browser\n" + "Copyright (C) 2004 Guido Wesdorp\n" + "Email johnny@debris.demon.nl\n" + "Downloads on http://debris.demon.nl\n" + " \n" + "This program is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 2 of the License, or\n" + "(at your option) any later version.\n" + " \n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + " \n" + "You should have received a copy of the GNU General Public License\n" + "along with this program; if not, write to the Free Software\n" + "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n" + " \n" + "A copy of the license should also be available on\n" + "http://debris.demon.nl/GPL.\n"; //----------------------------------------------------------------------------- // Simple synchronous implementation that uses window.prompt() to get input //----------------------------------------------------------------------------- function JSPromptPrompt(outputelement, promptwindow) { this.outputelement = outputelement; this.window = promptwindow ? promptwindow : window; }; JSPromptPrompt.prototype.displayOutput = function(output) { var win = this.window; var doc = win.document; var lines = output.split('\n'); for (var i=0; i < lines.length; i++) { var line = lines[i]; if (line.strip() == '') { line = '\xa0'; }; line = line.replace(/\t/g, '\xa0\xa0\xa0\xa0'); line = line.replace(/ /g, '\xa0'); var div = doc.createElement('div'); div.appendChild(doc.createTextNode(line)); this.outputelement.appendChild(div); var scrollmax = win.scrollMaxY ? win.scrollMaxY : doc.scrollHeight; if (scrollmax) { win.scroll(0, scrollmax); } else { try { div.scrollIntoView(); } catch(e) { // pass }; }; }; }; JSPromptPrompt.prototype.promptLine = function() { var line = this.window.prompt('Enter command:'); return line; }; //----------------------------------------------------------------------------- // More advanced async implementation that uses an input field //----------------------------------------------------------------------------- function JSInputPrompt(inputelement, outputelement, promptwindow, maxhistsize) { this.inputelement = inputelement; this.outputelement = outputelement; this.window = promptwindow ? promptwindow : window; this.maxhistsize = maxhistsize ? maxhistsize : 255; this._history = []; this._current_index = 0; }; JSInputPrompt.prototype = new JSPromptPrompt; JSInputPrompt.prototype.startPrompt = function(shell) { this.shell = shell; this.callback = shell.asyncHandler; addEventHandler(this.inputelement, 'keydown', this.handleKeyPress, this); }; JSInputPrompt.prototype.handleKeyPress = function(evt) { if (evt.keyCode == 13) { this.callback.call(this.shell); if (evt.preventDefault) { evt.preventDefault(); } else { evt.returnValue = false; }; } else if (evt.keyCode == 38 || evt.virtKeyVal == 22) { this.showPreviousCommand(); } else if (evt.keyCode == 40 || evt.virtKeyVal == 23) { this.showNextCommand(); }; }; JSInputPrompt.prototype.getCurrentLine = function() { var ret = this.inputelement.value; this.inputelement.value = ''; return ret; }; JSInputPrompt.prototype.showPreviousCommand = function() { if (this._current == this._history.length) { this._pop_current = true; }; this._history[this._current] = this.inputelement.value; if (this._current > 0) { this._current--; this.inputelement.value = this._history[this._current]; }; }; JSInputPrompt.prototype.showNextCommand = function() { if (this._current < this._history.length - 1) { this._current++; this.inputelement.value = this._history[this._current]; }; }; JSInputPrompt.prototype.registerCommandToHistory = function(command) { if (command.strip() == '') { return; }; if (this._pop_current) { this._history.pop(); this._pop_current = false; }; if (this._history.length > this.maxhistsize - 1) { this._history.unshift(); }; this._history.push(command); this._current = this._history.length; }; //----------------------------------------------------------------------------- // Glue code, drives the prompt object //----------------------------------------------------------------------------- function JSShell(promptobj, context, evalwindow) { this.prompt = promptobj; this.context = context; this.window = evalwindow ? evalwindow : window; this._helpers = {}; this._helper_helps = {}; this.registerHelper(this.user_about, 'sh_about', 'show \'about\' text'); this.registerHelper(this.user_help, 'sh_help', 'show this helptext'); this.registerHelper(this.user_license, 'sh_license', 'show some details about the license'); this.registerHelper(this.user_print, 'sh_print', 'print a line of text to the shell', 'string'); }; JSShell.prototype.loop = function() { this.displayStart(); while (1) { var input = this.prompt.promptLine(); if (input == null || input == 'quit') { break; }; this.prompt.displayOutput('--> ' + input); this.processInput(input); }; this.prompt.displayOutput('bye bye'); }; JSShell.prototype.startAsync = function(callback) { this.displayStart(); callback.call(this.prompt, this); }; JSShell.prototype.asyncHandler = function() { var input = this.harvestInput(); this.prompt.displayOutput('--> ' + input); if (this.prompt.registerCommandToHistory) { this.prompt.registerCommandToHistory(input); }; this.processInput(input); }; JSShell.prototype.displayStart = function() { var out = '*** JSShell v' + VERSION + ' ***\n' + '(c) Guido Wesdorp 2004, released under terms of the GPL\n' + navigator.appName + ' browser running on ' + navigator.platform + '\n' + navigator.userAgent + '\n' + 'Type \'sh_help()\' for help, \'sh_about()\' for info\n'; this.prompt.displayOutput(out); }; JSShell.prototype.processInput = function(input) { try { var res = eval.call(this.context, input); if (res !== undefined) { this.prompt.displayOutput('' + res); }; } catch (e) { var msg = ''; if (e.name) { msg += e.name + ': '; }; if (e.message) { msg += e.message; } else { msg += e.toString(); }; this.prompt.displayOutput('!!! ' + msg); }; }; JSShell.prototype.harvestInput = function() { return this.prompt.getCurrentLine(); }; JSShell.prototype.registerHelper = function(func, name, helptext, argstr, retstr) { /* register a helper command 'func' will be exposed to the user under name 'name' and will be called with context (this) 'this' (so if you use 'this' in the helper function you will get a reference to the current instance of the JSShell object). */ var wrapper = new ContextFixer(func, this); this.window[name] = wrapper.execute; this._helper_helps[name] = [helptext, argstr, retstr]; }; //----------------------------------------------------------------------------- // Additional user functions //----------------------------------------------------------------------------- JSShell.prototype.user_print = function(input) { this.prompt.displayOutput('' + input); }; JSShell.prototype.user_print.doc = "function sh_print(string)\n" + "\tprints to the shell"; JSShell.prototype.user_about = function() { var out = "\nJSShell is written by Guido Wesdorp in 2004.\n" + "For questions, remarks or to report bugs or submit patches\n" + "send an email to johnny@debris.demon.nl.\n\n" + "This program is Free Software. It is released under terms\n" + "of the General Public License (GPL) version 2. To read the\n" + "full license, go to http://debris.demon.nl/GPL.\n\n" + "This program can be downloaded from http://debris.demon.nl." + "\n\n" + "Thanks for using JSShell!\n\n" + "Guido Wesdorp\n"; this.prompt.displayOutput(out); }; JSShell.prototype.user_about.doc = 'function sh_about()\n' + '\tdisplays some text about the program ' + 'and its author'; JSShell.prototype.user_help = function() { var out = "\nJSShell - a browser-based JavaScript shell\n\n" + "This shell can be used to execute single-line JavaScript\n" + "commands. You can enter any type of JavaScript statement and\n" + "view the result instantly. The cursor up and down keys can\n" + "be used to re-use previous commands.\n\n" + "Some additional functions are available:\n" + "\n"; for (var name in this._helper_helps) { var helptuple = this._helper_helps[name]; var helptext = helptuple[0] ? helptuple[0] : ''; var helpargs = helptuple[1] ? helptuple[1] : ''; var helpret = helptuple[2] ? ' -> ' + helptuple[2] : ''; out += name + "(" + helpargs + ")" + helpret + "\n"; var lines = helptuple[0].split('\n'); for (var i=0; i < lines.length; i++) { out += "\t" + lines[i] + "\n"; }; out += "\n"; }; this.prompt.displayOutput(out.substr(out, out.length - 1)); }; JSShell.prototype.user_help.doc = "function sh_help()\n" + "\tdisplays help text"; JSShell.prototype.user_license = function() { this.prompt.displayOutput(LICENSE); }; JSShell.prototype.user_license.doc = "function sh_license()\n" + "\tdisplays details about the license"; //----------------------------------------------------------------------------- // Helper functions and classes // all these are copies from my JavaScript helper lib //----------------------------------------------------------------------------- // register an event handler for Mozilla or IE function addEventHandler(element, event, method, context) { /* method to add an event handler for both IE and Mozilla */ var wrappedmethod = new ContextFixer(method, context); var args = new Array(null, null); for (var i=4; i < arguments.length; i++) { args.push(arguments[i]); }; wrappedmethod.args = args; try { if (element.addEventListener) { element.addEventListener(event, wrappedmethod.execute, false); } else if (element.attachEvent) { element.attachEvent("on" + event, wrappedmethod.execute); } else { throw "Unsupported browser!"; }; } catch(e) { alert('exception ' + e.message + ' while registering an event handler for element ' + element + ', event ' + event + ', method ' + method); }; }; // fix 'this' when using a method as a callback function ContextFixer(func, context) { /* Make sure 'this' inside a method points to its class */ this.func = func; this.context = context; this.args = arguments; var self = this; this.execute = function() { /* execute the method */ var args = new Array(); // the first arguments will be the extra ones of the class for (var i=0; i < self.args.length - 2; i++) { args.push(self.args[i + 2]); }; // the last are the ones passed on to the execute method for (var i=0; i < arguments.length; i++) { args.push(arguments[i]); }; return self.func.apply(self.context, args); }; this.execute.toString = function() { return self.func.doc ? self.func.doc : self.func.toString(); }; }; // JavaScript has a friggin' blink() function, but not for string stripping... String.prototype.strip = function() { var stripspace = /^\s*([\s\S]*?)\s*$/; return stripspace.exec(this)[1]; }; //----------------------------------------------------------------------------- // Initializers, call one of these from your HTML //----------------------------------------------------------------------------- function startPromptShell(displayid, contextwindow) { var context = {}; var display = document.getElementById(displayid); contextwindow = contextwindow ? contextwindow : window; var prompt = new JSPromptPrompt(display, contextwindow); var shell = new JSShell(prompt, context, contextwindow); shell.loop(prompt.startPrompt); return shell; }; function startInputShell(inputid, displayid, contextwindow) { var input = document.getElementById(inputid); input.focus(); var display = document.getElementById(displayid); addEventHandler(display, 'click', function() {input.focus()}); contextwindow = contextwindow ? contextwindow : window; var prompt = new JSInputPrompt(input, display, contextwindow); // note that we use the contextwindow for both window and context // references, this works best in IE var shell = new JSShell(prompt, contextwindow, contextwindow); shell.startAsync(prompt.startPrompt); return shell; };