1303 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1303 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
|  * readlineSync
 | |
|  * https://github.com/anseki/readline-sync
 | |
|  *
 | |
|  * Copyright (c) 2018 anseki
 | |
|  * Licensed under the MIT license.
 | |
|  */
 | |
| 
 | |
| 'use strict';
 | |
| 
 | |
| var
 | |
|   IS_WIN = process.platform === 'win32',
 | |
| 
 | |
|   ALGORITHM_CIPHER = 'aes-256-cbc',
 | |
|   ALGORITHM_HASH = 'sha256',
 | |
|   DEFAULT_ERR_MSG = 'The current environment doesn\'t support interactive reading from TTY.',
 | |
| 
 | |
|   fs = require('fs'),
 | |
|   TTY = process.binding('tty_wrap').TTY,
 | |
|   childProc = require('child_process'),
 | |
|   pathUtil = require('path'),
 | |
| 
 | |
|   defaultOptions = {
 | |
|     /* eslint-disable key-spacing */
 | |
|     prompt:             '> ',
 | |
|     hideEchoBack:       false,
 | |
|     mask:               '*',
 | |
|     limit:              [],
 | |
|     limitMessage:       'Input another, please.$<( [)limit(])>',
 | |
|     defaultInput:       '',
 | |
|     trueValue:          [],
 | |
|     falseValue:         [],
 | |
|     caseSensitive:      false,
 | |
|     keepWhitespace:     false,
 | |
|     encoding:           'utf8',
 | |
|     bufferSize:         1024,
 | |
|     print:              void 0,
 | |
|     history:            true,
 | |
|     cd:                 false,
 | |
|     phContent:          void 0,
 | |
|     preCheck:           void 0
 | |
|     /* eslint-enable key-spacing */
 | |
|   },
 | |
| 
 | |
|   fdR = 'none', fdW, ttyR, isRawMode = false,
 | |
|   extHostPath, extHostArgs, tempdir, salt = 0,
 | |
|   lastInput = '', inputHistory = [], rawInput,
 | |
|   _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.js 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
 | |
|   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.hideEchoBack && !options.mask;
 | |
| 
 | |
|   function tryExt() {
 | |
|     var res = readlineExt(options);
 | |
|     if (res.error) { throw res.error; }
 | |
|     return res.input;
 | |
|   }
 | |
| 
 | |
|   if (_DBG_checkOptions) { _DBG_checkOptions(options); }
 | |
| 
 | |
