-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BREAKING CHANGE: this module has been refactored to use promises - the API is now promise only and no longer accepts a callback - the Promise is resolved to a string and no longer returns `isDefault`
- Loading branch information
1 parent
5a7563b
commit c5b56f6
Showing
8 changed files
with
173 additions
and
383 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,116 +1,82 @@ | ||
|
||
module.exports = read | ||
|
||
var readline = require('readline') | ||
var Mute = require('mute-stream') | ||
|
||
function read (opts, cb) { | ||
if (opts.num) { | ||
throw new Error('read() no longer accepts a char number limit') | ||
} | ||
|
||
if (typeof opts.default !== 'undefined' && | ||
typeof opts.default !== 'string' && | ||
typeof opts.default !== 'number') { | ||
const readline = require('readline') | ||
const Mute = require('mute-stream') | ||
|
||
module.exports = async function read ({ | ||
default: def = '', | ||
input = process.stdin, | ||
output = process.stdout, | ||
prompt = '', | ||
silent, | ||
timeout, | ||
edit, | ||
terminal, | ||
replace, | ||
}) { | ||
if (typeof def !== 'undefined' && typeof def !== 'string' && typeof def !== 'number') { | ||
throw new Error('default value must be string or number') | ||
} | ||
|
||
var input = opts.input || process.stdin | ||
var output = opts.output || process.stdout | ||
var prompt = (opts.prompt || '').trim() + ' ' | ||
var silent = opts.silent | ||
var editDef = false | ||
var timeout = opts.timeout | ||
let editDef = false | ||
prompt = prompt.trim() + ' ' | ||
terminal = !!(terminal || output.isTTY) | ||
|
||
var def = opts.default || '' | ||
if (def) { | ||
if (silent) { | ||
prompt += '(<default hidden>) ' | ||
} else if (opts.edit) { | ||
} else if (edit) { | ||
editDef = true | ||
} else { | ||
prompt += '(' + def + ') ' | ||
} | ||
} | ||
var terminal = !!(opts.terminal || output.isTTY) | ||
|
||
var m = new Mute({ replace: opts.replace, prompt: prompt }) | ||
const m = new Mute({ replace, prompt }) | ||
m.pipe(output, { end: false }) | ||
output = m | ||
var rlOpts = { input: input, output: output, terminal: terminal } | ||
|
||
if (process.version.match(/^v0\.6/)) { | ||
var rl = readline.createInterface(rlOpts.input, rlOpts.output) | ||
} else { | ||
var rl = readline.createInterface(rlOpts) | ||
} | ||
|
||
output.unmute() | ||
rl.setPrompt(prompt) | ||
rl.prompt() | ||
if (silent) { | ||
output.mute() | ||
} else if (editDef) { | ||
rl.line = def | ||
rl.cursor = def.length | ||
rl._refreshLine() | ||
} | ||
|
||
var called = false | ||
rl.on('line', onLine) | ||
rl.on('error', onError) | ||
|
||
rl.on('SIGINT', function () { | ||
rl.close() | ||
onError(new Error('canceled')) | ||
}) | ||
|
||
var timer | ||
if (timeout) { | ||
timer = setTimeout(function () { | ||
onError(new Error('timed out')) | ||
}, timeout) | ||
} | ||
return new Promise((resolve, reject) => { | ||
const rl = readline.createInterface({ input, output, terminal }) | ||
const timer = timeout && setTimeout(() => onError(new Error('timed out')), timeout) | ||
|
||
function done () { | ||
called = true | ||
rl.close() | ||
output.unmute() | ||
rl.setPrompt(prompt) | ||
rl.prompt() | ||
|
||
if (process.version.match(/^v0\.6/)) { | ||
rl.input.removeAllListeners('data') | ||
rl.input.removeAllListeners('keypress') | ||
rl.input.pause() | ||
if (silent) { | ||
output.mute() | ||
} else if (editDef) { | ||
rl.line = def | ||
rl.cursor = def.length | ||
rl._refreshLine() | ||
} | ||
|
||
clearTimeout(timer) | ||
output.mute() | ||
output.end() | ||
} | ||
|
||
function onError (er) { | ||
if (called) { | ||
return | ||
const done = () => { | ||
rl.close() | ||
clearTimeout(timer) | ||
output.mute() | ||
output.end() | ||
} | ||
done() | ||
return cb(er) | ||
} | ||
|
||
function onLine (line) { | ||
if (called) { | ||
return | ||
const onError = (er) => { | ||
done() | ||
reject(er) | ||
} | ||
if (silent && terminal) { | ||
output.unmute() | ||
output.write('\r\n') | ||
} | ||
done() | ||
// truncate the \n at the end. | ||
line = line.replace(/\r?\n$/, '') | ||
var isDefault = !!(editDef && line === def) | ||
if (def && !line) { | ||
isDefault = true | ||
line = def | ||
} | ||
cb(null, line, isDefault) | ||
} | ||
|
||
rl.on('error', onError) | ||
rl.on('line', (line) => { | ||
if (silent && terminal) { | ||
output.unmute() | ||
output.write('\r\n') | ||
} | ||
done() | ||
// truncate the \n at the end. | ||
const res = line.replace(/\r?\n$/, '') || def || '' | ||
return resolve(res) | ||
}) | ||
|
||
rl.on('SIGINT', () => { | ||
rl.close() | ||
onError(new Error('canceled')) | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,54 @@ | ||
var read = require('../lib/read.js') | ||
const t = require('tap') | ||
const read = require('../') | ||
const spawnRead = require('./fixtures/setup') | ||
|
||
if (process.argv[2] === 'child') { | ||
return child() | ||
} | ||
|
||
var tap = require('tap') | ||
var CLOSE = 'close' | ||
if (process.version.match(/^v0\.6/)) { | ||
CLOSE = 'exit' | ||
async function child () { | ||
const user = await read({ prompt: 'Username: ', default: 'test-user' }) | ||
const pass = await read({ prompt: 'Password: ', default: 'test-pass', silent: true }) | ||
const verify = await read({ prompt: 'Password again: ', default: 'test-pass', silent: true }) | ||
|
||
console.error(JSON.stringify({ | ||
user, | ||
pass, | ||
verify, | ||
passMatch: pass === verify, | ||
})) | ||
|
||
if (process.stdin.unref) { | ||
process.stdin.unref() | ||
} | ||
} | ||
|
||
var spawn = require('child_process').spawn | ||
|
||
tap.test('basic', function (t) { | ||
var child = spawn(process.execPath, [__filename, 'child']) | ||
var output = '' | ||
var write = child.stdin.write.bind(child.stdin) | ||
child.stdout.on('data', function (c) { | ||
console.error('data %s', c) | ||
output += c | ||
if (output.match(/Username: \(test-user\) $/)) { | ||
process.nextTick(write.bind(null, 'a user\n')) | ||
} else if (output.match(/Password: \(<default hidden>\) $/)) { | ||
process.nextTick(write.bind(null, 'a password\n')) | ||
} else if (output.match(/Password again: \(<default hidden>\) $/)) { | ||
process.nextTick(write.bind(null, 'a password\n')) | ||
} else { | ||
console.error('prompts done, output=%j', output) | ||
} | ||
t.test('basic', async (t) => { | ||
const { stdout, stderr } = await spawnRead(__filename, { | ||
'Username: (test-user)': 'a user', | ||
'Password: (<default hidden>)': 'a password', | ||
'Password again: (<default hidden>)': 'a password', | ||
}) | ||
|
||
var result = '' | ||
child.stderr.on('data', function (c) { | ||
result += c | ||
console.error('result %j', c.toString()) | ||
}) | ||
t.same(JSON.parse(stderr), | ||
{ user: 'a user', pass: 'a password', verify: 'a password', passMatch: true }) | ||
t.equal(stdout, | ||
'Username: (test-user) Password: (<default hidden>) Password again: (<default hidden>) ') | ||
}) | ||
|
||
child.on(CLOSE, function () { | ||
result = JSON.parse(result) | ||
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() | ||
t.test('defaults', async (t) => { | ||
const { stdout, stderr } = await spawnRead(__filename, { | ||
'Username: (test-user)': '', | ||
'Password: (<default hidden>)': '', | ||
'Password again: (<default hidden>)': '', | ||
}) | ||
|
||
t.same(JSON.parse(stderr), | ||
{ user: 'test-user', pass: 'test-pass', verify: 'test-pass', passMatch: true }) | ||
t.equal(stdout, | ||
'Username: (test-user) Password: (<default hidden>) Password again: (<default hidden>) ') | ||
}) | ||
|
||
function child () { | ||
read({ prompt: 'Username: ', default: 'test-user' }, function (er, user) { | ||
read({ prompt: 'Password: ', default: 'test-pass', silent: true }, function (er, pass) { | ||
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) })) | ||
if (process.stdin.unref) { | ||
process.stdin.unref() | ||
} | ||
}) | ||
}) | ||
}) | ||
} | ||
t.test('errors', async (t) => { | ||
t.rejects(() => read({ default: {} })) | ||
}) |
Oops, something went wrong.