From 63e5ad583e28b265988dc38694238dd2e4a35637 Mon Sep 17 00:00:00 2001 From: anseki Date: Sat, 4 Apr 2015 01:14:55 +0900 Subject: [PATCH] Add: `options.limit`, `options.caseSensitive` --- lib/read.cs.js | 6 +-- lib/read.ps1 | 94 ++++++++++++++++++++++++-------------------- lib/read.sh | 89 +++++++++++++++++++++++++---------------- lib/readline-sync.js | 47 +++++++++++----------- 4 files changed, 133 insertions(+), 103 deletions(-) diff --git a/lib/read.cs.js b/lib/read.cs.js index bc6f379..0669e16 100644 --- a/lib/read.cs.js +++ b/lib/read.cs.js @@ -60,10 +60,10 @@ var }); if (!options.noEchoBack && !options.keyIn) { - if (options.display !== '') { writeTTY(options.display); } + if (options.display) { writeTTY(options.display); } input = readByFSO(); -} else if (options.noEchoBack && !options.keyIn && options.mask === '') { - if (options.display !== '') { writeTTY(options.display); } +} else if (options.noEchoBack && !options.keyIn && !options.mask) { + if (options.display) { writeTTY(options.display); } input = readByPW(); } else { WScript.StdErr.WriteLine(PS_MSG); diff --git a/lib/read.ps1 b/lib/read.ps1 index 3b80d8f..7f3631a 100644 --- a/lib/read.ps1 +++ b/lib/read.ps1 @@ -9,7 +9,7 @@ Param( [switch] $keyIn, [switch] $noEchoBack, [string] $mask, - [string] $exclude, + [string] $limit, [switch] $cs, [switch] $encoded ) @@ -26,23 +26,22 @@ function decodeDOS ($arg) { } $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)) } if ($options.encoded) { $argList = New-Object string[] $options.Keys.Count - $options.Keys.CopyTo($argList, 0); + $options.Keys.CopyTo($argList, 0) 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] } } } [string] $inputTTY = '' -[string] $displaySave = $options.display -[bool] $silent = (-not $options.display) -and - $options.keyIn -and $options.noEchoBack -and (-not $options.mask) -[bool] $isCooked = (-not $options.noEchoBack) -and (-not $options.keyIn) +[bool] $silent = -not $options.display -and + $options.keyIn -and $options.noEchoBack -and -not $options.mask +[bool] $isCooked = -not $options.noEchoBack -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. @@ -63,49 +62,58 @@ function writeTTY ($text) { execWithTTY ('Write-Host ''' + ($text -replace '''', '''''') + ''' -NoNewline') } -if ($options.display -ne '') { +if ($options.display) { writeTTY $options.display - $options.display = '' } -if ((-not $options.keyIn) -and $options.noEchoBack -and ($options.mask -eq '*')) { - $inputTTY = execWithTTY ('$inputTTY = Read-Host -AsSecureString;' + - '$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($inputTTY);' + - '[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True +if (-not $options.keyIn -and $options.noEchoBack -and $options.mask -eq '*') { + $inputTTY = execWithTTY ('$text = Read-Host -AsSecureString;' + + '$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($text);' + + '[Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True return '''' + $inputTTY + '''' } if ($options.keyIn) { $reqSize = 1 } -while ($True) { - if (-not $isCooked) { - $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 - $chunk = $chunk -replace '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '' - - if ($chunk -ne '' -and (-not $isCooked)) { - if (-not $options.noEchoBack) { - writeTTY $chunk - } elseif ($options.mask -ne '') { - writeTTY ($options.mask * $chunk.Length) - } - } - - $inputTTY += $chunk - if ($atEol -or ($options.keyIn -and ($inputTTY.Length -ge $reqSize))) { break } +if ($options.keyIn -and $options.limit) { + $limitPtn = '[^' + $options.limit + ']' } -if ((-not $isCooked) -and (-not ($options.keyIn -and (-not $isInputLine)))) - { execWithTTY 'Write-Host ''''' } # new line +while ($True) { + if (-not $isCooked) { + $chunk = [char][int] (execWithTTY '[int] [Console]::ReadKey($True).KeyChar' $True) + } else { + $chunk = execWithTTY 'Read-Host' $True + $chunk += "`n" + } -return '''' + $inputTTY + '''' + 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) { + writeTTY $chunk + } elseif ($options.mask) { + writeTTY ($options.mask * $chunk.Length) + } + } + $inputTTY += $chunk + } + + if ((-not $options.keyIn -and $atEol) -or + ($options.keyIn -and $inputTTY.Length -ge $reqSize)) { break } +} + +if (-not $isCooked -and -not $silent) { execWithTTY 'Write-Host ''''' } # new line + +return "'$inputTTY'" diff --git a/lib/read.sh b/lib/read.sh index 2ecc442..20f6929 100644 --- a/lib/read.sh +++ b/lib/read.sh @@ -9,29 +9,32 @@ while [ $# -ge 1 ]; do arg="$(printf '%s' "$1" | grep -E '^-+[^-]+$' | tr '[A-Z]' '[a-z]' | tr -d '-')" case "$arg" in 'display') shift; options_display="$1";; - 'keyin') options_keyIn='true';; - 'noechoback') options_noEchoBack='true';; + 'keyin') options_keyIn=true;; + 'noechoback') options_noEchoBack=true;; 'mask') shift; options_mask="$1";; - 'exclude') shift; options_exclude="$1";; - 'cs') options_cs='true';; - 'encoded') options_encoded='true';; + 'limit') shift; options_limit="$1";; + 'cs') options_cs=true;; + 'encoded') options_encoded=true;; esac shift done 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 -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 - if [ -n "$2" ]; then + if [ "$2" = true ]; then printf '%b' "$1" >/dev/tty else printf '%s' "$1" >/dev/tty fi - is_inputline='true' } replace_allchars() { ( @@ -43,14 +46,15 @@ replace_allchars() { ( 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 write_tty "$options_display" - options_display='' fi -if [ -n "$is_cooked" ]; then +if [ "$is_cooked" = true ]; then stty --file=/dev/tty cooked 2>/dev/null || \ stty -F /dev/tty cooked 2>/dev/null || \ stty -f /dev/tty cooked || exit $? @@ -60,41 +64,58 @@ else stty -f /dev/tty raw -echo || exit $? 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 : do - if [ -z "$is_cooked" ]; then + if [ "$is_cooked" != true ]; then 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 IFS= read -r chunk 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; atEol = true; } - chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); // other ctrl-chars - if (chunk && exclude) { chunk = chunk.replace(exclude, ''); } + // other ctrl-chars + if (chunk) { chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); } + if (chunk && limit) { chunk = chunk.replace(limit, ''); } - if (chunk && !isCooked) { - if (!options.noEchoBack) { - fs.writeSync(fdW, chunk); - } else if (options.mask) { - fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask)); + if (chunk) { + if (!isCooked) { + if (!options.noEchoBack) { + fs.writeSync(fdW, chunk); + } else if (options.mask) { + fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask)); + } } + input += chunk; } - input += chunk; if (!options.keyIn && atEol || options.keyIn && input.length >= reqSize) { break; } } @@ -218,7 +221,6 @@ function readlineExt(options) { if (childProc.execFileSync) { hostArgs = getHostArgs(options); - console.warn(''); try { res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions); } catch (e) { // non-zero exit code @@ -232,11 +234,9 @@ function readlineExt(options) { res.error.signal = e.signal; } } else { - console.warn('<_execFileSync>'); res = _execFileSync(options, execOptions); } if (!res.error) { - console.warn('ROW-RES:<'+res.input+'>'); res.input = res.input.replace(/^\s*'|'\s*$/g, ''); options.display = ''; } @@ -368,7 +368,7 @@ function getHostArgs(options) { keyIn: 'boolean', noEchoBack: 'boolean', mask: 'string', - exclude: 'string', + limit: 'string', cs: 'boolean', encoded: 'boolean' })); @@ -429,7 +429,7 @@ exports.prompt = function(options) { keyIn: false, noEchoBack: !!(options && options.noEchoBack), mask: mask, - exclude: '', + limit: '', cs: !!(options && options.caseSensitive), noTrim: !!(options && options.noTrim) }; @@ -444,7 +444,7 @@ exports.question = function(query, options) { keyIn: false, noEchoBack: !!(options && options.noEchoBack), mask: mask, - exclude: '', + limit: '', cs: !!(options && options.caseSensitive), noTrim: !!(options && options.noTrim) }; @@ -452,8 +452,10 @@ exports.question = function(query, options) { }; exports.keyIn = function(query, options) { - var limit = options ? flattenArray(options.limit, function(value) { - return typeof value === 'string' || typeof value === 'number'; }).join('') : '', + var limit = options ? + flattenArray(options.limit, function(value) + { return typeof value === 'string' || typeof value === 'number'; }) + .join('').replace(/\n/g, '').replace(/[^A-Za-z0-9_ ]/g, '\\$&') : '', readOptions = { /* jshint eqnull:true */ display: query != null ? query + '' : '', @@ -461,8 +463,7 @@ exports.keyIn = function(query, options) { keyIn: true, noEchoBack: !!(options && options.noEchoBack), mask: mask, - exclude: limit ? '[^' + - limit.replace(/\n/g, '').replace(/\W/g, '\\$&') + ']' : '', + limit: limit, cs: !!(options && options.caseSensitive), noTrim: true };