|   (function() { // open TTY
 | |
|     var fsB, constants, verNum;
 | |
| 
 | |
|     function getFsB() {
 | |
|       if (!fsB) {
 | |
|         fsB = process.binding('fs'); // For raw device path
 | |
|         constants = process.binding('constants');
 | |
|       }
 | |
|       return fsB;
 | |
|     }
 | |
| 
 | |
|     if (typeof fdR !== 'string') { return; }
 | |
|     fdR = null;
 | |
| 
 | |
|     if (IS_WIN) {
 | |
|       // iojs-v2.3.2+ input stream can't read first line. (#18)
 | |
|       // ** Don't get process.stdin before check! **
 | |
|       // Fixed v5.1.0
 | |
|       // Fixed v4.2.4
 | |
|       // It regressed again in v5.6.0, it is fixed in v6.2.0.
 | |
|       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);
 | |
|       if (!(verNum >= 20302 && verNum < 40204 || verNum >= 50000 && verNum < 50100 || verNum >= 50600 && verNum < 60200) &&
 | |
|           process.stdin.isTTY) {
 | |
|         process.stdin.pause();
 | |
|         fdR = process.stdin.fd;
 | |
|         ttyR = process.stdin._handle;
 | |
|       } else {
 | |
|         try {
 | |
|           // The stream by fs.openSync('\\\\.\\CON', 'r') can't switch to raw mode.
 | |
|           // '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) { /* ignore */ }
 | |
|       }
 | |
| 
 | |
|       if (process.stdout.isTTY) {
 | |
|         fdW = process.stdout.fd;
 | |
|       } else {
 | |
|         try {
 | |
|           fdW = fs.openSync('\\\\.\\CON', 'w');
 | |
|         } catch (e) { /* ignore */ }
 | |
|         if (typeof fdW !== 'number') { // Retry
 | |
|           try {
 | |
|             fdW = getFsB().open('CONOUT$', constants.O_RDWR, parseInt('0666', 8));
 | |
|           } catch (e) { /* ignore */ }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|     } else {
 | |
|       if (process.stdin.isTTY) {
 | |
|         process.stdin.pause();
 | |
|         try {
 | |
|           fdR = fs.openSync('/dev/tty', 'r'); // device file, not process.stdin
 | |
|           ttyR = process.stdin._handle;
 | |
|         } catch (e) { /* ignore */ }
 | |
|       } else {
 | |
|         // Node.js v0.12 read() fails.
 | |
|         try {
 | |
|           fdR = fs.openSync('/dev/tty', 'r');
 | |
|           ttyR = new TTY(fdR, false);
 | |
|         } catch (e) { /* ignore */ }
 | |
|       }
 | |
| 
 | |
|       if (process.stdout.isTTY) {
 | |
|         fdW = process.stdout.fd;
 | |
|       } else {
 | |
|         try {
 | |
|           fdW = fs.openSync('/dev/tty', 'w');
 | |
|         } catch (e) { /* ignore */ }
 | |
|       }
 | |
|     }
 | |
|   })();
 | |
| 
 | |
|   (function() { // try read
 | |
|     var atEol, limit,
 | |
|       isCooked = !options.hideEchoBack && !options.keyIn,
 | |
|       buffer, reqSize, readSize, chunk, line;
 | |
|     rawInput = '';
 | |
| 
 | |
|     // Node.js v0.10- returns an error if same mode is set.
 | |
|     function setRawMode(mode) {
 | |
|       if (mode === isRawMode) { return true; }
 | |
|       if (ttyR.setRawMode(mode) !== 0) { return false; }
 | |
|       isRawMode = mode;
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (_DBG_useExt || !ttyR ||
 | |
|         typeof fdW !== 'number' && (options.display || !isCooked)) {
 | |
|       input = tryExt();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (options.display) {
 | |
|       fs.writeSync(fdW, options.display);
 | |
|       options.display = '';
 | |
|     }
 | |
|     if (options.displayOnly) { return; }
 | |
| 
 | |
|     if (!setRawMode(!isCooked)) {
 | |
|       input = tryExt();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     reqSize = options.keyIn ? 1 : options.bufferSize;
 | |
|     // Check `allocUnsafe` to make sure of the new API.
 | |
|     buffer = Buffer.allocUnsafe && Buffer.alloc ? Buffer.alloc(reqSize) : new Buffer(reqSize);
 | |
| 
 | |
|     if (options.keyIn && options.limit) {
 | |
|       limit = new RegExp('[^' + options.limit + ']',
 | |
|         'g' + (options.caseSensitive ? '' : 'i'));
 | |
|     }
 | |
| 
 | |
|     while (true) {
 | |
|       readSize = 0;
 | |
|       try {
 | |
|         readSize = fs.readSync(fdR, buffer, 0, reqSize);
 | |
|       } catch (e) {
 | |
|         if (e.code !== 'EOF') {
 | |
|           setRawMode(false);
 | |
|           input += tryExt();
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|       if (readSize > 0) {
 | |
|         chunk = buffer.toString(options.encoding, 0, readSize);
 | |
|         rawInput += chunk;
 | |
|       } else {
 | |
|         chunk = '\n';
 | |
|         rawInput += String.fromCharCode(0);
 | |
|       }
 | |
| 
 | |
|       if (chunk && typeof (line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') {
 | |
|         chunk = line;
 | |
|         atEol = true;
 | |
|       }
 | |
| 
 | |
|       // other ctrl-chars
 | |
|       // eslint-disable-next-line no-control-regex
 | |
|       if (chunk) { chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); }
 | |
|       if (chunk && limit) { chunk = chunk.replace(limit, ''); }
 | |
| 
 | |
|       if (chunk) {
 | |
|         if (!isCooked) {
 | |
|           if (!options.hideEchoBack) {
 | |
|             fs.writeSync(fdW, chunk);
 | |
|           } else if (options.mask) {
 | |
|             fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask));
 | |
|           }
 | |
|         }
 | |
|         input += chunk;
 | |
|       }
 | |
| 
 | |
|       if (!options.keyIn && atEol ||
 | |
|         options.keyIn && input.length >= reqSize) { break; }
 | |
|     }
 | |
| 
 | |
|     if (!isCooked && !silent) { fs.writeSync(fdW, '\n'); }
 | |
|     setRawMode(false);
 | |
|   })();
 | |
| 
 | |
|   if (options.print && !silent) {
 | |
|     options.print(displaySave + (options.displayOnly ? '' :
 | |
|         (options.hideEchoBack ? (new Array(input.length + 1)).join(options.mask)
 | |
|           : input) + '\n'), // must at least write '\n'
 | |
|       options.encoding);
 | |
|   }
 | |
| 
 | |
|   return options.displayOnly ? '' :
 | |
|     (lastInput = options.keepWhitespace || options.keyIn ? input : input.trim());
 | |
| }
 | |
| 
 | |
| function flattenArray(array, validator) {
 | |
|   var flatArray = [];
 | |
|   function _flattenArray(array) {
 | |
|     if (array == null) {
 | |
|       return;
 | |
|     } else if (Array.isArray(array)) {
 | |
|       array.forEach(_flattenArray);
 | |
|     } else if (!validator || validator(array)) {
 | |
|       flatArray.push(array);
 | |
|     }
 | |
|   }
 | |
|   _flattenArray(array);
 | |
|   return flatArray;
 | |
| }
 | |
| 
 | |
| function escapePattern(pattern) {
 | |
|   return pattern.replace(/[\x00-\x7f]/g, // eslint-disable-line no-control-regex
 | |
|     function(s) { return '\\x' + ('00' + s.charCodeAt().toString(16)).substr(-2); });
 | |
| }
 | |
| 
 | |
| // margeOptions(options1, options2 ... )
 | |
| // margeOptions(true, options1, options2 ... )
 | |
| //    arg1=true : Start from defaultOptions and pick elements of that.
 | |
| function margeOptions() {
 | |
|   var optionsList = Array.prototype.slice.call(arguments),
 | |
|     optionNames, fromDefault;
 | |
| 
 | |
|   if (optionsList.length && typeof optionsList[0] === 'boolean') {
 | |
|     fromDefault = optionsList.shift();
 | |
|     if (fromDefault) {
 | |
|       optionNames = Object.keys(defaultOptions);
 | |
|       optionsList.unshift(defaultOptions);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return optionsList.reduce(function(options, optionsPart) {
 | |
|     if (optionsPart == null) { return options; }
 | |
| 
 | |
|     // ======== DEPRECATED ========
 | |
|     if (optionsPart.hasOwnProperty('noEchoBack') &&
 | |
|         !optionsPart.hasOwnProperty('hideEchoBack')) {
 | |
|       optionsPart.hideEchoBack = optionsPart.noEchoBack;
 | |
|       delete optionsPart.noEchoBack;
 | |
|     }
 | |
|     if (optionsPart.hasOwnProperty('noTrim') &&
 | |
|         !optionsPart.hasOwnProperty('keepWhitespace')) {
 | |
|       optionsPart.keepWhitespace = optionsPart.noTrim;
 | |
|       delete optionsPart.noTrim;
 | |
|     }
 | |
|     // ======== /DEPRECATED ========
 | |
| 
 | |
|     if (!fromDefault) { optionNames = Object.keys(optionsPart); }
 | |
|     optionNames.forEach(function(optionName) {
 | |
|       var value;
 | |
|       if (!optionsPart.hasOwnProperty(optionName)) { return; }
 | |
|       value = optionsPart[optionName];
 | |
|       switch (optionName) {
 | |
|                            // _readlineSync <- *    * -> defaultOptions
 | |
|         // ================ string
 | |
|         case 'mask':                        // *    *
 | |
|         case 'limitMessage':                //      *
 | |
|         case 'defaultInput':                //      *
 | |
|         case 'encoding':                    // *    *
 | |
|           value = value != null ? value + '' : '';
 | |
|           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)
 | |
|           }
 | |
|           break;
 | |
|         // ================ boolean
 | |
|         case 'displayOnly':                 // *
 | |
|         case 'keyIn':                       // *
 | |
|         case 'hideEchoBack':                // *    *
 | |
|         case 'caseSensitive':               // *    *
 | |
|         case 'keepWhitespace':              // *    *
 | |
|         case 'history':                     //      *
 | |
|         case 'cd':                          //      *
 | |
|           options[optionName] = !!value;
 | |
|           break;
 | |
|         // ================ array
 | |
|         case 'limit':                       // *    *     to string for readlineExt
 | |
|         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;
 | |
|           });
 | |
|           break;
 | |
|         // ================ function
 | |
|         case 'print':                       // *    *
 | |
|         case 'phContent':                   //      *
 | |
|         case 'preCheck':                    //      *
 | |
|           options[optionName] = typeof value === 'function' ? value : void 0;
 | |
|           break;
 | |
|         // ================ other
 | |
|         case 'prompt':                      //      *
 | |
|         case 'display':                     // *
 | |
|           options[optionName] = value != null ? value : '';
 | |
|           break;
 | |
|         // no default
 | |
|       }
 | |
|     });
 | |
|     return options;
 | |
|   }, {});
 | |
| }
 | |
| 
 | |
| function isMatched(res, comps, caseSensitive) {
 | |
|   return comps.some(function(comp) {
 | |
|     var type = typeof comp;
 | |
|     return type === 'string' ?
 | |
|         (caseSensitive ? res === comp : res.toLowerCase() === comp.toLowerCase()) :
 | |
|       type === 'number' ? parseFloat(res) === comp :
 | |
|       type === 'function' ? comp(res) :
 | |
|       comp instanceof RegExp ? comp.test(res) : false;
 | |
|   });
 | |
| }
 | |
| 
 | |
| function replaceHomePath(path, expand) {
 | |
|   var homePath = pathUtil.normalize(
 | |
|     IS_WIN ? (process.env.HOMEDRIVE || '') + (process.env.HOMEPATH || '') :
 | |
|     process.env.HOME || '').replace(/[\/\\]+$/, '');
 | |
|   path = pathUtil.normalize(path);
 | |
|   return expand ? path.replace(/^~(?=\/|\\|$)/, homePath) :
 | |
|     path.replace(new RegExp('^' + escapePattern(homePath) +
 | |
|       '(?=\\/|\\\\|$)', IS_WIN ? 'i' : ''), '~');
 | |
| }
 | |
| 
 | |
| function replacePlaceholder(text, generator) {
 | |
|   var PTN_INNER = '(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?',
 | |
|     rePlaceholder = new RegExp('(\\$)?(\\$<' + PTN_INNER + '>)', 'g'),
 | |
|     rePlaceholderCompat = new RegExp('(\\$)?(\\$\\{' + PTN_INNER + '\\})', 'g');
 | |
| 
 | |
|   function getPlaceholderText(s, escape, placeholder, pre, param, post) {
 | |
|     var text;
 | |
|     return escape || typeof (text = generator(param)) !== 'string' ? placeholder :
 | |
|       text ? (pre || '') + text + (post || '') : '';
 | |
|   }
 | |
| 
 | |
|   return text.replace(rePlaceholder, getPlaceholderText)
 | |
|     .replace(rePlaceholderCompat, getPlaceholderText);
 | |
| }
 | |
| 
 | |
| function array2charlist(array, caseSensitive, collectSymbols) {
 | |
|   var values, group = [], groupClass = -1, charCode = 0, symbols = '', suppressed;
 | |
|   function addGroup(groups, group) {
 | |
|     if (group.length > 3) { // ellipsis
 | |
|       groups.push(group[0] + '...' + group[group.length - 1]);
 | |
|       suppressed = true;
 | |
|     } else if (group.length) {
 | |
|       groups = groups.concat(group);
 | |
|     }
 | |
|     return groups;
 | |
|   }
 | |
| 
 | |
|   values = array.reduce(
 | |
|       function(chars, value) { return chars.concat((value + '').split('')); }, [])
 | |
|     .reduce(function(groups, curChar) {
 | |
|       var curGroupClass, curCharCode;
 | |
|       if (!caseSensitive) { curChar = curChar.toLowerCase(); }
 | |
|       curGroupClass = /^\d$/.test(curChar) ? 1 :
 | |
|         /^[A-Z]$/.test(curChar) ? 2 : /^[a-z]$/.test(curChar) ? 3 : 0;
 | |
|       if (collectSymbols && curGroupClass === 0) {
 | |
|         symbols += curChar;
 | |
|       } else {
 | |
|         curCharCode = curChar.charCodeAt(0);
 | |
|         if (curGroupClass && curGroupClass === groupClass &&
 | |
|             curCharCode === charCode + 1) {
 | |
|           group.push(curChar);
 | |
|         } else {
 | |
|           groups = addGroup(groups, group);
 | |
|           group = [curChar];
 | |
|           groupClass = curGroupClass;
 | |
|         }
 | |
|         charCode = curCharCode;
 | |
|       }
 | |
|       return groups;
 | |
|     }, []);
 | |
|   values = addGroup(values, group); // last group
 | |
|   if (symbols) { values.push(symbols); suppressed = true; }
 | |
|   return {values: values, suppressed: suppressed};
 | |
| }
 | |
| 
 | |
| function joinChunks(chunks, suppressed) {
 | |
|   return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/');
 | |
| }
 | |
| 
 | |
| function getPhContent(param, options) {
 | |
|   var text, values, resCharlist = {}, arg;
 | |
|   if (options.phContent) {
 | |
|     text = options.phContent(param, options);
 | |
|   }
 | |
|   if (typeof text !== 'string') {
 | |
|     switch (param) {
 | |
|       case 'hideEchoBack':
 | |
|       case 'mask':
 | |
|       case 'defaultInput':
 | |
|       case 'caseSensitive':
 | |
|       case 'keepWhitespace':
 | |
|       case 'encoding':
 | |
|       case 'bufferSize':
 | |
|       case 'history':
 | |
|       case 'cd':
 | |
|         text = !options.hasOwnProperty(param) ? '' :
 | |
|           typeof options[param] === 'boolean' ? (options[param] ? 'on' : 'off') :
 | |
|           options[param] + '';
 | |
|         break;
 | |
|       // case 'prompt':
 | |
|       // case 'query':
 | |
|       // case 'display':
 | |
|       //   text = options.hasOwnProperty('displaySrc') ? options.displaySrc + '' : '';
 | |
|       //   break;
 | |
|       case 'limit':
 | |
|       case 'trueValue':
 | |
|       case 'falseValue':
 | |
|         values = options[options.hasOwnProperty(param + 'Src') ? param + 'Src' : param];
 | |
|         if (options.keyIn) { // suppress
 | |
|           resCharlist = array2charlist(values, options.caseSensitive);
 | |
|           values = resCharlist.values;
 | |
|         } else {
 | |
|           values = values.filter(function(value) {
 | |
|             var type = typeof value;
 | |
|             return type === 'string' || type === 'number';
 | |
|           });
 | |
|         }
 | |
|         text = joinChunks(values, resCharlist.suppressed);
 | |
|         break;
 | |
|       case 'limitCount':
 | |
|       case 'limitCountNotZero':
 | |
|         text = options[options.hasOwnProperty('limitSrc') ?
 | |
|           'limitSrc' : 'limit'].length;
 | |
|         text = text || param !== 'limitCountNotZero' ? text + '' : '';
 | |
|         break;
 | |
|       case 'lastInput':
 | |
|         text = lastInput;
 | |
|         break;
 | |
|       case 'cwd':
 | |
|       case 'CWD':
 | |
|       case 'cwdHome':
 | |
|         text = process.cwd();
 | |
|         if (param === 'CWD') {
 | |
|           text = pathUtil.basename(text);
 | |
|         } else if (param === 'cwdHome') {
 | |
|           text = replaceHomePath(text);
 | |
|         }
 | |
|         break;
 | |
|       case 'date':
 | |
|       case 'time':
 | |
|       case 'localeDate':
 | |
|       case 'localeTime':
 | |
|         text = (new Date())['to' +
 | |
|           param.replace(/^./, function(str) { return str.toUpperCase(); }) +
 | |
|           'String']();
 | |
|         break;
 | |
|       default: // with arg
 | |
|         if (typeof (arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') {
 | |
|           text = inputHistory[inputHistory.length - arg] || '';
 | |
|         }
 | |
|     }
 | |
|   }
 | |
|   return text;
 | |
| }
 | |
| 
 | |
| function getPhCharlist(param) {
 | |
|   var matches = /^(.)-(.)$/.exec(param), text = '', from, to, code, step;
 | |
|   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); }
 | |
|   return text;
 | |
| }
 | |
| 
 | |
| // cmd "arg" " a r g " "" 'a"r"g' "a""rg" "arg
 | |
| function parseCl(cl) {
 | |
|   var reToken = new RegExp(/(\s*)(?:("|')(.*?)(?:\2|$)|(\S+))/g), matches,
 | |
|     taken = '', args = [], part;
 | |
|   cl = cl.trim();
 | |
|   while ((matches = reToken.exec(cl))) {
 | |
|     part = matches[3] || matches[4] || '';
 | |
|     if (matches[1]) {
 | |
|       args.push(taken);
 | |
|       taken = '';
 | |
|     }
 | |
|     taken += part;
 | |
|   }
 | |
|   if (taken) { args.push(taken); }
 | |
|   return args;
 | |
| }
 | |
| 
 | |
| function toBool(res, options) {
 | |
|   return (
 | |
|     (options.trueValue.length &&
 | |
|       isMatched(res, options.trueValue, options.caseSensitive)) ? true :
 | |
|     (options.falseValue.length &&
 | |
|       isMatched(res, options.falseValue, options.caseSensitive)) ? false : res);
 | |
| }
 | |
| 
 | |
| 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; }
 | |
| 
 | |
|   options.limitSrc = options.limit;
 | |
|   options.displaySrc = options.display;
 | |
|   options.limit = ''; // for readlineExt
 | |
|   options.display = replacePlaceholder(options.display + '', _getPhContent);
 | |
| 
 | |
|   while (true) {
 | |
|     res = _readlineSync(options);
 | |
|     forceNext = false;
 | |
|     limitMessage = '';
 | |
| 
 | |
|     if (options.defaultInput && !res) { res = options.defaultInput; }
 | |
| 
 | |
|     if (options.history) {
 | |
|       if ((matches = /^\s*\!(?:\!|-1)(:p)?\s*$/.exec(res))) { // `!!` `!-1` +`:p`
 | |
|         histInput = inputHistory[0] || '';
 | |
|         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
 | |
|           options.displayOnly = true;
 | |
|           _readlineSync(options);
 | |
|           options.displayOnly = false;
 | |
|         }
 | |
|       } else if (res && res !== inputHistory[inputHistory.length - 1]) {
 | |
|         inputHistory = [res];
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (!forceNext && options.cd && res) {
 | |
|       args = parseCl(res);
 | |
|       switch (args[0].toLowerCase()) {
 | |
|         case 'cd':
 | |
|           if (args[1]) {
 | |
|             try {
 | |
|               process.chdir(replaceHomePath(args[1], true));
 | |
|             } catch (e) {
 | |
|               addDisplay(e + '');
 | |
|             }
 | |
|           }
 | |
|           forceNext = true;
 | |
|           break;
 | |
|         case 'pwd':
 | |
|           addDisplay(process.cwd());
 | |
|           forceNext = true;
 | |
|           break;
 | |
|         // no default
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (!forceNext && options.preCheck) {
 | |
|       resCheck = options.preCheck(res, options);
 | |
|       res = resCheck.res;
 | |
|       if (resCheck.forceNext) { forceNext = true; } // Don't switch to false.
 | |
|     }
 | |
| 
 | |
|     if (!forceNext) {
 | |
|       if (!options.limitSrc.length ||
 | |
|         isMatched(res, options.limitSrc, options.caseSensitive)) { break; }
 | |
|       if (options.limitMessage) {
 | |
|         limitMessage = replacePlaceholder(options.limitMessage, _getPhContent);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     addDisplay((limitMessage ? limitMessage + '\n' : '') +
 | |
|       replacePlaceholder(options.displaySrc + '', _getPhContent));
 | |
|   }
 | |
|   return toBool(res, options);
 | |
| }
 | |
| 
 | |
| // for dev
 | |
| exports._DBG_set_useExt = function(val) { _DBG_useExt = val; };
 | |
| exports._DBG_set_checkOptions = function(val) { _DBG_checkOptions = val; };
 | |
| exports._DBG_set_checkMethod = function(val) { _DBG_checkMethod = val; };
 | |
| exports._DBG_clearHistory = function() { lastInput = ''; inputHistory = []; };
 | |
| 
 | |
| // ------------------------------------
 | |
| 
 | |
| exports.setDefaultOptions = function(options) {
 | |
|   defaultOptions = margeOptions(true, options);
 | |
|   return margeOptions(true); // copy
 | |
| };
 | |
| 
 | |
| exports.question = function(query, options) {
 | |
|   /* eslint-disable key-spacing */
 | |
|   return getValidLine(margeOptions(margeOptions(true, options), {
 | |
|     display:            query
 | |
|   }));
 | |
|   /* eslint-enable key-spacing */
 | |
| };
 | |
| 
 | |
| exports.prompt = function(options) {
 | |
|   var readOptions = margeOptions(true, options);
 | |
|   readOptions.display = readOptions.prompt;
 | |
|   return getValidLine(readOptions);
 | |
| };
 | |
| 
 | |
| exports.keyIn = function(query, options) {
 | |
|   /* eslint-disable key-spacing */
 | |
|   var readOptions = margeOptions(margeOptions(true, options), {
 | |
|     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); });
 | |
|   // pattern
 | |
|   readOptions.limit = escapePattern(readOptions.limitSrc.join(''));
 | |
| 
 | |
|   ['trueValue', 'falseValue'].forEach(function(optionName) {
 | |
|     readOptions[optionName] = readOptions[optionName].reduce(function(comps, comp) {
 | |
|       var type = typeof comp;
 | |
|       if (type === 'string' || type === 'number') {
 | |
|         comps = comps.concat((comp + '').split(''));
 | |
|       } else { comps.push(comp); }
 | |
|       return comps;
 | |
|     }, []);
 | |
|   });
 | |
| 
 | |
|   readOptions.display = replacePlaceholder(readOptions.display + '',
 | |
|     function(param) { return getPhContent(param, readOptions); });
 | |
| 
 | |
|   return toBool(_readlineSync(readOptions), readOptions);
 | |
| };
 | |
| 
 | |
| // ------------------------------------
 | |
| 
 | |
| exports.questionEMail = function(query, options) {
 | |
|   if (query == null) { query = 'Input e-mail address: '; }
 | |
|   /* 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
 | |
|   }));
 | |
|   /* eslint-enable key-spacing */
 | |
| };
 | |
| 
 | |
| exports.questionNewPassword = function(query, options) {
 | |
|   /* eslint-disable key-spacing */
 | |
|   var resCharlist, min, max,
 | |
|     readOptions = margeOptions({
 | |
|       // -------- default
 | |
|       hideEchoBack:       true,
 | |
|       mask:               '*',
 | |
|       limitMessage:       'It can include: $<charlist>\n' +
 | |
|                             'And the length must be: $<length>',
 | |
|       trueValue:          null,
 | |
|       falseValue:         null,
 | |
|       caseSensitive:      true
 | |
|     }, options, {
 | |
|       // -------- forced
 | |
|       history:            false,
 | |
|       cd:                 false,
 | |
|       // limit (by charlist etc.),
 | |
|       phContent: function(param) {
 | |
|         return param === 'charlist' ? resCharlist.text :
 | |
|           param === 'length' ? min + '...' + max : null;
 | |
|       }
 | |
|     }),
 | |
|     // added:     charlist, min, max, confirmMessage, unmatchMessage
 | |
|     charlist, confirmMessage, unmatchMessage,
 | |
|     limit, limitMessage, res1, res2;
 | |
|   /* eslint-enable key-spacing */
 | |
|   options = options || {};
 | |
| 
 | |
|   charlist = replacePlaceholder(
 | |
|     options.charlist ? options.charlist + '' : '$<!-~>', getPhCharlist);
 | |
|   if (isNaN(min = parseInt(options.min, 10)) || typeof min !== 'number') { min = 12; }
 | |
|   if (isNaN(max = parseInt(options.max, 10)) || typeof max !== 'number') { max = 24; }
 | |
|   limit = new RegExp('^[' + escapePattern(charlist) +
 | |
|     ']{' + min + ',' + max + '}$');
 | |
|   resCharlist = array2charlist([charlist], readOptions.caseSensitive, true);
 | |
|   resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed);
 | |
| 
 | |
|   confirmMessage = options.confirmMessage != null ? options.confirmMessage :
 | |
|     'Reinput a same one to confirm it: ';
 | |
|   unmatchMessage = options.unmatchMessage != null ? options.unmatchMessage :
 | |
|     'It differs from first one.' +
 | |
|       ' Hit only the Enter key if you want to retry from first one.';
 | |
| 
 | |
|   if (query == null) { query = 'Input new password: '; }
 | |
| 
 | |
|   limitMessage = readOptions.limitMessage;
 | |
|   while (!res2) {
 | |
|     readOptions.limit = limit;
 | |
|     readOptions.limitMessage = limitMessage;
 | |
|     res1 = exports.question(query, readOptions);
 | |
| 
 | |
|     readOptions.limit = [res1, ''];
 | |
|     readOptions.limitMessage = unmatchMessage;
 | |
|     res2 = exports.question(confirmMessage, readOptions);
 | |
|   }
 | |
| 
 | |
|   return res1;
 | |
| };
 | |
| 
 | |
| function _questionNum(query, options, parser) {
 | |
|   var validValue;
 | |
|   function getValidValue(value) {
 | |
|     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.
 | |
|   }));
 | |
|   /* eslint-enable key-spacing */
 | |
|   return validValue;
 | |
| }
 | |
| exports.questionInt = function(query, options) {
 | |
|   return _questionNum(query, options, function(value) { return parseInt(value, 10); });
 | |
| };
 | |
| exports.questionFloat = function(query, options) {
 | |
|   return _questionNum(query, options, parseFloat);
 | |
| };
 | |
| 
 | |
| exports.questionPath = function(query, options) {
 | |
|   /* eslint-disable key-spacing */
 | |
|   var validPath, error = '',
 | |
|     readOptions = margeOptions({
 | |
|       // -------- default
 | |
|       hideEchoBack:       false,
 | |
|       limitMessage:       '$<error(\n)>Input valid path, please.' +
 | |
|                             '$<( Min:)min>$<( Max:)max>',
 | |
|       history:            true,
 | |
|       cd:                 true
 | |
|     }, 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;
 | |
|           }, '');
 | |
|         }
 | |
| 
 | |
|         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;
 | |
|           }
 | |
|           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] + '' : '';
 | |
