From 3ae0b17c76f693dd2e68a46f78c7dc7f595b33c6 Mon Sep 17 00:00:00 2001 From: Yazhong Liu Date: Sat, 15 Feb 2014 22:21:26 +0800 Subject: [PATCH] repl: REPLServer inherits from readline.Interface This exposes a setPrompt for and other readline features --- doc/api/repl.markdown | 5 ++- lib/repl.js | 58 +++++++++++++------------- test/simple/test-repl-options.js | 29 ++++++++++--- test/simple/test-repl-require-cache.js | 2 +- test/simple/test-repl-setprompt.js | 49 ++++++++++++++++++++++ 5 files changed, 104 insertions(+), 39 deletions(-) create mode 100644 test/simple/test-repl-setprompt.js diff --git a/doc/api/repl.markdown b/doc/api/repl.markdown index 5aad91e4e6..e5081c3c8d 100644 --- a/doc/api/repl.markdown +++ b/doc/api/repl.markdown @@ -32,8 +32,9 @@ For example, you could add this to your bashrc file: ## repl.start(options) -Returns and starts a `REPLServer` instance. Accepts an "options" Object that -takes the following values: +Returns and starts a `REPLServer` instance, that inherits from +[Readline Interface][]. Accepts an "options" Object that takes +the following values: - `prompt` - the prompt and `stream` for all I/O. Defaults to `> `. diff --git a/lib/repl.js b/lib/repl.js index fe1f98cbbf..a10bd33b38 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -48,7 +48,6 @@ var path = require('path'); var fs = require('fs'); var rl = require('readline'); var Console = require('console').Console; -var EventEmitter = require('events').EventEmitter; var domain = require('domain'); var debug = util.debuglog('repl'); @@ -82,8 +81,6 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { return new REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined); } - EventEmitter.call(this); - var options, input, output, dom; if (util.isObject(prompt)) { // an options object was given @@ -109,6 +106,9 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { self.useGlobal = !!useGlobal; self.ignoreUndefined = !!ignoreUndefined; + // just for backwards compat, see github.com/joyent/node/pull/7127 + self.rli = this; + eval_ = eval_ || defaultEval; function defaultEval(code, context, file, cb) { @@ -179,19 +179,18 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { self.bufferedCommand = ''; self.lines.level = []; - self.prompt = !util.isUndefined(prompt) ? prompt : '> '; - function complete(text, callback) { self.complete(text, callback); } - var rli = rl.createInterface({ - input: self.inputStream, - output: self.outputStream, - completer: complete, - terminal: options.terminal - }); - self.rli = rli; + rl.Interface.apply(this, [ + self.inputStream, + self.outputStream, + complete, + options.terminal + ]) + + self.setPrompt(!util.isUndefined(prompt) ? prompt : '> '); this.commands = {}; defineDefaultCommands(this); @@ -200,7 +199,7 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { self.writer = options.writer || exports.writer; if (util.isUndefined(options.useColors)) { - options.useColors = rli.terminal; + options.useColors = self.terminal; } self.useColors = !!options.useColors; @@ -211,24 +210,24 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { }; } - rli.setPrompt(self.prompt); + self.setPrompt(self._prompt); - rli.on('close', function() { + self.on('close', function() { self.emit('exit'); }); var sawSIGINT = false; - rli.on('SIGINT', function() { - var empty = rli.line.length === 0; - rli.clearLine(); + self.on('SIGINT', function() { + var empty = self.line.length === 0; + self.clearLine(); if (!(self.bufferedCommand && self.bufferedCommand.length > 0) && empty) { if (sawSIGINT) { - rli.close(); + self.close(); sawSIGINT = false; return; } - rli.output.write('(^C again to quit)\n'); + self.output.write('(^C again to quit)\n'); sawSIGINT = true; } else { sawSIGINT = false; @@ -239,7 +238,7 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { self.displayPrompt(); }); - rli.on('line', function(cmd) { + self.on('line', function(cmd) { debug('line %j', cmd); sawSIGINT = false; var skipCatchall = false; @@ -322,13 +321,13 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { }; }); - rli.on('SIGCONT', function() { + self.on('SIGCONT', function() { self.displayPrompt(true); }); self.displayPrompt(); } -inherits(REPLServer, EventEmitter); +inherits(REPLServer, rl.Interface); exports.REPLServer = REPLServer; @@ -340,7 +339,6 @@ exports.start = function(prompt, source, eval_, useGlobal, ignoreUndefined) { return repl; }; - REPLServer.prototype.createContext = function() { var context; if (this.useGlobal) { @@ -388,17 +386,17 @@ REPLServer.prototype.resetContext = function() { }; REPLServer.prototype.displayPrompt = function(preserveCursor) { - var prompt = this.prompt; + var prompt = this._prompt; if (this.bufferedCommand.length) { prompt = '...'; var levelInd = new Array(this.lines.level.length).join('..'); prompt += levelInd + ' '; + } else { + this.setPrompt(prompt); } - this.rli.setPrompt(prompt); - this.rli.prompt(preserveCursor); + this.prompt(preserveCursor); }; - // A stream to push an array into a REPL // used in REPLServer.complete function ArrayStream() { @@ -838,7 +836,7 @@ function defineDefaultCommands(repl) { repl.defineCommand('exit', { help: 'Exit the repl', action: function() { - this.rli.close(); + this.close(); } }); @@ -879,7 +877,7 @@ function defineDefaultCommands(repl) { this.displayPrompt(); lines.forEach(function(line) { if (line) { - self.rli.write(line + '\n'); + self.write(line + '\n'); } }); } diff --git a/test/simple/test-repl-options.js b/test/simple/test-repl-options.js index f9175e7df2..94a622da26 100644 --- a/test/simple/test-repl-options.js +++ b/test/simple/test-repl-options.js @@ -37,14 +37,23 @@ var r1 = repl.start({ output: stream, terminal: true }); + +assert.equal(r1.input, stream); +assert.equal(r1.output, stream); +assert.equal(r1.input, r1.inputStream); +assert.equal(r1.output, r1.outputStream); +assert.equal(r1.terminal, true); +assert.equal(r1.useColors, r1.terminal); +assert.equal(r1.useGlobal, false); +assert.equal(r1.ignoreUndefined, false); + +// test r1 for backwards compact assert.equal(r1.rli.input, stream); assert.equal(r1.rli.output, stream); assert.equal(r1.rli.input, r1.inputStream); assert.equal(r1.rli.output, r1.outputStream); assert.equal(r1.rli.terminal, true); assert.equal(r1.useColors, r1.rli.terminal); -assert.equal(r1.useGlobal, false); -assert.equal(r1.ignoreUndefined, false); // 2 function writer() {} @@ -59,12 +68,20 @@ var r2 = repl.start({ eval: evaler, writer: writer }); +assert.equal(r2.input, stream); +assert.equal(r2.output, stream); +assert.equal(r2.input, r2.inputStream); +assert.equal(r2.output, r2.outputStream); +assert.equal(r2.terminal, false); +assert.equal(r2.useColors, true); +assert.equal(r2.useGlobal, true); +assert.equal(r2.ignoreUndefined, true); +assert.equal(r2.writer, writer); + +// test r2 for backwards compact assert.equal(r2.rli.input, stream); assert.equal(r2.rli.output, stream); assert.equal(r2.rli.input, r2.inputStream); assert.equal(r2.rli.output, r2.outputStream); assert.equal(r2.rli.terminal, false); -assert.equal(r2.useColors, true); -assert.equal(r2.useGlobal, true); -assert.equal(r2.ignoreUndefined, true); -assert.equal(r2.writer, writer); + diff --git a/test/simple/test-repl-require-cache.js b/test/simple/test-repl-require-cache.js index ea2d6d6bd9..fe5753d215 100644 --- a/test/simple/test-repl-require-cache.js +++ b/test/simple/test-repl-require-cache.js @@ -28,6 +28,6 @@ var common = require('../common'), require.cache.something = 1; assert.equal(require.cache.something, 1); -repl.start({ useGlobal: false }).rli.close(); +repl.start({ useGlobal: false }).close(); assert.equal(require.cache.something, 1); diff --git a/test/simple/test-repl-setprompt.js b/test/simple/test-repl-setprompt.js new file mode 100644 index 0000000000..e5021c2356 --- /dev/null +++ b/test/simple/test-repl-setprompt.js @@ -0,0 +1,49 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'), + assert = require('assert'), + spawn = require('child_process').spawn, + os = require('os'), + util = require('util'); + +var args = [ + '-e', + 'var e = new (require("repl")).REPLServer("foo.. "); e.context.e = e;', +]; + +var p = "bar.. "; + +var child = spawn(process.execPath, args); + +child.stdout.setEncoding('utf8'); + +var data = ''; +child.stdout.on('data', function(d) { data += d }); + +child.stdin.end(util.format("e.setPrompt('%s');%s", p, os.EOL)); + +child.on('close', function(code, signal) { + assert.strictEqual(code, 0); + assert.ok(!signal); + var lines = data.split(/\n/); + assert.strictEqual(lines.pop(), p); +});