From 862cc28183b03f0e1a67b052b5c8250a3e2e8995 Mon Sep 17 00:00:00 2001 From: Julien Gilli Date: Mon, 22 Sep 2014 13:21:11 -0700 Subject: [PATCH] readline: should not require an output stream. Passing null as the output stream to readline.Interface()'s constructor is now supported. Any output written by readline is just discarded. It makes it easier to use readline just as a line parser. Fixes: https://github.com/joyent/node/issues/4408 Reviewed-by: Trevor Norris --- doc/api/readline.markdown | 13 +++++-- lib/readline.js | 54 +++++++++++++++++++------- test/simple/test-readline-interface.js | 31 +++++++++++++++ 3 files changed, 81 insertions(+), 17 deletions(-) diff --git a/doc/api/readline.markdown b/doc/api/readline.markdown index 16bbd3c0cdf..6aab28679ba 100644 --- a/doc/api/readline.markdown +++ b/doc/api/readline.markdown @@ -30,7 +30,7 @@ the following values: - `input` - the readable stream to listen to (Required). - - `output` - the writable stream to write readline data to (Required). + - `output` - the writable stream to write readline data to (Optional). - `completer` - an optional function that is used for Tab autocompletion. See below for an example of using this. @@ -100,6 +100,9 @@ to `true` to prevent the cursor placement being reset to `0`. This will also resume the `input` stream used with `createInterface` if it has been paused. +If `output` is set to `null` or `undefined` when calling `createInterface`, the +prompt is not written. + ### rl.question(query, callback) Prepends the prompt with `query` and invokes `callback` with the user's @@ -109,6 +112,9 @@ with the user's response after it has been typed. This will also resume the `input` stream used with `createInterface` if it has been paused. +If `output` is set to `null` or `undefined` when calling `createInterface`, +nothing is displayed. + Example usage: interface.question('What is your favorite food?', function(answer) { @@ -130,8 +136,9 @@ Closes the `Interface` instance, relinquishing control on the `input` and ### rl.write(data[, key]) -Writes `data` to `output` stream. `key` is an object literal to represent a key -sequence; available if the terminal is a TTY. +Writes `data` to `output` stream, unless `output` is set to `null` or +`undefined` when calling `createInterface`. `key` is an object literal to +represent a key sequence; available if the terminal is a TTY. This will also resume the `input` stream if it has been paused. diff --git a/lib/readline.js b/lib/readline.js index a3f1b9d5801..bafef00e0dd 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -68,7 +68,7 @@ function Interface(input, output, completer, terminal) { // backwards compat; check the isTTY prop of the output stream // when `terminal` was not specified - if (util.isUndefined(terminal)) { + if (util.isUndefined(terminal) && !util.isNullOrUndefined(output)) { terminal = !!output.isTTY; } @@ -142,11 +142,15 @@ function Interface(input, output, completer, terminal) { this.history = []; this.historyIndex = -1; - output.on('resize', onresize); + if (!util.isNullOrUndefined(output)) + output.on('resize', onresize); + self.once('close', function() { input.removeListener('keypress', onkeypress); input.removeListener('end', ontermend); - output.removeListener('resize', onresize); + if (!util.isNullOrUndefined(output)) { + output.removeListener('resize', onresize); + } }); } @@ -156,7 +160,10 @@ function Interface(input, output, completer, terminal) { inherits(Interface, EventEmitter); Interface.prototype.__defineGetter__('columns', function() { - return this.output.columns || Infinity; + var columns = Infinity; + if (this.output && this.output.columns) + columns = this.output.columns; + return columns; }); Interface.prototype.setPrompt = function(prompt) { @@ -177,7 +184,7 @@ Interface.prototype.prompt = function(preserveCursor) { if (!preserveCursor) this.cursor = 0; this._refreshLine(); } else { - this.output.write(this._prompt); + this._writeToOutput(this._prompt); } }; @@ -207,6 +214,13 @@ Interface.prototype._onLine = function(line) { } }; +Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) { + if (!util.isString(stringToWrite)) + throw new TypeError('stringToWrite must be a string'); + + if (!util.isNullOrUndefined(this.output)) + this.output.write(stringToWrite); +}; Interface.prototype._addHistory = function() { if (this.line.length === 0) return ''; @@ -245,11 +259,11 @@ Interface.prototype._refreshLine = function() { exports.clearScreenDown(this.output); // Write the prompt and the current buffer content. - this.output.write(line); + this._writeToOutput(line); // Force terminal to allocate a new line if (lineCols === 0) { - this.output.write(' '); + this._writeToOutput(' '); } // Move cursor to original position. @@ -351,7 +365,7 @@ Interface.prototype._insertString = function(c) { if (this._getCursorPos().cols === 0) { this._refreshLine(); } else { - this.output.write(c); + this._writeToOutput(c); } // a hack to get the line refreshed if it's needed @@ -378,7 +392,7 @@ Interface.prototype._tabComplete = function() { if (completions.length === 1) { self._insertString(completions[0].slice(completeOn.length)); } else { - self.output.write('\r\n'); + self._writeToOutput('\r\n'); var width = completions.reduce(function(a, b) { return a.length > b.length ? a : b; }).length + 2; // 2 space padding @@ -422,17 +436,17 @@ function handleGroup(self, group, width, maxColumns) { break; } var item = group[idx]; - self.output.write(item); + self._writeToOutput(item); if (col < maxColumns - 1) { for (var s = 0, itemLen = item.length; s < width - itemLen; s++) { - self.output.write(' '); + self._writeToOutput(' '); } } } - self.output.write('\r\n'); + self._writeToOutput('\r\n'); } - self.output.write('\r\n'); + self._writeToOutput('\r\n'); } function commonPrefix(strings) { @@ -525,7 +539,7 @@ Interface.prototype._deleteLineRight = function() { Interface.prototype.clearLine = function() { this._moveCursor(+Infinity); - this.output.write('\r\n'); + this._writeToOutput('\r\n'); this.line = ''; this.cursor = 0; this.prevRows = 0; @@ -1168,6 +1182,9 @@ function emitKeys(stream, s) { */ function cursorTo(stream, x, y) { + if (util.isNullOrUndefined(stream)) + return; + if (!util.isNumber(x) && !util.isNumber(y)) return; @@ -1188,6 +1205,9 @@ exports.cursorTo = cursorTo; */ function moveCursor(stream, dx, dy) { + if (util.isNullOrUndefined(stream)) + return; + if (dx < 0) { stream.write('\x1b[' + (-dx) + 'D'); } else if (dx > 0) { @@ -1211,6 +1231,9 @@ exports.moveCursor = moveCursor; */ function clearLine(stream, dir) { + if (util.isNullOrUndefined(stream)) + return; + if (dir < 0) { // to the beginning stream.write('\x1b[1K'); @@ -1230,6 +1253,9 @@ exports.clearLine = clearLine; */ function clearScreenDown(stream) { + if (util.isNullOrUndefined(stream)) + return; + stream.write('\x1b[0J'); } exports.clearScreenDown = clearScreenDown; diff --git a/test/simple/test-readline-interface.js b/test/simple/test-readline-interface.js index f91c10821a1..b86dd5a8a9b 100644 --- a/test/simple/test-readline-interface.js +++ b/test/simple/test-readline-interface.js @@ -307,5 +307,36 @@ function isWarned(emitter) { assert.equal(isWarned(process.stdout._events), false); } + //can create a new readline Interface with a null output arugument + fi = new FakeInput(); + rli = new readline.Interface({input: fi, output: null, terminal: terminal }); + + called = false; + rli.on('line', function(line) { + called = true; + assert.equal(line, 'asdf'); + }); + fi.emit('data', 'asdf\n'); + assert.ok(called); + + assert.doesNotThrow(function() { + rli.setPrompt("ddd> "); + }); + + assert.doesNotThrow(function() { + rli.prompt(); + }); + + assert.doesNotThrow(function() { + rli.write('really shouldnt be seeing this'); + }); + + assert.doesNotThrow(function() { + rli.question("What do you think of node.js? ", function(answer) { + console.log("Thank you for your valuable feedback:", answer); + rli.close(); + }) + }); + });