|       }
 | |
|     });
 | |
|     // added:     exists, create, min, max, isFile, isDirectory, validate
 | |
|   /* eslint-enable key-spacing */
 | |
|   options = options || {};
 | |
| 
 | |
|   if (query == null) { query = 'Input path (you can "cd" and "pwd"): '; }
 | |
| 
 | |
|   exports.question(query, readOptions);
 | |
|   return validPath;
 | |
| };
 | |
| 
 | |
| // props: preCheck, args, hRes, limit
 | |
| function getClHandler(commandHandler, options) {
 | |
|   var clHandler = {}, hIndex = {};
 | |
|   if (typeof commandHandler === 'object') {
 | |
|     Object.keys(commandHandler).forEach(function(cmd) {
 | |
|       if (typeof commandHandler[cmd] === 'function') {
 | |
|         hIndex[options.caseSensitive ? cmd : cmd.toLowerCase()] = commandHandler[cmd];
 | |
|       }
 | |
|     });
 | |
|     clHandler.preCheck = function(res) {
 | |
|       var cmdKey;
 | |
|       clHandler.args = parseCl(res);
 | |
|       cmdKey = clHandler.args[0] || '';
 | |
|       if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); }
 | |
|       clHandler.hRes =
 | |
|         cmdKey !== '_' && hIndex.hasOwnProperty(cmdKey) ?
 | |
|           hIndex[cmdKey].apply(res, clHandler.args.slice(1)) :
 | |
|         hIndex.hasOwnProperty('_') ? hIndex._.apply(res, clHandler.args) : null;
 | |
|       return {res: res, forceNext: false};
 | |
|     };
 | |
