Comparing version 0.1.1 to 1.0.0
@@ -5,15 +5,10 @@ var read = require("../lib/read.js") | ||
read({prompt: "Password: ", default: "test-pass", silent: true }, function (er, pass) { | ||
read({prompt: "Enter 4 characters: ", num: 4 }, function (er, four) { | ||
read({prompt: "Password again: ", default: "test-pass", silent: true }, function (er, pass2) { | ||
console.error({user: user, | ||
pass: pass, | ||
verify: pass2, | ||
four:four, | ||
passMatch: (pass === pass2)}) | ||
console.error("If the program doesn't end right now,\n" | ||
+"then you may be experiencing this bug:\n" | ||
+"https://github.com/joyent/node/issues/2257") | ||
}) | ||
read({prompt: "Password again: ", default: "test-pass", silent: true }, function (er, pass2) { | ||
console.error({user: user, | ||
pass: pass, | ||
verify: pass2, | ||
passMatch: (pass === pass2)}) | ||
console.error("the program should exit now") | ||
}) | ||
}) | ||
}) |
233
lib/read.js
module.exports = read | ||
var buffer = "" | ||
, tty = require("tty") | ||
, StringDecoder = require("string_decoder").StringDecoder | ||
, stdin | ||
, stdout | ||
var readline = require('readline') | ||
var Mute = require('mute-stream') | ||
function raw (stdin, mode) { | ||
if (stdin.setRawMode && stdin.isTTY) { | ||
stdin.setRawMode(mode) | ||
return | ||
function read (opts, cb) { | ||
if (opts.num) { | ||
throw new Error('read() no longer accepts a char number limit') | ||
} | ||
// old style | ||
try { | ||
tty.setRawMode(mode) | ||
} catch (e) {} | ||
} | ||
var input = opts.input || process.stdin | ||
var output = opts.output || process.stdout | ||
var m = new Mute() | ||
m.pipe(output) | ||
output = m | ||
var def = opts.default || '' | ||
var terminal = !!(opts.terminal || output.isTTY) | ||
var rlOpts = { input: input, output: output, terminal: terminal } | ||
var rl = readline.createInterface(rlOpts) | ||
var prompt = (opts.prompt || '').trim() + ' ' | ||
var silent = opts.silent | ||
var editDef = false | ||
var timeout = opts.timeout | ||
function read (opts, cb) { | ||
if (!cb) cb = opts, opts = {} | ||
var p = opts.prompt || "" | ||
, def = opts.default | ||
, silent = opts.silent | ||
, timeout = opts.timeout | ||
, num = opts.num || null | ||
stdin = opts.stdin || process.stdin | ||
stdout = opts.stdout || process.stdout | ||
if (p && def) p += "("+(silent ? "<default hidden>" : def)+") " | ||
// switching into raw mode is a little bit painful. | ||
// avoid if possible. | ||
var r = silent || num ? rawRead : normalRead | ||
if (r === rawRead && !stdin.isTTY) r = normalRead | ||
if (timeout) { | ||
cb = (function (cb) { | ||
var called = false | ||
var t = setTimeout(function () { | ||
raw(false) | ||
stdout.write("\n") | ||
if (def) done(null, def) | ||
else done(new Error("timeout")) | ||
}, timeout) | ||
function done (er, data) { | ||
clearTimeout(t) | ||
if (called) return | ||
// stop reading! | ||
stdin.pause() | ||
called = true | ||
cb(er, data) | ||
} | ||
return done | ||
})(cb) | ||
if (def) { | ||
if (silent) { | ||
prompt += '(<default hidden>) ' | ||
} else if (opts.edit) { | ||
editDef = true | ||
} else { | ||
prompt += '(' + def + ') ' | ||
} | ||
} | ||
if (p && !stdout.write(p)) { | ||
stdout.on("drain", function D () { | ||
stdout.removeListener("drain", D) | ||
r(def, timeout, silent, num, cb) | ||
}) | ||
if (silent) { | ||
output.unmute() | ||
rl.setPrompt(prompt) | ||
rl.prompt() | ||
output.mute() | ||
} else { | ||
process.nextTick(function () { | ||
r(def, timeout, silent, num, cb) | ||
}) | ||
rl.setPrompt(prompt) | ||
rl.prompt() | ||
if (editDef) { | ||
rl.line = def | ||
rl.cursor = def.length | ||
rl._refreshLine() | ||
} | ||
} | ||
} | ||
function normalRead (def, timeout, silent, num, cb) { | ||
var val = "" | ||
, decoder = new StringDecoder("utf8") | ||
var called = false | ||
rl.on('line', onLine) | ||
rl.on('error', onError) | ||
if (stdin === process.stdin) { | ||
stdin = process.openStdin() | ||
} | ||
stdin.resume() | ||
stdin.on("error", cb) | ||
stdin.on("data", function D (chunk) { | ||
// get the characters that are completed. | ||
val += buffer + decoder.write(chunk) | ||
buffer = "" | ||
// \r has no place here. | ||
val = val.replace(/\r/g, "") | ||
if (val.indexOf("\n") !== -1) { | ||
// pluck off any delims at the beginning. | ||
if (val !== "\n") { | ||
var i, l | ||
for (i = 0, l = val.length; i < l; i ++) { | ||
if (val.charAt(i) !== "\n") break | ||
} | ||
if (i !== 0) val = val.substr(i) | ||
} | ||
// hack. if we get the number of chars, just pretend there was a delim | ||
if (num > 0 && val.length >= num) { | ||
val = val.substr(0, num) + "\n" + val.substr(num) | ||
} | ||
// buffer whatever might have come *after* the delimter | ||
var delimIndex = val.indexOf("\n") | ||
if (delimIndex !== -1) { | ||
buffer = val.substr(delimIndex) | ||
val = val.substr(0, delimIndex) | ||
} else { | ||
buffer = "" | ||
} | ||
stdin.pause() | ||
stdin.removeListener("data", D) | ||
stdin.removeListener("error", cb) | ||
// read(1) trims | ||
val = val.trim() || def | ||
cb(null, val) | ||
} | ||
rl.on('SIGINT', function () { | ||
rl.close() | ||
onError(new Error('canceled')) | ||
}) | ||
} | ||
function rawRead (def, timeout, silent, num, cb) { | ||
var val = "" | ||
, decoder = new StringDecoder | ||
var timer | ||
if (timeout) { | ||
timer = setTimeout(function () { | ||
onError(new Error('timed out')) | ||
}, timeout) | ||
} | ||
if (stdin === process.stdin) { | ||
stdin = process.openStdin() | ||
function done () { | ||
called = true | ||
rl.close() | ||
clearTimeout(timer) | ||
output.mute() | ||
} | ||
raw(stdin, true) | ||
stdin.resume() | ||
stdin.on("error", cb) | ||
stdin.on("data", function D (c) { | ||
// \r is my enemy. | ||
var s = decoder.write(c).replace(/\r/g, "\n") | ||
var i = 0 | ||
function onError (er) { | ||
if (called) return | ||
done() | ||
return cb(er) | ||
} | ||
LOOP: while (c = s.charAt(i++)) switch (c) { | ||
case "\u007f": // backspace | ||
val = val.substr(0, val.length - 1) | ||
if (!silent) stdout.write('\b \b') | ||
break | ||
case "\u0004": // EOF | ||
case "\n": | ||
raw(stdin, false) | ||
stdin.removeListener("data", D) | ||
stdin.removeListener("error", cb) | ||
val = val.trim() || def | ||
stdout.write("\n") | ||
stdin.pause() | ||
return cb(null, val) | ||
case "\u0003": case "\0": // ^C or other signal abort | ||
raw(stdin, false) | ||
stdin.removeListener("data", D) | ||
stdin.removeListener("error", cb) | ||
stdin.pause() | ||
return cb(new Error("cancelled")) | ||
default: // just a normal char | ||
val += buffer + c | ||
buffer = "" | ||
if (!silent) stdout.write(c) | ||
// explicitly process a delim if we have enough chars | ||
// and stop the processing. | ||
if (num && val.length >= num) D("\n") | ||
break LOOP | ||
function onLine (line) { | ||
if (called) return | ||
if (silent && terminal) { | ||
output.unmute() | ||
output.write('\r\n') | ||
} | ||
}) | ||
done() | ||
var isDefault = !!(editDef && line === def) | ||
if (def && !line) { | ||
isDefault = true | ||
line = def | ||
} | ||
// truncate the \n at the end. | ||
line = line.replace(/\r?\n$/, '') | ||
cb(null, line, isDefault) | ||
} | ||
} |
{ | ||
"name": "read", | ||
"version": "0.1.1", | ||
"version": "1.0.0", | ||
"main": "lib/read.js", | ||
"dependencies": {}, | ||
"dependencies": { | ||
"mute-stream": "0" | ||
}, | ||
"devDependencies": { | ||
@@ -10,3 +12,3 @@ "tap": "*" | ||
"engines": { | ||
"node": ">=0.6" | ||
"node": ">=0.8" | ||
}, | ||
@@ -13,0 +15,0 @@ "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)", |
@@ -0,3 +1,8 @@ | ||
## read | ||
For reading user input from stdin. | ||
Similar to the `readline` builtin's `question()` method, but with a | ||
few more features. | ||
## USAGE | ||
@@ -11,3 +16,3 @@ | ||
The callback gets called with either the user input, or the default | ||
specified, or an error, in the traditional `callback(error, result)` | ||
specified, or an error, as `callback(error, result, isDefault)` | ||
node style. | ||
@@ -21,26 +26,14 @@ | ||
* `silent` Don't echo the output as the user types it. | ||
* `num` Max number of chars to read from terminal. | ||
* `timeout` Number of ms to wait for user input before giving up. | ||
* `default` The default value if the user enters nothing. | ||
* `edit` Allow the user to edit the default value. | ||
* `terminal` Treat the output as a TTY, whether it is or not. | ||
* `stdin` Readable stream to get input data from. (default `process.stdin`) | ||
* `stdout` Writeable stream to write prompts to. (default: `process.stdout`) | ||
If silent is true, or num is set, and the input is a TTY, | ||
then read will set raw mode, and read character by character. | ||
If silent is true, and the input is a TTY, then read will set raw | ||
mode, and read character by character. | ||
At this time, backspace and arrow keys are not supported very well. | ||
It's probably not too hard to add support for this, perhaps using node's | ||
built-in readline module. | ||
## CONTRIBUTING | ||
Patches welcome. | ||
## BUGS | ||
In node 0.6.0 through 0.6.5, you must explicitly call | ||
`process.stdin.destroy()` or `process.exit()` when you know that your | ||
program is done reading, or else it will keep the event loop running | ||
forever. | ||
See: <https://github.com/joyent/node/issues/2257> |
@@ -13,2 +13,3 @@ var tap = require('tap') | ||
var output = '' | ||
var write = child.stdin.write.bind(child.stdin) | ||
child.stdout.on('data', function (c) { | ||
@@ -18,9 +19,9 @@ console.error('data %s', c) | ||
if (output.match(/Username: \(test-user\) $/)) { | ||
child.stdin.write('a user\n') | ||
process.nextTick(write.bind(null, 'a user\n')) | ||
} else if (output.match(/Password: \(<default hidden>\) $/)) { | ||
child.stdin.write('a password\n') | ||
} else if (output.match(/characters: $/)) { | ||
child.stdin.write('asdf\n') | ||
process.nextTick(write.bind(null, 'a password\n')) | ||
} else if (output.match(/Password again: \(<default hidden>\) $/)) { | ||
child.stdin.write('a password\n') | ||
process.nextTick(write.bind(null, 'a password\n')) | ||
} else { | ||
console.error('prompts done, output=%j', output) | ||
} | ||
@@ -32,2 +33,3 @@ }) | ||
result += c | ||
console.error('result %j', c.toString()) | ||
}) | ||
@@ -37,4 +39,4 @@ | ||
result = JSON.parse(result) | ||
t.same(result, {"user":"a user","pass":"a password","verify":"a password","four":"asdf","passMatch":true}) | ||
t.equal(output, 'Username: (test-user) Password: (<default hidden>) Enter 4 characters: Password again: (<default hidden>) ') | ||
t.same(result, {"user":"a user","pass":"a password","verify":"a password","passMatch":true}) | ||
t.equal(output, 'Username: (test-user) Password: (<default hidden>) Password again: (<default hidden>) ') | ||
t.end() | ||
@@ -47,10 +49,7 @@ }) | ||
read({prompt: "Password: ", default: "test-pass", silent: true }, function (er, pass) { | ||
read({prompt: "Enter 4 characters: ", num: 4 }, function (er, four) { | ||
read({prompt: "Password again: ", default: "test-pass", silent: true }, function (er, pass2) { | ||
console.error(JSON.stringify({user: user, | ||
pass: pass, | ||
verify: pass2, | ||
four:four, | ||
passMatch: (pass === pass2)})) | ||
}) | ||
read({prompt: "Password again: ", default: "test-pass", silent: true }, function (er, pass2) { | ||
console.error(JSON.stringify({user: user, | ||
pass: pass, | ||
verify: pass2, | ||
passMatch: (pass === pass2)})) | ||
}) | ||
@@ -57,0 +56,0 @@ }) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
9
1
8560
1
199
38
2
+ Addedmute-stream@0
+ Addedmute-stream@0.0.8(transitive)