Add placeholder

This commit is contained in:
anseki 2015-04-07 18:38:14 +09:00
parent 008f31fb6d
commit cc0cfa5044
6 changed files with 209 additions and 97 deletions

View file

@ -216,7 +216,7 @@ if (readlineSync.keyIn('Are you sure? :', {limit: 'yn'}) === 'y') { // Accept 'y
Type: Boolean
Default: `false`
By default, the matching is non-case-sensitive when `limit` option (see [limit](#limit) option) is specified (i.e. `a` equals `A`). If `true` is specified, the matching is case-sensitive (i.e. `a` and `A` are different).
By default, the matching is case-insensitive when `limit` option (see [limit](#limit) option) is specified (i.e. `a` equals `A`). If `true` is specified, the matching is case-sensitive (i.e. `a` and `A` are different).
### noTrim

View file

@ -54,15 +54,15 @@ var
})({
display: 'string',
keyIn: 'boolean',
noEchoBack: 'boolean',
hideEchoBack: 'boolean',
mask: 'string',
encoded: 'boolean'
});
if (!options.noEchoBack && !options.keyIn) {
if (!options.hideEchoBack && !options.keyIn) {
if (options.display) { writeTTY(options.display); }
input = readByFSO();
} else if (options.noEchoBack && !options.keyIn && !options.mask) {
} else if (options.hideEchoBack && !options.keyIn && !options.mask) {
if (options.display) { writeTTY(options.display); }
input = readByPW();
} else {

View file

@ -7,7 +7,7 @@
Param(
[string] $display,
[switch] $keyIn,
[switch] $noEchoBack,
[switch] $hideEchoBack,
[string] $mask,
[string] $limit,
[switch] $caseSensitive,
@ -26,7 +26,7 @@ function decodeDOS ($arg) {
}
$options = @{}
foreach ($arg in @('display', 'keyIn', 'noEchoBack', 'mask', 'limit', 'caseSensitive', 'encoded')) {
foreach ($arg in @('display', 'keyIn', 'hideEchoBack', 'mask', 'limit', 'caseSensitive', 'encoded')) {
$options.Add($arg, (Get-Variable $arg -ValueOnly))
}
if ($options.encoded) {
@ -40,8 +40,8 @@ if ($options.encoded) {
[string] $inputTTY = ''
[bool] $silent = -not $options.display -and
$options.keyIn -and $options.noEchoBack -and -not $options.mask
[bool] $isCooked = -not $options.noEchoBack -and -not $options.keyIn
$options.keyIn -and $options.hideEchoBack -and -not $options.mask
[bool] $isCooked = -not $options.hideEchoBack -and -not $options.keyIn
# Instant method that opens TTY without CreateFile via P/Invoke in .NET Framework
# **NOTE** Don't include special characters of DOS in $command when $getRes is True.
@ -66,7 +66,7 @@ if ($options.display) {
writeTTY $options.display
}
if (-not $options.keyIn -and $options.noEchoBack -and $options.mask -eq '*') {
if (-not $options.keyIn -and $options.hideEchoBack -and $options.mask -eq '*') {
$inputTTY = execWithTTY ('$text = Read-Host -AsSecureString;' +
'$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($text);' +
'[Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True
@ -101,7 +101,7 @@ while ($True) {
if ($chunk) {
if (-not $isCooked) {
if (-not $options.noEchoBack) {
if (-not $options.hideEchoBack) {
writeTTY $chunk
} elseif ($options.mask) {
writeTTY ($options.mask * $chunk.Length)

View file

@ -10,10 +10,10 @@ while [ $# -ge 1 ]; do
case "$arg" in
'display') shift; options_display="$1";;
'keyin') options_keyIn=true;;
'noechoback') options_noEchoBack=true;;
'hideechoback') options_hideEchoBack=true;;
'mask') shift; options_mask="$1";;
'limit') shift; options_limit="$1";;
'caseSensitive') options_caseSensitive=true;;
'casesensitive') options_caseSensitive=true;;
'encoded') options_encoded=true;;
esac
shift
@ -47,8 +47,8 @@ replace_allchars() { (
) }
[ -z "$options_display" ] && [ "$options_keyIn" = true ] && \
[ "$options_noEchoBack" = true ] && [ -z "$options_mask" ] && silent=true
[ "$options_noEchoBack" != true ] && [ "$options_keyIn" != true ] && is_cooked=true
[ "$options_hideEchoBack" = true ] && [ -z "$options_mask" ] && silent=true
[ "$options_hideEchoBack" != true ] && [ "$options_keyIn" != true ] && is_cooked=true
if [ -n "$options_display" ]; then
write_tty "$options_display"
@ -102,7 +102,7 @@ do
if [ -n "$chunk" ]; then
if [ "$is_cooked" != true ]; then
if [ "$options_noEchoBack" != true ]; then
if [ "$options_hideEchoBack" != true ]; then
write_tty "$chunk"
elif [ -n "$options_mask" ]; then
write_tty "$(replace_allchars "$chunk" "$options_mask")"

View file

@ -20,18 +20,18 @@ var
childProc = require('child_process'),
defaultOptions = {
prompt: '> ',
noEchoBack: false,
mask: '*',
limit: [],
limitMessage: 'Input another, please.',
caseSensitive: false,
noTrim: false,
encoding: 'utf8',
bufferSize: 1024,
print: void 0,
isTrue: [],
isFalse: []
prompt: '> ',
hideEchoBack: false,
mask: '*',
limit: [],
limitMessage: 'Input another, please.${( [)limit(])}',
caseSensitive: false,
keepWhitespace: false,
encoding: 'utf8',
bufferSize: 1024,
print: void 0,
trueValue: [],
falseValue: []
},
useExt = false,
@ -39,18 +39,19 @@ var
extHostPath, extHostArgs, tempdir, salt = 0;
/*
display: string
keyIn: boolean
noEchoBack: boolean
mask: string
limit: string (pattern)
caseSensitive: boolean
noTrim: boolean
display: string
keyIn: boolean
hideEchoBack: boolean
mask: string
limit: string (pattern)
caseSensitive: boolean
keepWhitespace: boolean
encoding, bufferSize, print
*/
function _readlineSync(options) {
var input = '', displaySave = options.display,
silent = !options.display && options.keyIn && options.noEchoBack && !options.mask;
silent = !options.display &&
options.keyIn && options.hideEchoBack && !options.mask;
function tryExt() {
var res = readlineExt(options);
@ -124,7 +125,7 @@ function _readlineSync(options) {
(function() { // try read
var atEol, limit,
isCooked = !options.noEchoBack && !options.keyIn,
isCooked = !options.hideEchoBack && !options.keyIn,
buffer, reqSize, readSize, chunk, line;
// Node v0.10- returns an error if same mode is set.
@ -182,7 +183,7 @@ function _readlineSync(options) {
if (chunk) {
if (!isCooked) {
if (!options.noEchoBack) {
if (!options.hideEchoBack) {
fs.writeSync(fdW, chunk);
} else if (options.mask) {
fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask));
@ -200,12 +201,12 @@ function _readlineSync(options) {
})();
if (options.print && !silent) { // must at least write '\n'
options.print(displaySave + (options.noEchoBack ?
options.print(displaySave + (options.hideEchoBack ?
(new Array(input.length + 1)).join(options.mask) : input) + '\n',
options.encoding);
}
return (options.noTrim || options.keyIn ? input : input.trim());
return (options.keepWhitespace || options.keyIn ? input : input.trim());
}
function readlineExt(options) {
@ -381,7 +382,7 @@ function getHostArgs(options) {
})({
display: 'string',
keyIn: 'boolean',
noEchoBack: 'boolean',
hideEchoBack: 'boolean',
mask: 'string',
limit: 'string',
caseSensitive: 'boolean',
@ -389,6 +390,19 @@ function getHostArgs(options) {
}));
}
function flattenArray(array, validator) {
var flatArray = [];
function parseArray(array) {
/* jshint eqnull:true */
if (array == null) { return; }
/* jshint eqnull:false */
else if (Array.isArray(array)) { array.forEach(parseArray); }
else if (!validator || validator(array)) { flatArray.push(array); }
}
parseArray(array);
return flatArray;
}
// margeOptions(options1, options2 ... )
// margeOptions(true, options1, options2 ... ) // from defaultOptions
function margeOptions() {
@ -411,47 +425,57 @@ function margeOptions() {
if (!fromDefault) { optionNames = Object.keys(optionsPart); }
optionNames.forEach(function(optionName) {
var value;
// ======== DEPRECATED ========
if (optionName === 'noEchoBack') { optionName = 'hideEchoBack'; }
else if (optionName === 'noTrim') { optionName = 'keepWhitespace'; }
// ======== /DEPRECATED ========
if (!optionsPart.hasOwnProperty(optionName)) { return; }
value = optionsPart[optionName];
switch (optionName) {
// _readlineSync defaultOptions
// string
// ================ string
case 'mask': // * *
case 'encoding': // * *
case 'limitMessage': // *
/* jshint eqnull:true */
options[optionName] = value != null ? value + '' : '';
value = value != null ? value + '' : '';
/* jshint eqnull:false */
if (value && optionName === 'mask' || optionName === 'encoding')
{ value = value.replace(/[\r\n]/g, ''); }
options[optionName] = value;
break;
// number
// ================ number
case 'bufferSize': // * *
if (!isNaN(value = parseInt(value, 10)) && typeof value === 'number')
{ options[optionName] = value; }
break;
// boolean
case 'noEchoBack': // * *
// ================ boolean
case 'hideEchoBack': // * *
case 'caseSensitive': // * *
case 'noTrim': // * *
case 'keepWhitespace': // * *
case 'keyIn': // *
options[optionName] = !!value;
break;
// function
// ================ function
case 'print': // * *
options[optionName] = typeof value === 'function' ? value : void 0;
break;
// array
// ================ array
case 'limit': // * * readlineExt
case 'isTrue': // *
case 'isFalse': // *
case 'trueValue': // *
case 'falseValue': // *
/* jshint eqnull:true */
options[optionName] = value != null ?
value = value != null ?
flattenArray(value, function(value) {
return typeof value === 'string' || typeof value === 'number' ||
value instanceof RegExp;
}) : [];
/* jshint eqnull:false */
options[optionName] = value.map(function(value) {
return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value;
});
break;
// other
// ================ other
case 'prompt': // *
case 'display': // * readlineExt
/* jshint eqnull:true */
@ -464,19 +488,6 @@ function margeOptions() {
}, {});
}
function flattenArray(array, validator) {
var flatArray = [];
function parseArray(array) {
/* jshint eqnull:true */
if (array == null) { return; }
/* jshint eqnull:false */
else if (Array.isArray(array)) { array.forEach(parseArray); }
else if (!validator || validator(array)) { flatArray.push(array); }
}
parseArray(array);
return flatArray;
}
function isMatched(res, comps, caseSensitive) {
return comps.some(function(comp) {
if (typeof comp === 'number') { comp += ''; }
@ -488,31 +499,119 @@ function isMatched(res, comps, caseSensitive) {
});
}
function replacePlaceholder(text, generator) {
return text.replace(/(\$)?(\$\{(?:\(([\s\S]*?)\))?(\w+|.-.)(?:\(([\s\S]*?)\))?\})/g,
function(str, escape, placeholder, pre, param, post) {
var text;
return escape || typeof(text = generator(param)) !== 'string' ? placeholder :
text ? (pre || '') + text + (post || '') : '';
});
}
function placeholderInMessage(param, options) {
var text, values, group = [], groupClass = -1, charCode = 0, suppressed;
switch (param) {
case 'hideEchoBack':
case 'mask':
case 'caseSensitive':
case 'keepWhitespace':
case 'encoding':
case 'bufferSize':
case 'input':
text = options.hasOwnProperty(param) ? options[param] + '' : '';
break;
case 'prompt':
case 'query':
case 'display':
text = options.displaySrc + '';
break;
case 'limit':
case 'trueValue':
case 'falseValue':
values = options[options.hasOwnProperty(param + 'Src') ? param + 'Src' : param];
if (options.keyIn) { // suppress
values = values.reduce(function(chars, value) {
return chars.concat((value + '').split(''));
}, [])
.reduce(function(groups, curChar) {
var curGroupClass, curCharCode;
if (!options.caseSensitive) { curChar = curChar.toUpperCase(); }
curGroupClass = /^\d$/.test(curChar) ? 1 :
/^[A-Z]$/.test(curChar) ? 2 : /^[a-z]$/.test(curChar) ? 3 : 0;
curCharCode = curChar.charCodeAt(0);
if (curGroupClass && curGroupClass === groupClass &&
curCharCode === charCode + 1) {
group.push(curChar);
} else {
if (group.length > 3) { // ellipsis
groups.push(group[0] + ' ... ' + group[group.length - 1]);
suppressed = true;
} else {
groups = groups.concat(group);
}
group = [curChar];
groupClass = curGroupClass;
}
charCode = curCharCode;
return groups;
}, []);
if (group.length > 3) { // ellipsis
values.push(group[0] + ' ... ' + group[group.length - 1]);
suppressed = true;
} else {
values = values.concat(group);
}
} else {
values = values.filter(function(value)
{ return typeof value === 'string' || typeof value === 'number'; });
}
text = values.join(values.length > 2 ? ', ' : suppressed ? ' / ' : '/');
break;
case 'limitCount':
case 'limitCountNoZero':
text = options[options.hasOwnProperty('limitSrc') ? 'limitSrc' : 'limit'].length;
text = (text ? text : param === 'limitCountNoZero' ? '' : text) + '';
break;
}
return text;
}
function readlineWithOptions(options) {
var res, limitSave = options.limit, displaySave = options.display;
options.display += '';
var res,
generator = function(param) { return placeholderInMessage(param, options); };
options.limitSrc = options.limit;
options.displaySrc = options.display;
options.limit = ''; // for readlineExt
options.display = replacePlaceholder(options.display + '', generator);
while (true) {
res = _readlineSync(options);
if (!limitSave.length ||
isMatched(res, limitSave, options.caseSensitive)) { break; }
if (!options.limitSrc.length ||
isMatched(res, options.limitSrc, options.caseSensitive)) { break; }
options.input = res; // for placeholder
options.display += (options.display ? '\n' : '') +
(options.limitMessage ? options.limitMessage + '\n' : '') + displaySave;
(options.limitMessage ?
replacePlaceholder(options.limitMessage, generator) + '\n' : '') +
replacePlaceholder(options.displaySrc + '', generator);
}
return res;
}
function toBool(res, options) {
return (
(options.isTrue.length &&
isMatched(res, options.isTrue, options.caseSensitive)) ? true :
(options.isFalse.length &&
isMatched(res, options.isFalse, options.caseSensitive)) ? false : res);
(options.trueValue.length &&
isMatched(res, options.trueValue, options.caseSensitive)) ? true :
(options.falseValue.length &&
isMatched(res, options.falseValue, options.caseSensitive)) ? false : res);
}
// for dev
exports._useExtSet = function(use) { useExt = use; };
exports.setDefault = function(options) {
defaultOptions = margeOptions(true, options);
return margeOptions(true); // copy
};
exports.prompt = function(options) {
var readOptions = margeOptions(true, options), res;
readOptions.display = readOptions.prompt;
@ -522,7 +621,7 @@ exports.prompt = function(options) {
exports.question = function(query, options) {
var readOptions = margeOptions(margeOptions(true, options), {
display: query
display: query
}),
res = readlineWithOptions(readOptions);
return toBool(res, readOptions);
@ -530,17 +629,30 @@ exports.question = function(query, options) {
exports.keyIn = function(query, options) {
var readOptions = margeOptions(margeOptions(true, options), {
display: query,
keyIn: true,
noTrim: true
display: query,
keyIn: true,
keepWhitespace: true
}), res;
readOptions.display += '';
readOptions.limit = readOptions.limit.filter(function(value)
{ return typeof value === 'string' || typeof value === 'number'; })
.join('').replace(/\n/g, '').replace(/[^A-Za-z0-9_ ]/g, '\\$&');
res = _readlineSync(readOptions);
['isTrue', 'isFalse'].forEach(function(optionName) {
// char list
readOptions.limitSrc = readOptions.limit.filter(function(value)
{ return typeof value === 'string' || typeof value === 'number'; })
.map(function(text) { // placeholders
return replacePlaceholder(text + '', function(param) { // char list
var matches = /^(.)-(.)$/.exec(param), text = '', from, to, code, step;
if (!matches) { return; }
from = matches[1].charCodeAt(0);
to = matches[2].charCodeAt(0);
step = from < to ? 1 : -1;
for (code = from; code !== to + step; code += step)
{ text += String.fromCharCode(code); }
return text;
});
});
// pattern
readOptions.limit = readOptions.limitSrc.join('').replace(/[^A-Za-z0-9_ ]/g, '\\$&');
['trueValue', 'falseValue'].forEach(function(optionName) {
var comps = [];
readOptions[optionName].forEach(function(comp) {
if (typeof comp === 'string' || typeof comp === 'number') {
@ -552,22 +664,22 @@ exports.keyIn = function(query, options) {
readOptions[optionName] = comps;
});
readOptions.display = replacePlaceholder(readOptions.display + '',
function(param) { return placeholderInMessage(param, readOptions); });
res = _readlineSync(readOptions);
return toBool(res, readOptions);
};
exports.setDefault = function(options) {
defaultOptions = margeOptions(true, options);
return margeOptions(true); // copy
};
// ======== These APIs are now obsolete. ========
function setOption(optionName, args) {
// ======== DEPRECATED ========
function _setOption(optionName, args) {
var options;
if (args.length) { options = {}; options[optionName] = args[0]; }
return exports.setDefault(options)[optionName];
}
exports.setPrint = function() { return setOption('print', arguments); };
exports.setPrompt = function() { return setOption('prompt', arguments); };
exports.setEncoding = function() { return setOption('encoding', arguments); };
exports.setMask = function() { return setOption('mask', arguments); };
exports.setBufferSize = function() { return setOption('bufferSize', arguments); };
exports.setPrint = function() { return _setOption('print', arguments); };
exports.setPrompt = function() { return _setOption('prompt', arguments); };
exports.setEncoding = function() { return _setOption('encoding', arguments); };
exports.setMask = function() { return _setOption('mask', arguments); };
exports.setBufferSize = function() { return _setOption('bufferSize', arguments); };

View file

@ -1,6 +1,6 @@
{
"name": "readline-sync",
"version": "0.12.0",
"version": "0.13.0",
"title": "readlineSync",
"description": "Synchronous Readline for interactively running to have a conversation with the user via a console(TTY).",
"keywords": [