|     if (!hIndex.hasOwnProperty('_')) {
 | |
|       clHandler.limit = function() { // It's called after preCheck.
 | |
|         var cmdKey = clHandler.args[0] || '';
 | |
|         if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); }
 | |
|         return hIndex.hasOwnProperty(cmdKey);
 | |
|       };
 | |
|     }
 | |
|   } else {
 | |
|     clHandler.preCheck = function(res) {
 | |
|       clHandler.args = parseCl(res);
 | |
|       clHandler.hRes = typeof commandHandler === 'function' ?
 | |
|         commandHandler.apply(res, clHandler.args) : true; // true for break loop
 | |
|       return {res: res, forceNext: false};
 | |
|     };
 | |
|   }
 | |
|   return clHandler;
 | |
| }
 | |
| 
 | |
| exports.promptCL = function(commandHandler, options) {
 | |
|   /* eslint-disable key-spacing */
 | |
|   var readOptions = margeOptions({
 | |
|       // -------- default
 | |
|       hideEchoBack:       false,
 | |
|       limitMessage:       'Requested command is not available.',
 | |
|       caseSensitive:      false,
 | |
|       history:            true
 | |
|     }, options),
 | |
|       // -------- forced
 | |
|       // 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);
 | |
|   return clHandler.args;
 | |
| };
 | |
