diff --git a/lib/readline-sync.js b/lib/readline-sync.js index 24a702c..c2efd8b 100644 --- a/lib/readline-sync.js +++ b/lib/readline-sync.js @@ -25,6 +25,7 @@ var mask: '*', limit: [], limitMessage: 'Input another, please.${( [)limit(])}', + defaultInput: '', trueValue: [], falseValue: [], caseSensitive: false, @@ -32,7 +33,9 @@ var encoding: 'utf8', bufferSize: 1024, print: void 0, - history: true + history: true, + cd: false, + phContent: void 0 }, fdR = 'none', fdW, ttyR, isRawMode = false, @@ -448,15 +451,16 @@ function margeOptions() { if (!optionsPart.hasOwnProperty(optionName)) { return; } value = optionsPart[optionName]; switch (optionName) { - // _readlineSync defaultOptions + // _readlineSync <- * * -> defaultOptions // ================ string case 'mask': // * * case 'limitMessage': // * + case 'defaultInput': // * case 'encoding': // * * /* jshint eqnull:true */ value = value != null ? value + '' : ''; /* jshint eqnull:false */ - if (value && optionName === 'mask' || optionName === 'encoding') + if (value && optionName !== 'limitMessage') { value = value.replace(/[\r\n]/g, ''); } options[optionName] = value; break; @@ -472,6 +476,7 @@ function margeOptions() { case 'caseSensitive': // * * case 'keepWhitespace': // * * case 'history': // * + case 'cd': // * options[optionName] = !!value; break; // ================ array @@ -488,6 +493,7 @@ function margeOptions() { break; // ================ function case 'print': // * * + case 'phContent': // * options[optionName] = typeof value === 'function' ? value : void 0; break; // ================ other @@ -539,7 +545,7 @@ function array2charlist(array, caseSensitive, collectSymbols) { { return chars.concat((value + '').split('')); }, []) .reduce(function(groups, curChar) { var curGroupClass, curCharCode; - if (!caseSensitive) { curChar = curChar.toUpperCase(); } + 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) { @@ -567,54 +573,80 @@ function joinChunks(chunks, suppressed) { return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/'); } function getPhContent(param, options) { - var text, values, resCharlist = {}, arg; - switch (param) { - case 'hideEchoBack': - case 'mask': - case 'caseSensitive': - case 'keepWhitespace': - case 'encoding': - case 'bufferSize': - case 'history': - text = options.hasOwnProperty(param) ? options[param] + '' : ''; - break; - case 'prompt': - case 'query': - case 'display': - text = options.displaySrc + ''; - break; - case 'limit': - case 'trueValue': - case 'falseValue': - values = options[options.hasOwnProperty(param + 'Src') ? param + 'Src' : param]; - if (options.keyIn) { // suppress - 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': - text = process.cwd(); - if (param === 'CWD') { text = require('path').basename(text); } - break; - default: // with arg - if (typeof(arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') { - text = inputHistory[inputHistory.length - arg] || ''; - } + var text, values, resCharlist = {}, path, 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 = require('path').basename(text); } + else if (param === 'cwdHome') { + path = (IS_WIN ? ((process.env.HOMEDRIVE || '') + + (process.env.HOMEPATH || '')).toLowerCase() : + process.env.HOME || '').replace(/[\/\\]+$/, ''); + if (path && (IS_WIN ? text.toLowerCase() : text).indexOf(path) === 0) + { text = '~' + text.substr(path.length); } + } + 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; } @@ -630,7 +662,7 @@ function getPhCharlist(param) { return text; } -function readlineWithOptions(options, preCheck) { +function getValidLine(options, preCheck) { var res, forceNext, resCheck, limitMessage, matches, histInput; function _getPhContent(param) { return getPhContent(param, options); } @@ -706,11 +738,11 @@ exports.setDefault = function(options) { exports.prompt = function(options) { var readOptions = margeOptions(true, options); readOptions.display = readOptions.prompt; - return readlineWithOptions(readOptions); + return getValidLine(readOptions); }; exports.question = function(query, options) { - return readlineWithOptions(margeOptions(margeOptions(true, options), { + return getValidLine(margeOptions(margeOptions(true, options), { display: query })); }; @@ -767,6 +799,7 @@ exports.questionEMail = function(query, options) { }, options, { // -------- forced keepWhitespace: false, + cd: false })); }; @@ -780,159 +813,183 @@ exports.questionNewPassword = function(query, options) { trueValue: null, falseValue: null, caseSensitive: true - }, options), - // forced: limit + }, 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, confirm - charlist, min, max, confirm, resCharlist, res1, res2, limit1, limitMessage1; + charlist, min, max, confirm, limit, resCharlist, limitMessage, res1, res2; - function phSpecial(param) { - return param === 'charlist' ? resCharlist.text : - param === 'length' ? min + '...' + max : null; - } - - // bug? `eqnull:true` doesn't work - /* jshint ignore:start */ - if (query == null) { query = 'Input new password :'; } - /* jshint ignore:end */ - - charlist = options && options.charlist ? options.charlist + '' : '${!-~}'; - charlist = replacePlaceholder(charlist, getPhCharlist); + charlist = replacePlaceholder( + options && options.charlist ? options.charlist + '' : '${!-~}', getPhCharlist); if (options) { min = options.min; max = options.max; } if (isNaN(min = parseInt(min, 10)) || typeof min !== 'number') { min = 12; } if (isNaN(max = parseInt(max, 10)) || typeof max !== 'number') { max = 24; } + limit = new RegExp('^[' + charlist.replace(/[^A-Za-z0-9_ ]/g, '\\$&') + + ']{' + min + ',' + max + '}$'); + resCharlist = array2charlist([charlist], readOptions.caseSensitive, true); + resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed); + /* jshint eqnull:true */ confirm = options && options.confirm != null ? options.confirm : 'Reinput same one to confirm it :'; /* jshint eqnull:false */ - limit1 = new RegExp('^[' + charlist.replace(/[^A-Za-z0-9_ ]/g, '\\$&') + - ']{' + min + ',' + max + '}$'); - - if (readOptions.limitMessage) { - resCharlist = array2charlist([charlist], readOptions.caseSensitive, true); - resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed); - // getPhContent is called by readlineWithOptions - limitMessage1 = replacePlaceholder(readOptions.limitMessage, phSpecial); - } + /* jshint eqnull:true */ + if (query == null) { query = 'Input new password :'; } + /* jshint eqnull:false */ + limitMessage = readOptions.limitMessage; while (!res2) { - readOptions.limit = limit1; - readOptions.limitMessage = limitMessage1; + readOptions.limit = limit; + readOptions.limitMessage = limitMessage; res1 = exports.question(query, readOptions); readOptions.limit = [res1, '']; readOptions.limitMessage = 'Two passwords don\'t match.' + ' Hit only Enter key if you want to retry from first password.'; - // getPhContent is called by readlineWithOptions - res2 = exports.question(replacePlaceholder(confirm, phSpecial), readOptions); + res2 = exports.question(confirm, readOptions); } return res1; }; -function _questionParse(query, options, fncParse) { +function _questionNum(query, options, parser) { var validValue; function getValidValue(value) { - validValue = fncParse(value); + validValue = parser(value); return !isNaN(validValue) && typeof validValue === 'number'; } - exports.question(query, margeOptions(options, { + exports.question(query, margeOptions({ + // -------- default + limitMessage: 'Input valid number, please.' + }, options, { // -------- forced - limit: getValidValue + limit: getValidValue, + cd: false // trueValue, falseValue are don't work. })); return validValue; } exports.questionInt = function(query, options) { - return _questionParse(query, options, function(value) { return parseInt(value, 10); }); + return _questionNum(query, options, function(value) { return parseInt(value, 10); }); +}; +exports.questionFloat = function(query, options) { + return _questionNum(query, options, parseFloat); }; -exports.questionFloat = function(query, options) - { return _questionParse(query, options, parseFloat); }; exports.questionPath = function(query, options) { - var fs = require('fs'), validPath; - function getValidPath(value) { - var stat; - if (!fs.existsSync(value)) { return; } - validPath = fs.realpathSync(value); - if (options.minSize || options.maxSize || options.isFile || options.isDirectory) { - stat = fs.statSync(validPath); - if (options.minSize && stat.size < +options.minSize || - options.maxSize && stat.size > +options.maxSize || - options.isFile && !stat.isFile() || - options.isDirectory && !stat.isDirectory()) { return false; } - } - if (typeof options.validate === 'function' && !options.validate(validPath)) - { return false; } - return true; - } - exports.question(query, margeOptions(options, { + var readOptions = margeOptions({ + // -------- default + limitMessage: 'Input valid path, please.${( Min:)minSize}${( Max:)maxSize}', + history: true, + cd: true + }, options, { // -------- forced - limit: getValidPath + keepWhitespace: false, + limit: function(value) { + var stat; + if (!fs.existsSync(value)) { return; } + validPath = fs.realpathSync(value); + if (options) { + if (options.minSize || options.maxSize || + options.isFile || options.isDirectory) { + stat = fs.statSync(validPath); + if (options.minSize && stat.size < +options.minSize || + options.maxSize && stat.size > +options.maxSize || + options.isFile && !stat.isFile() || + options.isDirectory && !stat.isDirectory()) { return; } + } + if (typeof options.validate === 'function' && !options.validate(validPath)) + { return; } + } + return true; + }, // trueValue, falseValue are don't work. - })); + phContent: function(param) { + return param !== 'minSize' && param !== 'maxSize' ? null : + options && options.hasOwnProperty(param) ? options[param] + '' : ''; + } + }), // added: minSize, maxSize, isFile, isDirectory, validate + validPath; + + /* jshint eqnull:true */ + if (query == null) { query = 'Input path (you can "cd" and "pwd") :'; } + /* jshint eqnull:false */ + + exports.question(query, readOptions); return validPath; }; -exports.promptCWD = function(options) { - var readOptions = margeOptions({ +exports.promptSimShell = function(options) { + return exports.prompt(margeOptions({ // -------- default - prompt: '[${cwd}]$ ' - }, options); - return exports.prompt(readOptions); + history: true + }, options, { + // -------- forced + prompt: (function() { + return IS_WIN ? + '${cwd}>' : + // 'user@host:cwd$ ' + (process.env.USER || '') + + (process.env.HOSTNAME ? + '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') + + ':${cwdHome}$ '; + })() + })); }; -function _keyInYN(query, options, limit) { - var readOptions = margeOptions(options, { - // -------- forced - hideEchoBack: false, - limit: limit, - trueValue: 'y', - falseValue: 'n', - caseSensitive: false - }), res; - +exports.keyInYN = function(query, options) { + var res; /* jshint eqnull:true */ if (query == null) { query = 'Are you sure? :'; } /* jshint eqnull:false */ - if ((query += '') && options.guide !== false) - { query = query.replace(/\s*:?\s*$/, '') + ' [Y/N] :'; } - - res = exports.keyIn(query, readOptions); + if ((!options || options.guide !== false) && (query += '')) + { query = query.replace(/\s*:?\s*$/, '') + ' [y/n] :'; } + res = exports.keyIn(query, margeOptions(options, { + // -------- forced + hideEchoBack: false, + limit: options && options.strict ? 'yn' : null, + trueValue: 'y', + falseValue: 'n', + caseSensitive: false + })); 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) { - var readOptions = margeOptions(options, { - // -------- forced - hideEchoBack: true, - mask: '' - }); - /* jshint eqnull:true */ if (query == null) { query = 'Continue...'; } /* jshint eqnull:false */ - if ((query += '') && options.guide !== false) + if ((!options || options.guide !== false) && (query += '')) { query = query.replace(/\s+$/, '') + ' (Hit any key)'; } - - exports.keyIn(query, readOptions); + exports.keyIn(query, margeOptions(options, { + // -------- forced + hideEchoBack: true, + mask: '' + })); return; }; exports.keyInSelect = function(query, items, options) { var readOptions = margeOptions({ // -------- default - hideEchoBack: false, + hideEchoBack: false }, options, { // -------- forced trueValue: null, falseValue: null, caseSensitive: false - }), keylist = '', key2i = {}, charCode = 49 /* '1' */, display = '\n'; + // limit (by items) + }), + keylist = '', key2i = {}, charCode = 49 /* '1' */, display = '\n'; if (!Array.isArray(items) || items.length > 35) { throw '`items` must be Array (max length: 35).'; } @@ -941,9 +998,9 @@ exports.keyInSelect = function(query, items, options) { keylist += key; key2i[key] = i; display += '[' + key + '] ' + item.trim() + '\n'; - charCode = charCode === 57 /* '9' */ ? 65 /* 'A' */ : charCode + 1; + charCode = charCode === 57 /* '9' */ ? 97 /* 'a' */ : charCode + 1; }); - if (options.cancel !== false) { + if (!options || options.cancel !== false) { keylist += '0'; key2i['0'] = -1; display += '[' + '0' + '] CANCEL\n'; @@ -955,12 +1012,12 @@ exports.keyInSelect = function(query, items, options) { if (query == null) { query = 'Choose one from list :'; } /* jshint eqnull:false */ if ((query += '')) { - if (options.guide !== false) + if (!options || options.guide !== false) { query = query.replace(/\s*:?\s*$/, '') + ' [${limit}] :'; } display += query; } - return key2i[exports.keyIn(display, readOptions).toUpperCase()]; + return key2i[exports.keyIn(display, readOptions).toLowerCase()]; }; // ======== DEPRECATED ========