diff --git a/lib/repl.js b/lib/repl.js index 78171dab63..d0400235e7 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -46,6 +46,7 @@ exports.writer = util.inspect; function REPLServer(prompt, stream) { var self = this; if (!context) resetContext(); + if (!exports.repl) exports.repl = this; self.context = context; self.buffered_cmd = ''; @@ -56,6 +57,9 @@ function REPLServer(prompt, stream) { return self.complete(text); }); + this.commands = {}; + defineDefaultCommands(this); + if (rli.enabled && !disableColors) { // Turn on ANSI coloring. exports.writer = function(obj, showHidden, depth) { @@ -86,7 +90,12 @@ function REPLServer(prompt, stream) { // Check to see if a REPL keyword was used. If it returns true, // display next prompt and return. - if (self.parseREPLKeyword(cmd) === true) return; + if (cmd && cmd.charAt(0) === '.') { + var matches = cmd.match(/^(\.[^\s]+)\s*(.*)$/); + var keyword = matches && matches[1]; + var rest = matches && matches[2]; + if (self.parseREPLKeyword(keyword, rest) === true) return; + } // The catchall for errors try { @@ -185,7 +194,7 @@ REPLServer.prototype.complete = function (line) { var match = null; match = line.match(/^\s*(\.\w*)$/); if (match) { - completionGroups.push(['.break', '.clear', '.exit', '.help']); + completionGroups.push(Object.keys(this.commands)); completeOn = match[1]; if (match[1].length > 1) { filter = match[1]; @@ -397,34 +406,63 @@ REPLServer.prototype.complete = function (line) { * @returns {Boolean} If true it means don't continue parsing the command */ -REPLServer.prototype.parseREPLKeyword = function (cmd) { - var self = this; - - switch (cmd) { - case ".break": - // TODO remove me after 0.3.x - self.buffered_cmd = ''; - self.displayPrompt(); - return true; - case ".clear": - self.stream.write("Clearing context...\n"); - self.buffered_cmd = ''; - resetContext(); - self.displayPrompt(); - return true; - case ".exit": - self.stream.destroy(); - return true; - case ".help": - self.stream.write(".clear\tBreak, and also clear the local context.\n"); - self.stream.write(".exit\tExit the prompt\n"); - self.stream.write(".help\tShow repl options\n"); - self.displayPrompt(); +REPLServer.prototype.parseREPLKeyword = function (keyword, rest) { + var cmd = this.commands[keyword]; + if (cmd) { + cmd.action.call(this, rest); return true; } return false; }; +REPLServer.prototype.defineCommand = function(keyword, cmd) { + if (typeof cmd === 'function') cmd = {action: cmd}; + else if (typeof cmd.action !== 'function') { + throw new Error('bad argument, action must be a function'); + } + this.commands['.' + keyword] = cmd; +}; + +function defineDefaultCommands(repl) { + // TODO remove me after 0.3.x + repl.defineCommand('break', { + help: 'Sometimes you get stuck, this gets you out', + action: function() { + this.buffered_cmd = ''; + this.displayPrompt(); + } + }); + + repl.defineCommand('clear', { + help: 'Break, and also clear the local context', + action: function() { + this.stream.write("Clearing context...\n"); + this.buffered_cmd = ''; + resetContext(); + this.displayPrompt(); + } + }); + + repl.defineCommand('exit', { + help: 'Exit the repl', + action: function() { + this.stream.destroy(); + } + }); + + repl.defineCommand('help', { + help: 'Show repl options', + action: function() { + var self = this; + Object.keys(this.commands).sort().forEach(function(name) { + var cmd = self.commands[name]; + self.stream.write(name + "\t" + (cmd.help || '') + "\n"); + }); + this.displayPrompt(); + } + }); +} + function trimWhitespace (cmd) { var trimmer = /^\s*(.+)\s*$/m, matches = trimmer.exec(cmd);