| 
 | |
| exports.promptLoop = function(inputHandler, options) {
 | |
|   /* eslint-disable key-spacing */
 | |
|   var readOptions = margeOptions({
 | |
|     // -------- 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,
 | |
|       limitMessage:       'Requested command is not available.',
 | |
|       caseSensitive:      false,
 | |
|       history:            true
 | |
|     }, options),
 | |
|       // -------- forced
 | |
|       // 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) {
 | |
|     exports.prompt(readOptions);
 | |
|     if (clHandler.hRes) { break; }
 | |
|   }
 | |
|   return;
 | |
| };
 | |
| 
 | |
| exports.promptSimShell = function(options) {
 | |
|   /* eslint-disable key-spacing */
 | |
|   return exports.prompt(margeOptions({
 | |
|     // -------- default
 | |
|     hideEchoBack:       false,
 | |
|     history:            true
 | |
|   }, options, {
 | |
|     // -------- forced
 | |
|     prompt:             (function() {
 | |
|       return IS_WIN ?
 | |
|         '$<cwd>>' :
 | |
|         // 'user@host:cwd$ '
 | |
|         (process.env.USER || '') +
 | |
|         (process.env.HOSTNAME ?
 | |
|           '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') +
 | |
|         ':$<cwdHome>$ ';
 | |
|     })()
 | |
|   }));
 | |
|   /* eslint-enable key-spacing */
 | |
| };
 | |
