Add: options.limit, options.caseSensitive

This commit is contained in:
anseki 2015-04-04 01:14:55 +09:00
parent b4bdf19667
commit 63e5ad583e
4 changed files with 133 additions and 103 deletions

View file

@ -60,10 +60,10 @@ var
}); });
if (!options.noEchoBack && !options.keyIn) { if (!options.noEchoBack && !options.keyIn) {
if (options.display !== '') { writeTTY(options.display); } if (options.display) { writeTTY(options.display); }
input = readByFSO(); input = readByFSO();
} else if (options.noEchoBack && !options.keyIn && options.mask === '') { } else if (options.noEchoBack && !options.keyIn && !options.mask) {
if (options.display !== '') { writeTTY(options.display); } if (options.display) { writeTTY(options.display); }
input = readByPW(); input = readByPW();
} else { } else {
WScript.StdErr.WriteLine(PS_MSG); WScript.StdErr.WriteLine(PS_MSG);

View file

@ -9,7 +9,7 @@ Param(
[switch] $keyIn, [switch] $keyIn,
[switch] $noEchoBack, [switch] $noEchoBack,
[string] $mask, [string] $mask,
[string] $exclude, [string] $limit,
[switch] $cs, [switch] $cs,
[switch] $encoded [switch] $encoded
) )
@ -26,23 +26,22 @@ function decodeDOS ($arg) {
} }
$options = @{} $options = @{}
foreach ($arg in @('display', 'keyIn', 'noEchoBack', 'mask', 'exclude', 'cs', 'encoded')) { foreach ($arg in @('display', 'keyIn', 'noEchoBack', 'mask', 'limit', 'cs', 'encoded')) {
$options.Add($arg, (Get-Variable $arg -ValueOnly)) $options.Add($arg, (Get-Variable $arg -ValueOnly))
} }
if ($options.encoded) { if ($options.encoded) {
$argList = New-Object string[] $options.Keys.Count $argList = New-Object string[] $options.Keys.Count
$options.Keys.CopyTo($argList, 0); $options.Keys.CopyTo($argList, 0)
foreach ($arg in $argList) { foreach ($arg in $argList) {
if (($options[$arg] -is [string]) -and ($options[$arg] -ne '')) if ($options[$arg] -is [string] -and $options[$arg])
{ $options[$arg] = decodeDOS $options[$arg] } { $options[$arg] = decodeDOS $options[$arg] }
} }
} }
[string] $inputTTY = '' [string] $inputTTY = ''
[string] $displaySave = $options.display [bool] $silent = -not $options.display -and
[bool] $silent = (-not $options.display) -and $options.keyIn -and $options.noEchoBack -and -not $options.mask
$options.keyIn -and $options.noEchoBack -and (-not $options.mask) [bool] $isCooked = -not $options.noEchoBack -and -not $options.keyIn
[bool] $isCooked = (-not $options.noEchoBack) -and (-not $options.keyIn)
# Instant method that opens TTY without CreateFile via P/Invoke in .NET Framework # 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. # **NOTE** Don't include special characters of DOS in $command when $getRes is True.
@ -63,49 +62,58 @@ function writeTTY ($text) {
execWithTTY ('Write-Host ''' + ($text -replace '''', '''''') + ''' -NoNewline') execWithTTY ('Write-Host ''' + ($text -replace '''', '''''') + ''' -NoNewline')
} }
if ($options.display -ne '') { if ($options.display) {
writeTTY $options.display writeTTY $options.display
$options.display = ''
} }
if ((-not $options.keyIn) -and $options.noEchoBack -and ($options.mask -eq '*')) { if (-not $options.keyIn -and $options.noEchoBack -and $options.mask -eq '*') {
$inputTTY = execWithTTY ('$inputTTY = Read-Host -AsSecureString;' + $inputTTY = execWithTTY ('$text = Read-Host -AsSecureString;' +
'$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($inputTTY);' + '$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($text);' +
'[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True '[Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True
return '''' + $inputTTY + '''' return '''' + $inputTTY + ''''
} }
if ($options.keyIn) { $reqSize = 1 } if ($options.keyIn) { $reqSize = 1 }
while ($True) { if ($options.keyIn -and $options.limit) {
if (-not $isCooked) { $limitPtn = '[^' + $options.limit + ']'
$chunk = execWithTTY '[System.Console]::ReadKey($True).KeyChar' $True
# ReadKey() may returns [System.Array], then don't cast data.
if ($chunk -isnot [string]) { $chunk = '' }
$chunk = $chunk -replace '[\r\n]', ''
if ($chunk -eq '') { $atEol = $True } # NL or empty-text was input
} else {
$chunk = execWithTTY 'Read-Host' $True
$chunk = $chunk -replace '[\r\n]', ''
$atEol = $True
} }
# other ctrl-chars while ($True) {
$chunk = $chunk -replace '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '' if (-not $isCooked) {
$chunk = [char][int] (execWithTTY '[int] [Console]::ReadKey($True).KeyChar' $True)
} else {
$chunk = execWithTTY 'Read-Host' $True
$chunk += "`n"
}
if ($chunk -ne '' -and (-not $isCooked)) { if ($chunk -and $chunk -match '^(.*?)[\r\n]') {
$chunk = $Matches[1]
$atEol = $True
} else { $atEol = $False }
# other ctrl-chars
if ($chunk) { $chunk = $chunk -replace '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '' }
if ($chunk -and $limitPtn) {
if ($options.cs) { $chunk = $chunk -creplace $limitPtn, '' }
else { $chunk = $chunk -ireplace $limitPtn, '' }
}
if ($chunk) {
if (-not $isCooked) {
if (-not $options.noEchoBack) { if (-not $options.noEchoBack) {
writeTTY $chunk writeTTY $chunk
} elseif ($options.mask -ne '') { } elseif ($options.mask) {
writeTTY ($options.mask * $chunk.Length) writeTTY ($options.mask * $chunk.Length)
} }
} }
$inputTTY += $chunk $inputTTY += $chunk
if ($atEol -or ($options.keyIn -and ($inputTTY.Length -ge $reqSize))) { break }
} }
if ((-not $isCooked) -and (-not ($options.keyIn -and (-not $isInputLine)))) if ((-not $options.keyIn -and $atEol) -or
{ execWithTTY 'Write-Host ''''' } # new line ($options.keyIn -and $inputTTY.Length -ge $reqSize)) { break }
}
return '''' + $inputTTY + '''' if (-not $isCooked -and -not $silent) { execWithTTY 'Write-Host ''''' } # new line
return "'$inputTTY'"

View file

@ -9,29 +9,32 @@ while [ $# -ge 1 ]; do
arg="$(printf '%s' "$1" | grep -E '^-+[^-]+$' | tr '[A-Z]' '[a-z]' | tr -d '-')" arg="$(printf '%s' "$1" | grep -E '^-+[^-]+$' | tr '[A-Z]' '[a-z]' | tr -d '-')"
case "$arg" in case "$arg" in
'display') shift; options_display="$1";; 'display') shift; options_display="$1";;
'keyin') options_keyIn='true';; 'keyin') options_keyIn=true;;
'noechoback') options_noEchoBack='true';; 'noechoback') options_noEchoBack=true;;
'mask') shift; options_mask="$1";; 'mask') shift; options_mask="$1";;
'exclude') shift; options_exclude="$1";; 'limit') shift; options_limit="$1";;
'cs') options_cs='true';; 'cs') options_cs=true;;
'encoded') options_encoded='true';; 'encoded') options_encoded=true;;
esac esac
shift shift
done done
reset_tty() { reset_tty() {
if [ -n "$save_tty" ]; then stty "$save_tty"; fi if [ -n "$save_tty" ]; then
stty --file=/dev/tty "$save_tty" 2>/dev/null || \
stty -F /dev/tty "$save_tty" 2>/dev/null || \
stty -f /dev/tty "$save_tty" || exit $?
fi
} }
trap 'reset_tty' EXIT trap 'reset_tty' EXIT
save_tty="$(stty -g)" save_tty="$(stty --file=/dev/tty -g 2>/dev/null || stty -F /dev/tty -g 2>/dev/null || stty -f /dev/tty -g || exit $?)"
write_tty() { # 2nd arg: enable escape sequence write_tty() { # 2nd arg: enable escape sequence
if [ -n "$2" ]; then if [ "$2" = true ]; then
printf '%b' "$1" >/dev/tty printf '%b' "$1" >/dev/tty
else else
printf '%s' "$1" >/dev/tty printf '%s' "$1" >/dev/tty
fi fi
is_inputline='true'
} }
replace_allchars() { ( replace_allchars() { (
@ -43,14 +46,15 @@ replace_allchars() { (
printf '%s' "$text" printf '%s' "$text"
) } ) }
[ -z "$options_noEchoBack" ] && [ -z "$options_keyIn" ] && is_cooked='true' [ -z "$options_display" ] && [ "$options_keyIn" = true ] && \
[ "$options_noEchoBack" = true ] && [ -z "$options_mask" ] && silent=true
[ "$options_noEchoBack" != true ] && [ "$options_keyIn" != true ] && is_cooked=true
if [ -n "$options_display" ]; then if [ -n "$options_display" ]; then
write_tty "$options_display" write_tty "$options_display"
options_display=''
fi fi
if [ -n "$is_cooked" ]; then if [ "$is_cooked" = true ]; then
stty --file=/dev/tty cooked 2>/dev/null || \ stty --file=/dev/tty cooked 2>/dev/null || \
stty -F /dev/tty cooked 2>/dev/null || \ stty -F /dev/tty cooked 2>/dev/null || \
stty -f /dev/tty cooked || exit $? stty -f /dev/tty cooked || exit $?
@ -60,41 +64,58 @@ else
stty -f /dev/tty raw -echo || exit $? stty -f /dev/tty raw -echo || exit $?
fi fi
[ -n "$options_keyIn" ] && req_size=1 [ "$options_keyIn" = true ] && req_size=1
if [ "$options_keyIn" = true ] && [ -n "$options_limit" ]; then
if [ "$options_cs" = true ]; then
limit_ptn="$options_limit"
else
# Safe list
limit_ptn="$(printf '%s' "$options_limit" | sed 's/\([a-z]\)/\L\1\U\1/ig')"
fi
fi
while : while :
do do
if [ -z "$is_cooked" ]; then if [ "$is_cooked" != true ]; then
chunk="$(dd if=/dev/tty bs=1 count=1 2>/dev/null)" chunk="$(dd if=/dev/tty bs=1 count=1 2>/dev/null)"
chunk="$(printf '%s' "$chunk" | tr -d '\r\n')"
[ -z "$chunk" ] && at_eol='true' # NL or empty-text was input
else else
IFS= read -r chunk </dev/tty || exit $? IFS= read -r chunk </dev/tty || exit $?
chunk="$(printf '%s' "$chunk" | tr -d '\r\n')" chunk="$(printf '%s\n_NL_' "$chunk")"
at_eol='true' fi
# Don't assign '\n' to chunk.
if [ -n "$chunk" ] && [ "$(printf '%s' "$chunk" | tr '\r' '\n' | wc -l)" != "0" ]; then
chunk="$(printf '%s' "$chunk" | tr '\r' '\n' | head -n 1)"
at_eol=true
fi fi
# other ctrl-chars # other ctrl-chars
if [ -n "$chunk" ]; then
# chunk="$(printf '%s' "$chunk" | tr -d '\00-\10\13\14\16-\37\177')" # chunk="$(printf '%s' "$chunk" | tr -d '\00-\10\13\14\16-\37\177')"
# for System V # for System V
chunk="$(printf '%s' "$chunk" | tr -d '\00\01\02\03\04\05\06\07\10\13\14\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177')" chunk="$(printf '%s' "$chunk" | tr -d '\00\01\02\03\04\05\06\07\10\13\14\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177')"
fi
if [ -n "$chunk" ] && [ -n "$limit_ptn" ]; then
chunk="$(printf '%s' "$chunk" | tr -cd "$limit_ptn")"
fi
if [ -n "$chunk" ] && [ -z "$is_cooked" ]; then if [ -n "$chunk" ]; then
if [ -z "$options_noEchoBack" ]; then if [ "$is_cooked" != true ]; then
if [ "$options_noEchoBack" != true ]; then
write_tty "$chunk" write_tty "$chunk"
elif [ -n "$options_mask" ]; then elif [ -n "$options_mask" ]; then
write_tty "$(replace_allchars "$chunk" "$options_mask")" write_tty "$(replace_allchars "$chunk" "$options_mask")"
fi fi
fi fi
input="$input$chunk" input="$input$chunk"
if [ -n "$at_eol" ] || \ fi
( [ -n "$options_keyIn" ] && [ ${#input} -ge $req_size ] ); then break; fi
if ( [ "$options_keyIn" != true ] && [ "$at_eol" = true ] ) || \
( [ "$options_keyIn" = true ] && [ ${#input} -ge $req_size ] ); then break; fi
done done
if [ -z "$is_cooked" ] && ! ( [ -n "$options_keyIn" ] && [ -z "$is_inputline" ] ); then if [ "$is_cooked" != true ] && [ "$silent" != true ]; then write_tty '\r\n' true; fi
write_tty '\r\n' 'true'
fi
printf "'%s'" "$input" printf "'%s'" "$input"

View file

@ -34,7 +34,7 @@ var
keyIn: boolean keyIn: boolean
noEchoBack: boolean noEchoBack: boolean
mask: string mask: string
exclude: string (pattern, not RegExp) limit: string (pattern)
cs: boolean cs: boolean
noTrim: boolean noTrim: boolean
*/ */
@ -113,7 +113,7 @@ function readlineSync(options) {
})(); })();
(function() { // try read (function() { // try read
var atEol, exclude, var atEol, limit,
isCooked = !options.noEchoBack && !options.keyIn, isCooked = !options.noEchoBack && !options.keyIn,
buffer, reqSize, readSize, chunk, line; buffer, reqSize, readSize, chunk, line;
@ -141,8 +141,8 @@ function readlineSync(options) {
} }
buffer = new Buffer((reqSize = options.keyIn ? 1 : bufSize)); buffer = new Buffer((reqSize = options.keyIn ? 1 : bufSize));
if (options.keyIn && options.exclude) { if (options.keyIn && options.limit) {
exclude = new RegExp(options.exclude, 'g' + (options.cs ? '' : 'i')); limit = new RegExp('[^' + options.limit + ']', 'g' + (options.cs ? '' : 'i'));
} }
while (true) { while (true) {
@ -158,23 +158,26 @@ function readlineSync(options) {
} }
chunk = readSize > 0 ? buffer.toString(encoding, 0, readSize) : '\n'; chunk = readSize > 0 ? buffer.toString(encoding, 0, readSize) : '\n';
if (typeof(line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') { if (chunk && typeof(line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') {
chunk = line; chunk = line;
atEol = true; atEol = true;
} }
chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); // other ctrl-chars // other ctrl-chars
if (chunk && exclude) { chunk = chunk.replace(exclude, ''); } if (chunk) { chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); }
if (chunk && limit) { chunk = chunk.replace(limit, ''); }
if (chunk && !isCooked) { if (chunk) {
if (!isCooked) {
if (!options.noEchoBack) { if (!options.noEchoBack) {
fs.writeSync(fdW, chunk); fs.writeSync(fdW, chunk);
} else if (options.mask) { } else if (options.mask) {
fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask)); fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask));
} }
} }
input += chunk; input += chunk;
}
if (!options.keyIn && atEol || if (!options.keyIn && atEol ||
options.keyIn && input.length >= reqSize) { break; } options.keyIn && input.length >= reqSize) { break; }
} }
@ -218,7 +221,6 @@ function readlineExt(options) {
if (childProc.execFileSync) { if (childProc.execFileSync) {
hostArgs = getHostArgs(options); hostArgs = getHostArgs(options);
console.warn('<childProc.execFileSync>');
try { try {
res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions); res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions);
} catch (e) { // non-zero exit code } catch (e) { // non-zero exit code
@ -232,11 +234,9 @@ function readlineExt(options) {
res.error.signal = e.signal; res.error.signal = e.signal;
} }
} else { } else {
console.warn('<_execFileSync>');
res = _execFileSync(options, execOptions); res = _execFileSync(options, execOptions);
} }
if (!res.error) { if (!res.error) {
console.warn('ROW-RES:<'+res.input+'>');
res.input = res.input.replace(/^\s*'|'\s*$/g, ''); res.input = res.input.replace(/^\s*'|'\s*$/g, '');
options.display = ''; options.display = '';
} }
@ -368,7 +368,7 @@ function getHostArgs(options) {
keyIn: 'boolean', keyIn: 'boolean',
noEchoBack: 'boolean', noEchoBack: 'boolean',
mask: 'string', mask: 'string',
exclude: 'string', limit: 'string',
cs: 'boolean', cs: 'boolean',
encoded: 'boolean' encoded: 'boolean'
})); }));
@ -429,7 +429,7 @@ exports.prompt = function(options) {
keyIn: false, keyIn: false,
noEchoBack: !!(options && options.noEchoBack), noEchoBack: !!(options && options.noEchoBack),
mask: mask, mask: mask,
exclude: '', limit: '',
cs: !!(options && options.caseSensitive), cs: !!(options && options.caseSensitive),
noTrim: !!(options && options.noTrim) noTrim: !!(options && options.noTrim)
}; };
@ -444,7 +444,7 @@ exports.question = function(query, options) {
keyIn: false, keyIn: false,
noEchoBack: !!(options && options.noEchoBack), noEchoBack: !!(options && options.noEchoBack),
mask: mask, mask: mask,
exclude: '', limit: '',
cs: !!(options && options.caseSensitive), cs: !!(options && options.caseSensitive),
noTrim: !!(options && options.noTrim) noTrim: !!(options && options.noTrim)
}; };
@ -452,8 +452,10 @@ exports.question = function(query, options) {
}; };
exports.keyIn = function(query, options) { exports.keyIn = function(query, options) {
var limit = options ? flattenArray(options.limit, function(value) { var limit = options ?
return typeof value === 'string' || typeof value === 'number'; }).join('') : '', flattenArray(options.limit, function(value)
{ return typeof value === 'string' || typeof value === 'number'; })
.join('').replace(/\n/g, '').replace(/[^A-Za-z0-9_ ]/g, '\\$&') : '',
readOptions = { readOptions = {
/* jshint eqnull:true */ /* jshint eqnull:true */
display: query != null ? query + '' : '', display: query != null ? query + '' : '',
@ -461,8 +463,7 @@ exports.keyIn = function(query, options) {
keyIn: true, keyIn: true,
noEchoBack: !!(options && options.noEchoBack), noEchoBack: !!(options && options.noEchoBack),
mask: mask, mask: mask,
exclude: limit ? '[^' + limit: limit,
limit.replace(/\n/g, '').replace(/\W/g, '\\$&') + ']' : '',
cs: !!(options && options.caseSensitive), cs: !!(options && options.caseSensitive),
noTrim: true noTrim: true
}; };