From 67f3b66480e66f23444a7fed14acb2f34b62044c Mon Sep 17 00:00:00 2001 From: anseki Date: Wed, 24 Feb 2016 01:20:41 +0900 Subject: [PATCH] Support new placeholder syntax. --- README.md | 64 +++++++++++++++++++++++--------------------- lib/readline-sync.js | 47 +++++++++++++++++++------------- package.json | 2 +- 3 files changed, 63 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 95f775c..8ec075d 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ The `query` is handled the same as that of the [`question`](#basic_methods-quest For example: ```js -menuId = readlineSync.keyIn('Hit 1...5 key :', {limit: '${1-5}'}); +menuId = readlineSync.keyIn('Hit 1...5 key :', {limit: '$<1-5>'}); ``` ### `setDefaultOptions` @@ -375,7 +375,7 @@ Accept only the key that matches value that is specified to this, ignore others. Specify the characters as the key. All strings or Array of those are decomposed into single characters. For example, `'abcde'` or `['a', 'bc', ['d', 'e']]` are the same as `['a', 'b', 'c', 'd', 'e']`. These strings are compared with the input. It is affected by [`caseSensitive`](#basic_options-casesensitive) option. -The [placeholders](#placeholders) like `'${a-e}'` are replaced to an Array that is the character list like `['a', 'b', 'c', 'd', 'e']`. +The [placeholders](#placeholders) like `'$'` are replaced to an Array that is the character list like `['a', 'b', 'c', 'd', 'e']`. For example: @@ -385,14 +385,14 @@ direction = readlineSync.keyIn('Left or Right? :', {limit: 'lr'}); // 'l' or 'r' ```js dice = readlineSync.keyIn('Roll the dice, What will the result be? :', - {limit: '${1-6}'}); // range of '1' to '6' + {limit: '$<1-6>'}); // range of '1' to '6' ``` ### `limitMessage` _For `question*` and `prompt*` methods only_ *Type:* string -*Default:* `'Input another, please.${( [)limit(])}'` +*Default:* `'Input another, please.$<( [)limit(])>'` Display this to the user when the [`limit`](#basic_options-limit) option is specified and the user input others. The [placeholders](#placeholders) can be included. @@ -402,7 +402,7 @@ For example: ```js file = readlineSync.question('Name of Text File :', { limit: /\.txt$/i, - limitMessage: 'Sorry, ${lastInput} is not text file.' + limitMessage: 'Sorry, $ is not text file.' }); ``` @@ -720,7 +720,7 @@ The following options have independent default value that is not affected by [De |-------------------|---------------| | [`hideEchoBack`](#basic_options-hideechoback) | `true` | | [`mask`](#basic_options-mask) | `'*'` | -| [`limitMessage`](#basic_options-limitmessage) | `'It can include: ${charlist}\nAnd the length must be: ${length}'` | +| [`limitMessage`](#basic_options-limitmessage) | `'It can include: $\nAnd the length must be: $'` | | [`trueValue`](#basic_options-truevalue_falsevalue) | `null` | | [`falseValue`](#basic_options-truevalue_falsevalue) | `null` | | [`caseSensitive`](#basic_options-casesensitive) | `true` | @@ -736,15 +736,15 @@ And the following additional options are available. ##### `charlist` *Type:* string -*Default:* `'${!-~}'` +*Default:* `'$'` A string as the characters that can be included in the password. For example, if `'abc123'` is specified, the passwords that include any character other than these 6 characters are refused. -The [placeholders](#placeholders) like `'${a-e}'` are replaced to the characters like `'abcde'`. +The [placeholders](#placeholders) like `'$'` are replaced to the characters like `'abcde'`. For example, let the user input a password that is created with alphabet and some symbols: ```js -password = readlineSync.questionNewPassword('PASSWORD :', {charlist: '${a-z}#$@%'}); +password = readlineSync.questionNewPassword('PASSWORD :', {charlist: '$#$@%'}); ``` ##### `min`, `max` @@ -887,7 +887,7 @@ The following options have independent default value that is not affected by [De | Option Name | Default Value | |-------------------|---------------| | [`hideEchoBack`](#basic_options-hideechoback) | `false` | -| [`limitMessage`](#basic_options-limitmessage) | `'${error(\n)}Input valid path, please.${( Min:)min}${( Max:)max}'` | +| [`limitMessage`](#basic_options-limitmessage) | `'$Input valid path, please.$<( Min:)min>$<( Max:)max>'` | | [`history`](#basic_options-history) | `true` | | [`cd`](#basic_options-cd) | `true` | @@ -1449,7 +1449,7 @@ If `true` is specified, a string like `'[1...5]'` as guide for the user is added *Default:* `'CANCEL'` If a value other than `false` is specified, an item to let the user tell "cancel" is added to the item list. "[0] CANCEL" (default) is displayed, and if `0` key is pressed, `-1` is returned. -You can specify a label of this item other than `'CANCEL'`. A string such as `'Go back'` (empty string `''` also), something that is converted to string such as `Date`, a string that includes [placeholder](#placeholders) such as `'Next ${itemsCount} items'` are accepted. +You can specify a label of this item other than `'CANCEL'`. A string such as `'Go back'` (empty string `''` also), something that is converted to string such as `Date`, a string that includes [placeholder](#placeholders) such as `'Next $ items'` are accepted. #### Additional Placeholders @@ -1464,7 +1464,7 @@ For example: ```js items = ['item-A', 'item-B', 'item-C', 'item-D', 'item-E']; index = readlineSync.keyInSelect(items, null, - {cancel: 'Show more items than ${itemsCount}'}); + {cancel: 'Show more items than $'}); ``` ```console @@ -1483,7 +1483,7 @@ A first item in a current `items` Array. For example: ```js -index = readlineSync.keyInSelect(items, 'Choose ${firstItem} or another :'); +index = readlineSync.keyInSelect(items, 'Choose $ or another :'); ``` ##### `lastItem` @@ -1495,7 +1495,7 @@ For example: ```js items = ['January', 'February', 'March', 'April', 'May', 'June']; index = readlineSync.keyInSelect(items, null, - {cancel: 'In after ${lastItem}'}); + {cancel: 'In after $'}); ``` ```console @@ -1519,7 +1519,7 @@ For example, the [`limitMessage`](#basic_options-limitmessage) option to display ```js command = readlineSync.prompt({ limit: ['add', 'remove'], - limitMessage: '${lastInput} is not available.' + limitMessage: '$ is not available.' }); ``` @@ -1538,18 +1538,18 @@ The placeholders can be included in: ### Syntax ``` -${parameter} +$ ``` Or ``` -${(text1)parameter(text2)} +$<(text1)parameter(text2)> ``` The placeholder is replaced to a string that is got by a `parameter`. Both the `(text1)` and `(text2)` are optional. -A more added `'$'` at the left of the placeholder is used as an escape character, it disables a placeholder. For example, `'$${foo}'` is replaced to `'${foo}'`. If you want to put a `'$'` which is *not* an escape character at the left of a placeholder, specify it like `'${($)bufferSize}'`, then it is replaced to `'$1024'`. +A more added `'$'` at the left of the placeholder is used as an escape character, it disables a placeholder. For example, `'$$'` is replaced to `'$'`. If you want to put a `'$'` which is *not* an escape character at the left of a placeholder, specify it like `'$<($)bufferSize>'`, then it is replaced to `'$1024'`. At the each position of `'(text1)'` and `'(text2)'`, `'text1'` and `'text2'` are put when a string that was got by a `parameter` has length more than 0. If that got string is `''`, a placeholder with or without `'(text1)'` and `'(text2)'` is replaced to `''`. @@ -1558,7 +1558,7 @@ For example, a warning message that means that the command the user requested is ```js command = readlineSync.prompt({ limit: ['add', 'remove'], - limitMessage: 'Refused ${lastInput} you requested. Please input another.' + limitMessage: 'Refused $ you requested. Please input another.' }); ``` @@ -1580,7 +1580,7 @@ This goes well: ```js command = readlineSync.prompt({ limit: ['add', 'remove'], - limitMessage: 'Refused ${lastInput( you requested)}. Please input another.' + limitMessage: 'Refused $. Please input another.' }); ``` @@ -1589,7 +1589,9 @@ command = readlineSync.prompt({ Refused . Please input another. ``` -(May be more better: `'${(Refused )lastInput( you requested. )}Please input another.'`) +(May be more better: `'$<(Refused )lastInput( you requested. )>Please input another.'`) + +*Note:* The syntax `${parameter}` of older version is still supported, but this should not be used because it may be confused with ES6. And this will not be supported in due course of time. ### Parameters @@ -1599,13 +1601,13 @@ The following parameters are available. And some additional parameters are avail A current value of each option. It is converted to human readable if possible. The boolean value is replaced to `'on'` or `'off'`, and the Array is replaced to the list of only string and number elements. -And in the `keyIn*` method, the parts of the list as characters sequence are suppressed. For example, when `['a', 'b', 'c', 'd', 'e']` is specified to the [`limit`](#basic_options-limit) option, `'${limit}'` is replaced to `'a...e'`. If `true` is specified to the [`caseSensitive`](#basic_options-casesensitive) option, the characters are converted to lower case. +And in the `keyIn*` method, the parts of the list as characters sequence are suppressed. For example, when `['a', 'b', 'c', 'd', 'e']` is specified to the [`limit`](#basic_options-limit) option, `'$'` is replaced to `'a...e'`. If `true` is specified to the [`caseSensitive`](#basic_options-casesensitive) option, the characters are converted to lower case. For example: ```js input = readlineSync.question( - 'Input something or the Enter key as "${defaultInput}" :', + 'Input something or the Enter key as "$" :', {defaultInput: 'hello'} ); ``` @@ -1617,13 +1619,13 @@ Input something or the Enter key as "hello" : #### `limitCount`, `limitCountNotZero` A length of a current value of the [`limit`](#basic_options-limit) option. -When the value of the [`limit`](#basic_options-limit) option is empty, `'${limitCount}'` is replaced to `'0'`, `'${limitCountNotZero}'` is replaced to `''`. +When the value of the [`limit`](#basic_options-limit) option is empty, `'$'` is replaced to `'0'`, `'$'` is replaced to `''`. For example: ```js action = readlineSync.question( - 'Choose action${( from )limitCountNotZero( actions)} :', + 'Choose action$<( from )limitCountNotZero( actions)> :', {limit: availableActions} ); ``` @@ -1642,7 +1644,7 @@ For example: ```js command = readlineSync.prompt({ limit: availableCommands, - limitMessage: '${lastInput} is not available.' + limitMessage: '$ is not available.' }); ``` @@ -1660,7 +1662,7 @@ For example: ```js while (true) { - input = readlineSync.question('Something${( or "!!" as ")history_m1(")} :'); + input = readlineSync.question('Something$<( or "!!" as ")history_m1(")> :'); console.log('-- You said "' + input + '"'); } ``` @@ -1684,7 +1686,7 @@ A current working directory. For example, like bash/zsh: ```js -command = readlineSync.prompt({prompt: '[${cwdHome}]$ '}); +command = readlineSync.prompt({prompt: '[$]$ '}); ``` ```console @@ -1703,7 +1705,7 @@ A string as current date or time. For example: ```js -command = readlineSync.prompt({prompt: '[${localeDate}]> '}); +command = readlineSync.prompt({prompt: '[$]> '}); ``` ```console @@ -1715,12 +1717,12 @@ command = readlineSync.prompt({prompt: '[${localeDate}]> '}); _For [`limit` option for `keyIn*` method](#basic_options-limit-for_keyin_method) and [`charlist`](#utility_methods-questionnewpassword-options-charlist) option for [`questionNewPassword`](#utility_methods-questionnewpassword) method only_ A character list. -`C1` and `C2` are each single character as the start and the end. A sequence in ascending or descending order of characters ranging from `C1` to `C2` is created. For example, `'${a-e}'` is replaced to `'abcde'`. `'${5-1}'` is replaced to `'54321'`. +`C1` and `C2` are each single character as the start and the end. A sequence in ascending or descending order of characters ranging from `C1` to `C2` is created. For example, `'$'` is replaced to `'abcde'`. `'$<5-1>'` is replaced to `'54321'`. For example, let the user input a password that is created with alphabet: ```js -password = readlineSync.questionNewPassword('PASSWORD :', {charlist: '${a-z}'}); +password = readlineSync.questionNewPassword('PASSWORD :', {charlist: '$'}); ``` See also [`limit` option for `keyIn*` method](#basic_options-limit-for_keyin_method). diff --git a/lib/readline-sync.js b/lib/readline-sync.js index d6d3741..2d49ca6 100644 --- a/lib/readline-sync.js +++ b/lib/readline-sync.js @@ -25,7 +25,7 @@ var hideEchoBack: false, mask: '*', limit: [], - limitMessage: 'Input another, please.${( [)limit(])}', + limitMessage: 'Input another, please.$<( [)limit(])>', defaultInput: '', trueValue: [], falseValue: [], @@ -430,6 +430,11 @@ function flattenArray(array, validator) { return flatArray; } +function escapePattern(pattern) { + return pattern.replace(/[\x00-\x7f]/g, + 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. @@ -545,17 +550,23 @@ function replaceHomePath(path, expand) { process.env.HOME || '').replace(/[\/\\]+$/, ''); path = pathUtil.normalize(path); return expand ? path.replace(/^~(?=\/|\\|$)/, homePath) : - path.replace(new RegExp('^' + homePath.replace(/[^A-Za-z0-9_ ]/g, '\\$&') + + path.replace(new RegExp('^' + escapePattern(homePath) + '(?=\\/|\\\\|$)', IS_WIN ? 'i' : ''), '~'); } function replacePlaceholder(text, generator) { - return text.replace(/(\$)?(\$\{(?:\(([\s\S]*?)\))?(\w+|.-.)(?:\(([\s\S]*?)\))?\})/g, - function(str, escape, placeholder, pre, param, post) { - var text; - return escape || typeof(text = generator(param)) !== 'string' ? placeholder : - text ? (pre || '') + text + (post || '') : ''; - }); + 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) { @@ -824,7 +835,7 @@ exports.keyIn = function(query, options) { }) .map(function(text) { return replacePlaceholder(text + '', getPhCharlist); }); // pattern - readOptions.limit = readOptions.limitSrc.join('').replace(/[^A-Za-z0-9_ ]/g, '\\$&'); + readOptions.limit = escapePattern(readOptions.limitSrc.join('')); ['trueValue', 'falseValue'].forEach(function(optionName) { readOptions[optionName] = readOptions[optionName].reduce(function(comps, comp) { @@ -868,8 +879,8 @@ exports.questionNewPassword = function(query, options) { // -------- default hideEchoBack: true, mask: '*', - limitMessage: 'It can include: ${charlist}\n' + - 'And the length must be: ${length}', + limitMessage: 'It can include: $\n' + + 'And the length must be: $', trueValue: null, falseValue: null, caseSensitive: true @@ -889,10 +900,10 @@ exports.questionNewPassword = function(query, options) { options = options || {}; charlist = replacePlaceholder( - options.charlist ? options.charlist + '' : '${!-~}', getPhCharlist); + 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('^[' + charlist.replace(/[^A-Za-z0-9_ ]/g, '\\$&') + + limit = new RegExp('^[' + escapePattern(charlist) + ']{' + min + ',' + max + '}$'); resCharlist = array2charlist([charlist], readOptions.caseSensitive, true); resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed); @@ -951,8 +962,8 @@ exports.questionPath = function(query, options) { var readOptions = margeOptions({ // -------- default hideEchoBack: false, - limitMessage: '${error(\n)}Input valid path, please.' + - '${( Min:)min}${( Max:)max}', + limitMessage: '$Input valid path, please.' + + '$<( Min:)min>$<( Max:)max>', history: true, cd: true }, options, { @@ -1139,12 +1150,12 @@ exports.promptSimShell = function(options) { // -------- forced prompt: (function() { return IS_WIN ? - '${cwd}>' : + '$>' : // 'user@host:cwd$ ' (process.env.USER || '') + (process.env.HOSTNAME ? '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') + - ':${cwdHome}$ '; + ':$$ '; })() })); }; @@ -1235,7 +1246,7 @@ exports.keyInSelect = function(items, query, options) { /* jshint eqnull:false */ if ((query += '')) { if (!options || options.guide !== false) - { query = query.replace(/\s*:?\s*$/, '') + ' [${limit}] :'; } + { query = query.replace(/\s*:?\s*$/, '') + ' [$] :'; } display += query; } diff --git a/package.json b/package.json index cbab78e..3c576e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "readline-sync", - "version": "1.3.1", + "version": "1.4.0", "title": "readlineSync", "description": "Synchronous Readline for interactively running to have a conversation with the user via a console(TTY).", "keywords": [