| 
 | |
| function _keyInYN(query, options, limit) {
 | |
|   var res;
 | |
|   if (query == null) { query = 'Are you sure? '; }
 | |
|   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
 | |
|   /* 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.keyInPause = function(query, options) {
 | |
|   if (query == null) { query = 'Continue...'; }
 | |
|   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
 | |
|   /* eslint-enable key-spacing */
 | |
|   return;
 | |
| };
 | |
| 
 | |
| exports.keyInSelect = function(items, query, options) {
 | |
|   /* eslint-disable key-spacing */
 | |
|   var readOptions = margeOptions({
 | |
|       // -------- default
 | |
|       hideEchoBack:       false
 | |
|     }, options, {
 | |
|       // -------- forced
 | |
|       trueValue:          null,
 | |
|       falseValue:         null,
 | |
|       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;
 | |
|       }
 | |
|     }),
 | |
|     // added:     guide, cancel
 | |
|     keylist = '', key2i = {}, charCode = 49 /* '1' */, display = '\n';
 | |
|   /* 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);
 | |
|     keylist += key;
 | |
|     key2i[key] = i;
 | |
|     display += '[' + key + '] ' + (item + '').trim() + '\n';
 | |
|     charCode = charCode === 57 /* '9' */ ? 97 /* 'a' */ : charCode + 1;
 | |
