diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..120cb42 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,8 @@ +{ + "extends": "../../../_common/eslintrc.json", + "env": {"node": true}, + "rules": { + "no-underscore-dangle": [2, {"allow": ["_DBG_useExt", "_DBG_checkOptions", "_DBG_checkMethod", "_readlineSync", "_execFileSync", "_handle", "_flattenArray", "_getPhContent", "_DBG_set_useExt", "_DBG_set_checkOptions", "_DBG_set_checkMethod", "_DBG_clearHistory", "_questionNum", "_keyInYN", "_setOption"]}], + "camelcase": 0 + } +} diff --git a/.gitignore b/.gitignore index b785247..5c6e52a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode node_modules npm-debug.log tmp diff --git a/lib/readline-sync.js b/lib/readline-sync.js index 2d49ca6..7da0f1f 100644 --- a/lib/readline-sync.js +++ b/lib/readline-sync.js @@ -21,6 +21,7 @@ var pathUtil = require('path'), defaultOptions = { + /* eslint-disable key-spacing */ prompt: '> ', hideEchoBack: false, mask: '*', @@ -38,6 +39,7 @@ var cd: false, phContent: void 0, preCheck: void 0 + /* eslint-enable key-spacing */ }, fdR = 'none', fdW, ttyR, isRawMode = false, @@ -45,6 +47,188 @@ var lastInput = '', inputHistory = [], _DBG_useExt = false, _DBG_checkOptions = false, _DBG_checkMethod = false; +function getHostArgs(options) { + // Send any text to crazy Windows shell safely. + function encodeArg(arg) { + return arg.replace(/[^\w\u0080-\uFFFF]/g, function(chr) { + return '#' + chr.charCodeAt(0) + ';'; + }); + } + + return extHostArgs.concat((function(conf) { + var args = []; + Object.keys(conf).forEach(function(optionName) { + if (conf[optionName] === 'boolean') { + if (options[optionName]) { args.push('--' + optionName); } + } else if (conf[optionName] === 'string') { + if (options[optionName]) { + args.push('--' + optionName, encodeArg(options[optionName])); + } + } + }); + return args; + })({ + /* eslint-disable key-spacing */ + display: 'string', + displayOnly: 'boolean', + keyIn: 'boolean', + hideEchoBack: 'boolean', + mask: 'string', + limit: 'string', + caseSensitive: 'boolean' + /* eslint-enable key-spacing */ + })); +} + +// piping via files (for Node v0.10-) +function _execFileSync(options, execOptions) { + + function getTempfile(name) { + var filepath, suffix = '', fd; + tempdir = tempdir || require('os').tmpdir(); + + while (true) { + filepath = pathUtil.join(tempdir, name + suffix); + try { + fd = fs.openSync(filepath, 'wx'); + } catch (e) { + if (e.code === 'EEXIST') { + suffix++; + continue; + } else { + throw e; + } + } + fs.closeSync(fd); + break; + } + return filepath; + } + + var hostArgs, shellPath, shellArgs, res = {}, exitCode, extMessage, + pathStdout = getTempfile('readline-sync.stdout'), + pathStderr = getTempfile('readline-sync.stderr'), + pathExit = getTempfile('readline-sync.exit'), + pathDone = getTempfile('readline-sync.done'), + crypto = require('crypto'), shasum, decipher, password; + + shasum = crypto.createHash(ALGORITHM_HASH); + shasum.update('' + process.pid + (salt++) + Math.random()); + password = shasum.digest('hex'); + decipher = crypto.createDecipher(ALGORITHM_CIPHER, password); + + hostArgs = getHostArgs(options); + if (IS_WIN) { + shellPath = process.env.ComSpec || 'cmd.exe'; + process.env.Q = '"'; // The quote (") that isn't escaped. + // `()` for ignore space by echo + shellArgs = ['/V:ON', '/S', '/C', + '(%Q%' + shellPath + '%Q% /V:ON /S /C %Q%' + /* ESLint bug? */ // eslint-disable-line no-path-concat + '%Q%' + extHostPath + '%Q%' + + hostArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') + + ' & (echo !ERRORLEVEL!)>%Q%' + pathExit + '%Q%%Q%) 2>%Q%' + pathStderr + '%Q%' + + ' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' + + ' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' + + ' >%Q%' + pathStdout + '%Q%' + + ' & (echo 1)>%Q%' + pathDone + '%Q%']; + } else { + shellPath = '/bin/sh'; + shellArgs = ['-c', + // Use `()`, not `{}` for `-c` (text param) + '("' + extHostPath + '"' + /* ESLint bug? */ // eslint-disable-line no-path-concat + hostArgs.map(function(arg) { return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') + + '; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' + + ' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' + + ' "' + ALGORITHM_CIPHER + '" "' + password + '"' + + ' >"' + pathStdout + '"' + + '; echo 1 >"' + pathDone + '"']; + } + if (_DBG_checkMethod) { _DBG_checkMethod('_execFileSync', hostArgs); } + try { + childProc.spawn(shellPath, shellArgs, execOptions); + } catch (e) { + res.error = new Error(e.message); + res.error.method = '_execFileSync - spawn'; + res.error.program = shellPath; + res.error.args = shellArgs; + } + + while (fs.readFileSync(pathDone, {encoding: options.encoding}).trim() !== '1') {} // eslint-disable-line no-empty + if ((exitCode = + fs.readFileSync(pathExit, {encoding: options.encoding}).trim()) === '0') { + res.input = + decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}), + 'hex', options.encoding) + + decipher.final(options.encoding); + } else { + extMessage = fs.readFileSync(pathStderr, {encoding: options.encoding}).trim(); + res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); + res.error.method = '_execFileSync'; + res.error.program = shellPath; + res.error.args = shellArgs; + res.error.extMessage = extMessage; + res.error.exitCode = +exitCode; + } + + fs.unlinkSync(pathStdout); + fs.unlinkSync(pathStderr); + fs.unlinkSync(pathExit); + fs.unlinkSync(pathDone); + + return res; +} + +function readlineExt(options) { + var hostArgs, res = {}, extMessage, + execOptions = {env: process.env, encoding: options.encoding}; + + if (!extHostPath) { + if (IS_WIN) { + if (process.env.PSModulePath) { // Windows PowerShell + extHostPath = 'powershell.exe'; + extHostArgs = ['-ExecutionPolicy', 'Bypass', '-File', __dirname + '\\read.ps1']; // eslint-disable-line no-path-concat + } else { // Windows Script Host + extHostPath = 'cscript.exe'; + extHostArgs = ['//nologo', __dirname + '\\read.cs.js']; // eslint-disable-line no-path-concat + } + } else { + extHostPath = '/bin/sh'; + extHostArgs = [__dirname + '/read.sh']; // eslint-disable-line no-path-concat + } + } + if (IS_WIN && !process.env.PSModulePath) { // Windows Script Host + // ScriptPW (Win XP and Server2003) needs TTY stream as STDIN. + // In this case, If STDIN isn't TTY, an error is thrown. + execOptions.stdio = [process.stdin]; + } + + if (childProc.execFileSync) { + hostArgs = getHostArgs(options); + if (_DBG_checkMethod) { _DBG_checkMethod('execFileSync', hostArgs); } + try { + res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions); + } catch (e) { // non-zero exit code + extMessage = e.stderr ? (e.stderr + '').trim() : ''; + res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); + res.error.method = 'execFileSync'; + res.error.program = extHostPath; + res.error.args = hostArgs; + res.error.extMessage = extMessage; + res.error.exitCode = e.status; + res.error.code = e.code; + res.error.signal = e.signal; + } + } else { + res = _execFileSync(options, execOptions); + } + if (!res.error) { + res.input = res.input.replace(/^\s*'|'\s*$/g, ''); + options.display = ''; + } + + return res; +} + /* display: string displayOnly: boolean @@ -89,13 +273,13 @@ function _readlineSync(options) { // Fixed v5.1.0 https://nodejs.org/en/blog/release/v5.1.0/ // Fixed v4.2.4 (LTS) https://nodejs.org/en/blog/release/v4.2.4/ verNum = (function(ver) { // getVerNum - var nums = ver.replace(/^\D+/, '').split('.'); - var verNum = 0; - if ((nums[0] = +nums[0])) { verNum += nums[0] * 10000; } - if ((nums[1] = +nums[1])) { verNum += nums[1] * 100; } - if ((nums[2] = +nums[2])) { verNum += nums[2]; } - return verNum; - })(process.version); + var nums = ver.replace(/^\D+/, '').split('.'); + var verNum = 0; + if ((nums[0] = +nums[0])) { verNum += nums[0] * 10000; } + if ((nums[1] = +nums[1])) { verNum += nums[1] * 100; } + if ((nums[2] = +nums[2])) { verNum += nums[2]; } + return verNum; + })(process.version); if (!(verNum >= 20302 && verNum < 40204 || verNum >= 50000 && verNum < 50100) && process.stdin.isTTY) { process.stdin.pause(); @@ -107,7 +291,7 @@ function _readlineSync(options) { // 'CONIN$' might fail on XP, 2000, 7 (x86). fdR = getFsB().open('CONIN$', constants.O_RDWR, parseInt('0666', 8)); ttyR = new TTY(fdR, true); - } catch (e) {} + } catch (e) { /* ignore */ } } if (process.stdout.isTTY) { @@ -115,11 +299,11 @@ function _readlineSync(options) { } else { try { fdW = fs.openSync('\\\\.\\CON', 'w'); - } catch (e) {} + } catch (e) { /* ignore */ } if (typeof fdW !== 'number') { // Retry try { fdW = getFsB().open('CONOUT$', constants.O_RDWR, parseInt('0666', 8)); - } catch (e) {} + } catch (e) { /* ignore */ } } } @@ -129,13 +313,13 @@ function _readlineSync(options) { try { fdR = fs.openSync('/dev/tty', 'r'); // device file, not process.stdin ttyR = process.stdin._handle; - } catch (e) {} + } catch (e) { /* ignore */ } } else { // Node v0.12 read() fails. try { fdR = fs.openSync('/dev/tty', 'r'); ttyR = new TTY(fdR, false); - } catch (e) {} + } catch (e) { /* ignore */ } } if (process.stdout.isTTY) { @@ -143,7 +327,7 @@ function _readlineSync(options) { } else { try { fdW = fs.openSync('/dev/tty', 'w'); - } catch (e) {} + } catch (e) { /* ignore */ } } } })(); @@ -197,7 +381,7 @@ function _readlineSync(options) { } chunk = readSize > 0 ? buffer.toString(options.encoding, 0, readSize) : '\n'; - if (chunk && typeof(line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') { + if (chunk && typeof (line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') { chunk = line; atEol = true; } @@ -236,195 +420,16 @@ function _readlineSync(options) { (lastInput = options.keepWhitespace || options.keyIn ? input : input.trim()); } -function readlineExt(options) { - var hostArgs, res = {}, extMessage, - execOptions = {env: process.env, encoding: options.encoding}; - - if (!extHostPath) { - if (IS_WIN) { - if (process.env.PSModulePath) { // Windows PowerShell - extHostPath = 'powershell.exe'; - extHostArgs = ['-ExecutionPolicy', 'Bypass', '-File', __dirname + '\\read.ps1']; - } else { // Windows Script Host - extHostPath = 'cscript.exe'; - extHostArgs = ['//nologo', __dirname + '\\read.cs.js']; - } - } else { - extHostPath = '/bin/sh'; - extHostArgs = [__dirname + '/read.sh']; - } - } - if (IS_WIN && !process.env.PSModulePath) { // Windows Script Host - // ScriptPW (Win XP and Server2003) needs TTY stream as STDIN. - // In this case, If STDIN isn't TTY, an error is thrown. - execOptions.stdio = [process.stdin]; - } - - if (childProc.execFileSync) { - hostArgs = getHostArgs(options); - if (_DBG_checkMethod) { _DBG_checkMethod('execFileSync', hostArgs); } - try { - res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions); - } catch (e) { // non-zero exit code - extMessage = e.stderr ? (e.stderr + '').trim() : ''; - res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); - res.error.method = 'execFileSync'; - res.error.program = extHostPath; - res.error.args = hostArgs; - res.error.extMessage = extMessage; - res.error.exitCode = e.status; - res.error.code = e.code; - res.error.signal = e.signal; - } - } else { - res = _execFileSync(options, execOptions); - } - if (!res.error) { - res.input = res.input.replace(/^\s*'|'\s*$/g, ''); - options.display = ''; - } - - return res; -} - -// piping via files (for Node v0.10-) -function _execFileSync(options, execOptions) { - - function getTempfile(name) { - var filepath, suffix = '', fd; - tempdir = tempdir || require('os').tmpdir(); - - while (true) { - filepath = pathUtil.join(tempdir, name + suffix); - try { - fd = fs.openSync(filepath, 'wx'); - } catch (e) { - if (e.code === 'EEXIST') { - suffix++; - continue; - } else { - throw e; - } - } - fs.closeSync(fd); - break; - } - return filepath; - } - - var hostArgs, shellPath, shellArgs, res = {}, exitCode, extMessage, - pathStdout = getTempfile('readline-sync.stdout'), - pathStderr = getTempfile('readline-sync.stderr'), - pathExit = getTempfile('readline-sync.exit'), - pathDone = getTempfile('readline-sync.done'), - crypto = require('crypto'), shasum, decipher, password; - - shasum = crypto.createHash(ALGORITHM_HASH); - shasum.update('' + process.pid + (salt++) + Math.random()); - password = shasum.digest('hex'); - decipher = crypto.createDecipher(ALGORITHM_CIPHER, password); - - hostArgs = getHostArgs(options); - if (IS_WIN) { - shellPath = process.env.ComSpec || 'cmd.exe'; - process.env.Q = '"'; // The quote (") that isn't escaped. - // `()` for ignore space by echo - shellArgs = ['/V:ON', '/S', '/C', - '(%Q%' + shellPath + '%Q% /V:ON /S /C %Q%' + - '%Q%' + extHostPath + '%Q%' + - hostArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') + - ' & (echo !ERRORLEVEL!)>%Q%' + pathExit + '%Q%%Q%) 2>%Q%' + pathStderr + '%Q%' + - ' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' + - ' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' + - ' >%Q%' + pathStdout + '%Q%' + - ' & (echo 1)>%Q%' + pathDone + '%Q%']; - } else { - shellPath = '/bin/sh'; - shellArgs = ['-c', - // Use `()`, not `{}` for `-c` (text param) - '("' + extHostPath + '"' + - hostArgs.map(function(arg) - { return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') + - '; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' + - ' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' + - ' "' + ALGORITHM_CIPHER + '" "' + password + '"' + - ' >"' + pathStdout + '"' + - '; echo 1 >"' + pathDone + '"']; - } - if (_DBG_checkMethod) { _DBG_checkMethod('_execFileSync', hostArgs); } - try { - childProc.spawn(shellPath, shellArgs, execOptions); - } catch (e) { - res.error = new Error(e.message); - res.error.method = '_execFileSync - spawn'; - res.error.program = shellPath; - res.error.args = shellArgs; - } - - while (fs.readFileSync(pathDone, {encoding: options.encoding}).trim() !== '1') {} - if ((exitCode = - fs.readFileSync(pathExit, {encoding: options.encoding}).trim()) === '0') { - res.input = - decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}), - 'hex', options.encoding) + - decipher.final(options.encoding); - } else { - extMessage = fs.readFileSync(pathStderr, {encoding: options.encoding}).trim(); - res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); - res.error.method = '_execFileSync'; - res.error.program = shellPath; - res.error.args = shellArgs; - res.error.extMessage = extMessage; - res.error.exitCode = +exitCode; - } - - fs.unlinkSync(pathStdout); - fs.unlinkSync(pathStderr); - fs.unlinkSync(pathExit); - fs.unlinkSync(pathDone); - - return res; -} - -function getHostArgs(options) { - // Send any text to crazy Windows shell safely. - function encodeArg(arg) { - return arg.replace(/[^\w\u0080-\uFFFF]/g, function(chr) { - return '#' + chr.charCodeAt(0) + ';'; - }); - } - - return extHostArgs.concat((function(conf) { - var args = []; - Object.keys(conf).forEach(function(optionName) { - if (conf[optionName] === 'boolean') { - if (options[optionName]) { args.push('--' + optionName); } - } else if (conf[optionName] === 'string') { - if (options[optionName]) { - args.push('--' + optionName, encodeArg(options[optionName])); - } - } - }); - return args; - })({ - display: 'string', - displayOnly: 'boolean', - keyIn: 'boolean', - hideEchoBack: 'boolean', - mask: 'string', - limit: 'string', - caseSensitive: 'boolean' - })); -} - function flattenArray(array, validator) { var flatArray = []; function _flattenArray(array) { - /* jshint eqnull:true */ - if (array == null) { return; } - /* jshint eqnull:false */ - else if (Array.isArray(array)) { array.forEach(_flattenArray); } - else if (!validator || validator(array)) { flatArray.push(array); } + if (array == null) { // eslint-disable-line eqeqeq + return; + } else if (Array.isArray(array)) { + array.forEach(_flattenArray); + } else if (!validator || validator(array)) { + flatArray.push(array); + } } _flattenArray(array); return flatArray; @@ -451,9 +456,7 @@ function margeOptions() { } return optionsList.reduce(function(options, optionsPart) { - /* jshint eqnull:true */ - if (optionsPart == null) { return options; } - /* jshint eqnull:false */ + if (optionsPart == null) { return options; } // eslint-disable-line eqeqeq // ======== DEPRECATED ======== if (optionsPart.hasOwnProperty('noEchoBack') && @@ -473,24 +476,22 @@ function margeOptions() { var value; if (!optionsPart.hasOwnProperty(optionName)) { return; } value = optionsPart[optionName]; - switch (optionName) { + switch (optionName) { // eslint-disable-line default-case // _readlineSync <- * * -> defaultOptions // ================ string case 'mask': // * * case 'limitMessage': // * case 'defaultInput': // * case 'encoding': // * * - /* jshint eqnull:true */ - value = value != null ? value + '' : ''; - /* jshint eqnull:false */ - if (value && optionName !== 'limitMessage') - { value = value.replace(/[\r\n]/g, ''); } + value = value != null ? value + '' : ''; // eslint-disable-line eqeqeq + if (value && optionName !== 'limitMessage') { value = value.replace(/[\r\n]/g, ''); } options[optionName] = value; break; // ================ number(int) case 'bufferSize': // * * - if (!isNaN(value = parseInt(value, 10)) && typeof value === 'number') - { options[optionName] = value; } // limited updating (number is needed) + if (!isNaN(value = parseInt(value, 10)) && typeof value === 'number') { + options[optionName] = value; // limited updating (number is needed) + } break; // ================ boolean case 'displayOnly': // * @@ -507,12 +508,12 @@ function margeOptions() { case 'trueValue': // * case 'falseValue': // * options[optionName] = flattenArray(value, function(value) { - var type = typeof value; - return type === 'string' || type === 'number' || - type === 'function' || value instanceof RegExp; - }).map(function(value) { - return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value; - }); + var type = typeof value; + return type === 'string' || type === 'number' || + type === 'function' || value instanceof RegExp; + }).map(function(value) { + return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value; + }); break; // ================ function case 'print': // * * @@ -523,9 +524,7 @@ function margeOptions() { // ================ other case 'prompt': // * case 'display': // * - /* jshint eqnull:true */ - options[optionName] = value != null ? value : ''; - /* jshint eqnull:false */ + options[optionName] = value != null ? value : ''; // eslint-disable-line eqeqeq break; } }); @@ -538,7 +537,7 @@ function isMatched(res, comps, caseSensitive) { var type = typeof comp; return type === 'string' ? (caseSensitive ? res === comp : res.toLowerCase() === comp.toLowerCase()) : - type === 'number' ? parseFloat(res) === comp : + type === 'number' ? parseFloat(res) === comp : type === 'function' ? comp(res) : comp instanceof RegExp ? comp.test(res) : false; }); @@ -561,7 +560,7 @@ function replacePlaceholder(text, generator) { function getPlaceholderText(s, escape, placeholder, pre, param, post) { var text; - return escape || typeof(text = generator(param)) !== 'string' ? placeholder : + return escape || typeof (text = generator(param)) !== 'string' ? placeholder : text ? (pre || '') + text + (post || '') : ''; } @@ -581,8 +580,8 @@ function array2charlist(array, caseSensitive, collectSymbols) { return groups; } - values = array.reduce(function(chars, value) - { return chars.concat((value + '').split('')); }, []) + values = array.reduce( + function(chars, value) { return chars.concat((value + '').split('')); }, []) .reduce(function(groups, curChar) { var curGroupClass, curCharCode; if (!caseSensitive) { curChar = curChar.toLowerCase(); } @@ -609,8 +608,9 @@ function array2charlist(array, caseSensitive, collectSymbols) { return {values: values, suppressed: suppressed}; } -function joinChunks(chunks, suppressed) - { return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/'); } +function joinChunks(chunks, suppressed) { + return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/'); +} function getPhContent(param, options) { var text, values, resCharlist = {}, arg; @@ -665,8 +665,11 @@ function getPhContent(param, options) { case 'CWD': case 'cwdHome': text = process.cwd(); - if (param === 'CWD') { text = pathUtil.basename(text); } - else if (param === 'cwdHome') { text = replaceHomePath(text); } + if (param === 'CWD') { + text = pathUtil.basename(text); + } else if (param === 'cwdHome') { + text = replaceHomePath(text); + } break; case 'date': case 'time': @@ -677,7 +680,7 @@ function getPhContent(param, options) { 'String'](); break; default: // with arg - if (typeof(arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') { + if (typeof (arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') { text = inputHistory[inputHistory.length - arg] || ''; } } @@ -687,12 +690,11 @@ function getPhContent(param, options) { function getPhCharlist(param) { var matches = /^(.)-(.)$/.exec(param), text = '', from, to, code, step; - if (!matches) { return; } + if (!matches) { return null; } 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); } + for (code = from; code !== to + step; code += step) { text += String.fromCharCode(code); } /* ESLint bug */ // eslint-disable-line no-unmodified-loop-condition return text; } @@ -724,9 +726,9 @@ function toBool(res, options) { function getValidLine(options) { var res, forceNext, limitMessage, matches, histInput, args, resCheck; + function _getPhContent(param) { return getPhContent(param, options); } - function addDisplay(text) - { options.display += (/[^\r\n]$/.test(options.display) ? '\n' : '') + text; } + function addDisplay(text) { options.display += (/[^\r\n]$/.test(options.display) ? '\n' : '') + text; } options.limitSrc = options.limit; options.displaySrc = options.display; @@ -743,8 +745,11 @@ function getValidLine(options) { if (options.history) { if ((matches = /^\s*\!(?:\!|-1)(:p)?\s*$/.exec(res))) { // `!!` `!-1` +`:p` histInput = inputHistory[0] || ''; - if (matches[1]) { forceNext = true; } // only display - else { res = histInput; } // replace input + if (matches[1]) { // only display + forceNext = true; + } else { // replace input + res = histInput; + } // Show it even if it is empty (NL only). addDisplay(histInput + '\n'); if (!forceNext) { // Loop may break @@ -759,7 +764,7 @@ function getValidLine(options) { if (!forceNext && options.cd && res) { args = parseCl(res); - switch (args[0].toLowerCase()) { + switch (args[0].toLowerCase()) { // eslint-disable-line default-case case 'cd': if (args[1]) { try { @@ -786,8 +791,9 @@ function getValidLine(options) { if (!forceNext) { if (!options.limitSrc.length || isMatched(res, options.limitSrc, options.caseSensitive)) { break; } - if (options.limitMessage) - { limitMessage = replacePlaceholder(options.limitMessage, _getPhContent); } + if (options.limitMessage) { + limitMessage = replacePlaceholder(options.limitMessage, _getPhContent); + } } addDisplay((limitMessage ? limitMessage + '\n' : '') + @@ -810,9 +816,11 @@ exports.setDefaultOptions = function(options) { }; exports.question = function(query, options) { + /* eslint-disable key-spacing */ return getValidLine(margeOptions(margeOptions(true, options), { - display: query - })); + display: query + })); + /* eslint-enable key-spacing */ }; exports.prompt = function(options) { @@ -822,18 +830,20 @@ exports.prompt = function(options) { }; exports.keyIn = function(query, options) { + /* eslint-disable key-spacing */ var readOptions = margeOptions(margeOptions(true, options), { - display: query, - keyIn: true, - keepWhitespace: true - }); + display: query, + keyIn: true, + keepWhitespace: true + }); + /* eslint-enable key-spacing */ // char list readOptions.limitSrc = readOptions.limit.filter(function(value) { - var type = typeof value; - return type === 'string' || type === 'number'; - }) - .map(function(text) { return replacePlaceholder(text + '', getPhCharlist); }); + var type = typeof value; + return type === 'string' || type === 'number'; + }) + .map(function(text) { return replacePlaceholder(text + '', getPhCharlist); }); // pattern readOptions.limit = escapePattern(readOptions.limitSrc.join('')); @@ -856,26 +866,28 @@ exports.keyIn = function(query, options) { // ------------------------------------ exports.questionEMail = function(query, options) { - /* jshint eqnull:true */ - if (query == null) { query = 'Input e-mail address :'; } - /* jshint eqnull:false */ + if (query == null) { query = 'Input e-mail address :'; } // eslint-disable-line eqeqeq + /* eslint-disable key-spacing */ return exports.question(query, margeOptions({ - // -------- default - hideEchoBack: false, - // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address - limit: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/, - limitMessage: 'Input valid e-mail address, please.', - trueValue: null, - falseValue: null - }, options, { - // -------- forced - keepWhitespace: false, - cd: false - })); + // -------- default + hideEchoBack: false, + // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address + limit: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/, + limitMessage: 'Input valid e-mail address, please.', + trueValue: null, + falseValue: null + }, options, { + // -------- forced + keepWhitespace: false, + cd: false + })); + /* eslint-enable key-spacing */ }; exports.questionNewPassword = function(query, options) { - var readOptions = margeOptions({ + /* eslint-disable key-spacing */ + var resCharlist, min, max, + readOptions = margeOptions({ // -------- default hideEchoBack: true, mask: '*', @@ -890,13 +902,14 @@ exports.questionNewPassword = function(query, options) { cd: false, // limit (by charlist etc.), phContent: function(param) { - return param === 'charlist' ? resCharlist.text : - param === 'length' ? min + '...' + max : null; - } + return param === 'charlist' ? resCharlist.text : + param === 'length' ? min + '...' + max : null; + } }), // added: charlist, min, max, confirmMessage, unmatchMessage - charlist, min, max, confirmMessage, unmatchMessage, - limit, resCharlist, limitMessage, res1, res2; + charlist, confirmMessage, unmatchMessage, + limit, limitMessage, res1, res2; + /* eslint-enable key-spacing */ options = options || {}; charlist = replacePlaceholder( @@ -908,17 +921,13 @@ exports.questionNewPassword = function(query, options) { resCharlist = array2charlist([charlist], readOptions.caseSensitive, true); resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed); - /* jshint eqnull:true */ - confirmMessage = options.confirmMessage != null ? options.confirmMessage : + confirmMessage = options.confirmMessage != null ? options.confirmMessage : // eslint-disable-line eqeqeq 'Reinput a same one to confirm it :'; - unmatchMessage = options.unmatchMessage != null ? options.unmatchMessage : + unmatchMessage = options.unmatchMessage != null ? options.unmatchMessage : // eslint-disable-line eqeqeq 'It differs from first one.' + ' Hit only the Enter key if you want to retry from first one.'; - /* jshint eqnull:false */ - /* jshint eqnull:true */ - if (query == null) { query = 'Input new password :'; } - /* jshint eqnull:false */ + if (query == null) { query = 'Input new password :'; } // eslint-disable-line eqeqeq limitMessage = readOptions.limitMessage; while (!res2) { @@ -940,15 +949,17 @@ function _questionNum(query, options, parser) { validValue = parser(value); return !isNaN(validValue) && typeof validValue === 'number'; } + /* eslint-disable key-spacing */ exports.question(query, margeOptions({ - // -------- default - limitMessage: 'Input valid number, please.' - }, options, { - // -------- forced - limit: getValidValue, - cd: false - // trueValue, falseValue, caseSensitive, keepWhitespace don't work. - })); + // -------- default + limitMessage: 'Input valid number, please.' + }, options, { + // -------- forced + limit: getValidValue, + cd: false + // trueValue, falseValue, caseSensitive, keepWhitespace don't work. + })); + /* eslint-enable key-spacing */ return validValue; } exports.questionInt = function(query, options) { @@ -959,7 +970,9 @@ exports.questionFloat = function(query, options) { }; exports.questionPath = function(query, options) { - var readOptions = margeOptions({ + /* eslint-disable key-spacing */ + var validPath, error = '', + readOptions = margeOptions({ // -------- default hideEchoBack: false, limitMessage: '$Input valid path, please.' + @@ -970,82 +983,80 @@ exports.questionPath = function(query, options) { // -------- forced keepWhitespace: false, limit: function(value) { - var exists, stat, res; - value = replaceHomePath(value, true); - error = ''; // for validate - // mkdir -p - function mkdirParents(dirPath) { - dirPath.split(/\/|\\/).reduce(function(parents, dir) { - var path = pathUtil.resolve((parents += dir + pathUtil.sep)); - if (!fs.existsSync(path)) { - fs.mkdirSync(path); - } else if (!fs.statSync(path).isDirectory()) { - throw new Error('Non directory already exists: ' + path); - } - return parents; - }, ''); - } + var exists, stat, res; + value = replaceHomePath(value, true); + error = ''; // for validate + // mkdir -p + function mkdirParents(dirPath) { + dirPath.split(/\/|\\/).reduce(function(parents, dir) { + var path = pathUtil.resolve((parents += dir + pathUtil.sep)); + if (!fs.existsSync(path)) { + fs.mkdirSync(path); + } else if (!fs.statSync(path).isDirectory()) { + throw new Error('Non directory already exists: ' + path); + } + return parents; + }, ''); + } - try { - exists = fs.existsSync(value); - validPath = exists ? fs.realpathSync(value) : pathUtil.resolve(value); - // options.exists default: true, not-bool: no-check - if (!options.hasOwnProperty('exists') && !exists || - typeof options.exists === 'boolean' && options.exists !== exists) { - error = (exists ? 'Already exists' : 'No such file or directory') + - ': ' + validPath; - return; - } - if (!exists && options.create) { - if (options.isDirectory) { - mkdirParents(validPath); - } else { - mkdirParents(pathUtil.dirname(validPath)); - fs.closeSync(fs.openSync(validPath, 'w')); // touch - } - validPath = fs.realpathSync(validPath); - } - if (exists && (options.min || options.max || - options.isFile || options.isDirectory)) { - stat = fs.statSync(validPath); - // type check first (directory has zero size) - if (options.isFile && !stat.isFile()) { - error = 'Not file: ' + validPath; - return; - } else if (options.isDirectory && !stat.isDirectory()) { - error = 'Not directory: ' + validPath; - return; - } else if (options.min && stat.size < +options.min || - options.max && stat.size > +options.max) { - error = 'Size ' + stat.size +' is out of range: ' + validPath; - return; - } - } - if (typeof options.validate === 'function' && - (res = options.validate(validPath)) !== true) { - if (typeof res === 'string') { error = res; } - return; - } - } catch (e) { - error = e + ''; - return; + try { + exists = fs.existsSync(value); + validPath = exists ? fs.realpathSync(value) : pathUtil.resolve(value); + // options.exists default: true, not-bool: no-check + if (!options.hasOwnProperty('exists') && !exists || + typeof options.exists === 'boolean' && options.exists !== exists) { + error = (exists ? 'Already exists' : 'No such file or directory') + + ': ' + validPath; + return false; } - return true; - }, + if (!exists && options.create) { + if (options.isDirectory) { + mkdirParents(validPath); + } else { + mkdirParents(pathUtil.dirname(validPath)); + fs.closeSync(fs.openSync(validPath, 'w')); // touch + } + validPath = fs.realpathSync(validPath); + } + if (exists && (options.min || options.max || + options.isFile || options.isDirectory)) { + stat = fs.statSync(validPath); + // type check first (directory has zero size) + if (options.isFile && !stat.isFile()) { + error = 'Not file: ' + validPath; + return false; + } else if (options.isDirectory && !stat.isDirectory()) { + error = 'Not directory: ' + validPath; + return false; + } else if (options.min && stat.size < +options.min || + options.max && stat.size > +options.max) { + error = 'Size ' + stat.size + ' is out of range: ' + validPath; + return false; + } + } + if (typeof options.validate === 'function' && + (res = options.validate(validPath)) !== true) { + if (typeof res === 'string') { error = res; } + return false; + } + } catch (e) { + error = e + ''; + return false; + } + return true; + }, // trueValue, falseValue, caseSensitive don't work. phContent: function(param) { - return param === 'error' ? error : - param !== 'min' && param !== 'max' ? null : - options.hasOwnProperty(param) ? options[param] + '' : ''; - } - }), + return param === 'error' ? error : + param !== 'min' && param !== 'max' ? null : + options.hasOwnProperty(param) ? options[param] + '' : ''; + } + }); // added: exists, create, min, max, isFile, isDirectory, validate - validPath, error = ''; + /* eslint-enable key-spacing */ options = options || {}; - /* jshint eqnull:true */ - if (query == null) { query = 'Input path (you can "cd" and "pwd") :'; } - /* jshint eqnull:false */ + if (query == null) { query = 'Input path (you can "cd" and "pwd") :'; } // eslint-disable-line eqeqeq exports.question(query, readOptions); return validPath; @@ -1090,6 +1101,7 @@ function getClHandler(commandHandler, options) { } exports.promptCL = function(commandHandler, options) { + /* eslint-disable key-spacing */ var readOptions = margeOptions({ // -------- default hideEchoBack: false, @@ -1101,6 +1113,7 @@ exports.promptCL = function(commandHandler, options) { // trueValue, falseValue, keepWhitespace don't work. // preCheck, limit (by clHandler) clHandler = getClHandler(commandHandler, readOptions); + /* eslint-enable key-spacing */ readOptions.limit = clHandler.limit; readOptions.preCheck = clHandler.preCheck; exports.prompt(readOptions); @@ -1108,19 +1121,22 @@ exports.promptCL = function(commandHandler, options) { }; exports.promptLoop = function(inputHandler, options) { + /* eslint-disable key-spacing */ var readOptions = margeOptions({ - // -------- default - hideEchoBack: false, - trueValue: null, - falseValue: null, - caseSensitive: false, - history: true - }, options); + // -------- default + hideEchoBack: false, + trueValue: null, + falseValue: null, + caseSensitive: false, + history: true + }, options); + /* eslint-enable key-spacing */ while (true) { if (inputHandler(exports.prompt(readOptions))) { break; } } return; }; exports.promptCLLoop = function(commandHandler, options) { + /* eslint-disable key-spacing */ var readOptions = margeOptions({ // -------- default hideEchoBack: false, @@ -1132,6 +1148,7 @@ exports.promptCLLoop = function(commandHandler, options) { // trueValue, falseValue, keepWhitespace don't work. // preCheck, limit (by clHandler) clHandler = getClHandler(commandHandler, readOptions); + /* eslint-enable key-spacing */ readOptions.limit = clHandler.limit; readOptions.preCheck = clHandler.preCheck; while (true) { @@ -1142,66 +1159,70 @@ exports.promptCLLoop = function(commandHandler, options) { }; exports.promptSimShell = function(options) { + /* eslint-disable key-spacing */ return exports.prompt(margeOptions({ - // -------- default - hideEchoBack: false, - history: true - }, options, { - // -------- forced - prompt: (function() { - return IS_WIN ? - '$>' : - // 'user@host:cwd$ ' - (process.env.USER || '') + - (process.env.HOSTNAME ? - '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') + - ':$$ '; - })() - })); + // -------- default + hideEchoBack: false, + history: true + }, options, { + // -------- forced + prompt: (function() { + return IS_WIN ? + '$>' : + // 'user@host:cwd$ ' + (process.env.USER || '') + + (process.env.HOSTNAME ? + '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') + + ':$$ '; + })() + })); + /* eslint-enable key-spacing */ }; function _keyInYN(query, options, limit) { var res; - /* jshint eqnull:true */ - if (query == null) { query = 'Are you sure? :'; } - /* jshint eqnull:false */ - if ((!options || options.guide !== false) && (query += '')) - { query = query.replace(/\s*:?\s*$/, '') + ' [y/n] :'; } + if (query == null) { query = 'Are you sure? :'; } // eslint-disable-line eqeqeq + if ((!options || options.guide !== false) && (query += '')) { + query = query.replace(/\s*:?\s*$/, '') + ' [y/n] :'; + } + /* eslint-disable key-spacing */ res = exports.keyIn(query, margeOptions(options, { - // -------- forced - hideEchoBack: false, - limit: limit, - trueValue: 'y', - falseValue: 'n', - caseSensitive: false - // mask doesn't work. - })); - // added: guide + // -------- forced + hideEchoBack: false, + limit: limit, + trueValue: 'y', + falseValue: 'n', + caseSensitive: false + // mask doesn't work. + })); + // added: guide + /* eslint-enable key-spacing */ return typeof res === 'boolean' ? res : ''; } exports.keyInYN = function(query, options) { return _keyInYN(query, options); }; -exports.keyInYNStrict = function(query, options) - { return _keyInYN(query, options, 'yn'); }; +exports.keyInYNStrict = function(query, options) { return _keyInYN(query, options, 'yn'); }; exports.keyInPause = function(query, options) { - /* jshint eqnull:true */ - if (query == null) { query = 'Continue...'; } - /* jshint eqnull:false */ - if ((!options || options.guide !== false) && (query += '')) - { query = query.replace(/\s+$/, '') + ' (Hit any key)'; } + if (query == null) { query = 'Continue...'; } // eslint-disable-line eqeqeq + if ((!options || options.guide !== false) && (query += '')) { + query = query.replace(/\s+$/, '') + ' (Hit any key)'; + } + /* eslint-disable key-spacing */ exports.keyIn(query, margeOptions({ - // -------- default - limit: null - }, options, { - // -------- forced - hideEchoBack: true, - mask: '' - })); - // added: guide + // -------- default + limit: null + }, options, { + // -------- forced + hideEchoBack: true, + mask: '' + })); + // added: guide + /* eslint-enable key-spacing */ return; }; exports.keyInSelect = function(items, query, options) { + /* eslint-disable key-spacing */ var readOptions = margeOptions({ // -------- default hideEchoBack: false @@ -1212,15 +1233,17 @@ exports.keyInSelect = function(items, query, options) { caseSensitive: false, // limit (by items), phContent: function(param) { - return param === 'itemsCount' ? items.length + '' : - param === 'firstItem' ? (items[0] + '').trim() : - param === 'lastItem' ? (items[items.length - 1] + '').trim() : null; - } + return param === 'itemsCount' ? items.length + '' : + param === 'firstItem' ? (items[0] + '').trim() : + param === 'lastItem' ? (items[items.length - 1] + '').trim() : null; + } }), // added: guide, cancel keylist = '', key2i = {}, charCode = 49 /* '1' */, display = '\n'; - if (!Array.isArray(items) || !items.length || items.length > 35) - { throw '`items` must be Array (max length: 35).'; } + /* eslint-enable key-spacing */ + if (!Array.isArray(items) || !items.length || items.length > 35) { + throw '`items` must be Array (max length: 35).'; + } items.forEach(function(item, i) { var key = String.fromCharCode(charCode); @@ -1232,21 +1255,18 @@ exports.keyInSelect = function(items, query, options) { if (!options || options.cancel !== false) { keylist += '0'; key2i['0'] = -1; - /* jshint eqnull:true */ - display += '[' + '0' + '] ' + - (options && options.cancel != null && typeof options.cancel !== 'boolean' ? + display += '[0] ' + + (options && options.cancel != null && typeof options.cancel !== 'boolean' ? // eslint-disable-line eqeqeq (options.cancel + '').trim() : 'CANCEL') + '\n'; - /* jshint eqnull:false */ } readOptions.limit = keylist; display += '\n'; - /* jshint eqnull:true */ - if (query == null) { query = 'Choose one from list :'; } - /* jshint eqnull:false */ + if (query == null) { query = 'Choose one from list :'; } // eslint-disable-line eqeqeq if ((query += '')) { - if (!options || options.guide !== false) - { query = query.replace(/\s*:?\s*$/, '') + ' [$] :'; } + if (!options || options.guide !== false) { + query = query.replace(/\s*:?\s*$/, '') + ' [$] :'; + } display += query; }