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 <trev.norris@gmail.com>
This commit is contained in:
Julien Gilli 2014-09-22 13:21:11 -07:00 committed by Trevor Norris
parent 8dc6be1747
commit 862cc28183
3 changed files with 81 additions and 17 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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();
})
});
});