|   });
 | |
|   if (!options || options.cancel !== false) {
 | |
|     keylist += '0';
 | |
|     key2i['0'] = -1;
 | |
|     display += '[0] ' +
 | |
|       (options && options.cancel != null && typeof options.cancel !== 'boolean' ?
 | |
|         (options.cancel + '').trim() : 'CANCEL') + '\n';
 | |
|   }
 | |
|   readOptions.limit = keylist;
 | |
|   display += '\n';
 | |
| 
 | |
|   if (query == null) { query = 'Choose one from list: '; }
 | |
|   if ((query += '')) {
 | |
|     if (!options || options.guide !== false) {
 | |
|       query = query.replace(/\s*:?\s*$/, '') + ' [$<limit>]: ';
 | |
|     }
 | |
|     display += query;
 | |
|   }
 | |
| 
 | |
|   return key2i[exports.keyIn(display, readOptions).toLowerCase()];
 | |
| };
 | |
| 
 | |
| exports.getRawInput = function() { return rawInput; };
 | |
| 
 | |
| // ======== DEPRECATED ========
 | |
| function _setOption(optionName, args) {
 | |
|   var options;
 | |
|   if (args.length) { options = {}; options[optionName] = args[0]; }
 | |
|   return exports.setDefaultOptions(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); };
 |