├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README-Deprecated.md ├── README.md ├── lib ├── encrypt.js ├── read.ps1 ├── read.sh ├── read.wsh.js └── readline-sync.js ├── package.json ├── screen_01.png ├── screen_02.gif └── screen_03.gif /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "../../_common/files/eslintrc.json", 4 | "env": {"node": true}, 5 | "rules": { 6 | "no-var": "off", 7 | "prefer-arrow-callback": "off", 8 | "object-shorthand": "off", 9 | "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"]}], 10 | "camelcase": 0 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 anseki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README-Deprecated.md: -------------------------------------------------------------------------------- 1 | # readlineSync 2 | 3 | ## Deprecated Methods and Options 4 | 5 | The readlineSync current version is fully compatible with older version. 6 | The following methods and options are deprecated. 7 | 8 | ### `setPrint` method 9 | 10 | Use the [`print`](README.md#basic_options-print) option. 11 | For the [Default Options](README.md#basic_options), use: 12 | 13 | ```js 14 | readlineSync.setDefaultOptions({print: value}); 15 | ``` 16 | 17 | instead of: 18 | 19 | ```js 20 | readlineSync.setPrint(value); 21 | ``` 22 | 23 | ### `setPrompt` method 24 | 25 | Use the [`prompt`](README.md#basic_options-prompt) option. 26 | For the [Default Options](README.md#basic_options), use: 27 | 28 | ```js 29 | readlineSync.setDefaultOptions({prompt: value}); 30 | ``` 31 | 32 | instead of: 33 | 34 | ```js 35 | readlineSync.setPrompt(value); 36 | ``` 37 | 38 | ### `setEncoding` method 39 | 40 | Use the [`encoding`](README.md#basic_options-encoding) option. 41 | For the [Default Options](README.md#basic_options), use: 42 | 43 | ```js 44 | readlineSync.setDefaultOptions({encoding: value}); 45 | ``` 46 | 47 | instead of: 48 | 49 | ```js 50 | readlineSync.setEncoding(value); 51 | ``` 52 | 53 | ### `setMask` method 54 | 55 | Use the [`mask`](README.md#basic_options-mask) option. 56 | For the [Default Options](README.md#basic_options), use: 57 | 58 | ```js 59 | readlineSync.setDefaultOptions({mask: value}); 60 | ``` 61 | 62 | instead of: 63 | 64 | ```js 65 | readlineSync.setMask(value); 66 | ``` 67 | 68 | ### `setBufferSize` method 69 | 70 | Use the [`bufferSize`](README.md#basic_options-buffersize) option. 71 | For the [Default Options](README.md#basic_options), use: 72 | 73 | ```js 74 | readlineSync.setDefaultOptions({bufferSize: value}); 75 | ``` 76 | 77 | instead of: 78 | 79 | ```js 80 | readlineSync.setBufferSize(value); 81 | ``` 82 | 83 | ### `noEchoBack` option 84 | 85 | Use [`hideEchoBack`](README.md#basic_options-hideechoback) option instead of it. 86 | 87 | ### `noTrim` option 88 | 89 | Use [`keepWhitespace`](README.md#basic_options-keepwhitespace) option instead of it. 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # readlineSync 2 | 3 | [![npm](https://img.shields.io/npm/v/readline-sync.svg)](https://www.npmjs.com/package/readline-sync) [![GitHub issues](https://img.shields.io/github/issues/anseki/readline-sync.svg)](https://github.com/anseki/readline-sync/issues) [![dependencies](https://img.shields.io/badge/dependencies-No%20dependency-brightgreen.svg)](package.json) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 4 | 5 | **Now, ECMAScript supports `Promise`, `await` and `async`.** 6 | 7 | Synchronous [Readline](http://nodejs.org/api/readline.html) for interactively running to have a conversation with the user via a console(TTY). 8 | 9 | readlineSync tries to let your script have a conversation with the user via a console, even when the input/output stream is redirected like `your-script bar.log`. 10 | 11 | 12 | 13 |
Basic OptionsUtility MethodsPlaceholders
14 | 15 | * Simple case: 16 | 17 | ```js 18 | var readlineSync = require('readline-sync'); 19 | 20 | // Wait for user's response. 21 | var userName = readlineSync.question('May I have your name? '); 22 | console.log('Hi ' + userName + '!'); 23 | 24 | // Handle the secret text (e.g. password). 25 | var favFood = readlineSync.question('What is your favorite food? ', { 26 | hideEchoBack: true // The typed text on screen is hidden by `*` (default). 27 | }); 28 | console.log('Oh, ' + userName + ' loves ' + favFood + '!'); 29 | ``` 30 | 31 | ```console 32 | May I have your name? CookieMonster 33 | Hi CookieMonster! 34 | What is your favorite food? **** 35 | Oh, CookieMonster loves tofu! 36 | ``` 37 | 38 | * Get the user's response by a single key without the Enter key: 39 | 40 | ```js 41 | var readlineSync = require('readline-sync'); 42 | if (readlineSync.keyInYN('Do you want this module?')) { 43 | // 'Y' key was pressed. 44 | console.log('Installing now...'); 45 | // Do something... 46 | } else { 47 | // Another key was pressed. 48 | console.log('Searching another...'); 49 | // Do something... 50 | } 51 | ``` 52 | 53 | * Let the user choose an item from a list: 54 | 55 | ```js 56 | var readlineSync = require('readline-sync'), 57 | animals = ['Lion', 'Elephant', 'Crocodile', 'Giraffe', 'Hippo'], 58 | index = readlineSync.keyInSelect(animals, 'Which animal?'); 59 | console.log('Ok, ' + animals[index] + ' goes to your room.'); 60 | ``` 61 | 62 | ```console 63 | [1] Lion 64 | [2] Elephant 65 | [3] Crocodile 66 | [4] Giraffe 67 | [5] Hippo 68 | [0] CANCEL 69 | 70 | Which animal? [1...5 / 0]: 2 71 | Ok, Elephant goes to your room. 72 | ``` 73 | 74 | * An UI like the Range Slider: 75 | (Press `Z` or `X` key to change a value, and Space Bar to exit) 76 | 77 | ```js 78 | var readlineSync = require('readline-sync'), 79 | MAX = 60, MIN = 0, value = 30, key; 80 | console.log('\n\n' + (new Array(20)).join(' ') + 81 | '[Z] <- -> [X] FIX: [SPACE]\n'); 82 | while (true) { 83 | console.log('\x1B[1A\x1B[K|' + 84 | (new Array(value + 1)).join('-') + 'O' + 85 | (new Array(MAX - value + 1)).join('-') + '| ' + value); 86 | key = readlineSync.keyIn('', 87 | {hideEchoBack: true, mask: '', limit: 'zx '}); 88 | if (key === 'z') { if (value > MIN) { value--; } } 89 | else if (key === 'x') { if (value < MAX) { value++; } } 90 | else { break; } 91 | } 92 | console.log('\nA value the user requested: ' + value); 93 | ``` 94 | 95 | ![sample](screen_03.gif) 96 | 97 | * Handle the commands repeatedly, such as the shell interface: 98 | 99 | ```js 100 | readlineSync.promptCLLoop({ 101 | add: function(target, into) { 102 | console.log(target + ' is added into ' + into + '.'); 103 | // Do something... 104 | }, 105 | remove: function(target) { 106 | console.log(target + ' is removed.'); 107 | // Do something... 108 | }, 109 | bye: function() { return true; } 110 | }); 111 | console.log('Exited'); 112 | ``` 113 | 114 | ```console 115 | > add pic01.png archive 116 | pic01.png is added into archive. 117 | > delete pic01.png 118 | Requested command is not available. 119 | > remove pic01.png 120 | pic01.png is removed. 121 | > bye 122 | Exited 123 | ``` 124 | 125 | ## Installation 126 | 127 | ```console 128 | npm install readline-sync 129 | ``` 130 | 131 | ## Quick Start 132 | 133 | **How does the user input?** 134 | 135 | - [Type a reply to a question, and press the Enter key](#quick_start-a) (A) 136 | - [Type a keyword like a command in prompt, and press the Enter key](#quick_start-b) (B) 137 | - [Press a single key without the Enter key](#quick_start-c) (C) 138 | 139 | **(A) What does the user input?** 140 | 141 | - [E-mail address](#utility_methods-questionemail) 142 | - [New password](#utility_methods-questionnewpassword) 143 | - [Integer number](#utility_methods-questionint) 144 | - [Floating-point number](#utility_methods-questionfloat) 145 | - [Local file/directory path](#utility_methods-questionpath) 146 | - [Others](#basic_methods-question) 147 | 148 | **(B) What does your script do?** 149 | 150 | - [Receive a parsed command-name and arguments](#utility_methods-promptcl) 151 | - [Receive an input repeatedly](#utility_methods-promptloop) 152 | - [Receive a parsed command-name and arguments repeatedly](#utility_methods-promptclloop) 153 | - [Receive an input with prompt that is similar to that of the user's shell](#utility_methods-promptsimshell) 154 | - [Others](#basic_methods-prompt) 155 | 156 | **(C) What does the user do?** 157 | 158 | - [Say "Yes" or "No"](#utility_methods-keyinyn) 159 | - [Say "Yes" or "No" explicitly](#utility_methods-keyinynstrict) 160 | - [Make the running of script continue when ready](#utility_methods-keyinpause) 161 | - [Choose an item from a list](#utility_methods-keyinselect) 162 | - [Others](#basic_methods-keyin) 163 | 164 | ## Basic Methods 165 | 166 | These are used to control details of the behavior. It is recommended to use the [Utility Methods](#utility_methods) instead of Basic Methods if it satisfy your request. 167 | 168 | ### `question` 169 | 170 | ```js 171 | answer = readlineSync.question([query[, options]]) 172 | ``` 173 | 174 | Display a `query` to the user if it's specified, and then return the input from the user after it has been typed and the Enter key was pressed. 175 | You can specify an `options` (see [Basic Options](#basic_options)) to control the behavior (e.g. refusing unexpected input, avoiding trimming white spaces, etc.). **If you let the user input the secret text (e.g. password), you should consider [`hideEchoBack`](#basic_options-hideechoback) option.** 176 | 177 | The `query` may be string, or may not be (e.g. number, Date, Object, etc.). It is converted to string (i.e. `toString` method is called) before it is displayed. (see [Note](#note) also) 178 | It can include the [placeholders](#placeholders). 179 | 180 | For example: 181 | 182 | ```js 183 | program = readlineSync.question('Which program starts do you want? ', { 184 | defaultInput: 'firefox' 185 | }); 186 | ``` 187 | 188 | ### `prompt` 189 | 190 | ```js 191 | input = readlineSync.prompt([options]) 192 | ``` 193 | 194 | Display a prompt-sign (see [`prompt`](#basic_options-prompt) option) to the user, and then return the input from the user after it has been typed and the Enter key was pressed. 195 | You can specify an `options` (see [Basic Options](#basic_options)) to control the behavior (e.g. refusing unexpected input, avoiding trimming white spaces, etc.). 196 | 197 | For example: 198 | 199 | ```js 200 | while (true) { 201 | command = readlineSync.prompt(); 202 | // Do something... 203 | } 204 | ``` 205 | 206 | ### `keyIn` 207 | 208 | ```js 209 | pressedKey = readlineSync.keyIn([query[, options]]) 210 | ``` 211 | 212 | Display a `query` to the user if it's specified, and then return a character as a key immediately it was pressed by the user, **without pressing the Enter key**. Note that the user has no chance to change the input. 213 | You can specify an `options` (see [Basic Options](#basic_options)) to control the behavior (e.g. ignoring keys except some keys, checking target key, etc.). 214 | 215 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 216 | 217 | For example: 218 | 219 | ```js 220 | menuId = readlineSync.keyIn('Hit 1...5 key: ', {limit: '$<1-5>'}); 221 | ``` 222 | 223 | ### `setDefaultOptions` 224 | 225 | ```js 226 | currentDefaultOptions = readlineSync.setDefaultOptions([newDefaultOptions]) 227 | ``` 228 | 229 | Change the [Default Options](#basic_options) to the values of properties of `newDefaultOptions` Object. 230 | All it takes is to specify options that you want change, because unspecified options are not updated. 231 | 232 | ## Basic Options 233 | 234 | [`prompt`](#basic_options-prompt), [`hideEchoBack`](#basic_options-hideechoback), [`mask`](#basic_options-mask), [`limit`](#basic_options-limit), [`limitMessage`](#basic_options-limitmessage), [`defaultInput`](#basic_options-defaultinput), [`trueValue`, `falseValue`](#basic_options-truevalue_falsevalue), [`caseSensitive`](#basic_options-casesensitive), [`keepWhitespace`](#basic_options-keepwhitespace), [`encoding`](#basic_options-encoding), [`bufferSize`](#basic_options-buffersize), [`print`](#basic_options-print), [`history`](#basic_options-history), [`cd`](#basic_options-cd) 235 | 236 | An `options` Object can be specified to the methods to control the behavior of readlineSync. The options that were not specified to the methods are got from the Default Options. You can change the Default Options by [`setDefaultOptions`](#basic_methods-setdefaultoptions) method anytime, and it is kept until a current process is exited. 237 | Specify the options that are often used to the Default Options, and specify temporary options to the methods. 238 | 239 | For example: 240 | 241 | ```js 242 | readlineSync.setDefaultOptions({limit: ['green', 'yellow', 'red']}); 243 | a1 = readlineSync.question('Which color of signal? '); // Input is limited to 3 things. 244 | a2 = readlineSync.question('Which color of signal? '); // It's limited yet. 245 | a3 = readlineSync.question('What is your favorite color? ', {limit: null}); // It's unlimited temporarily. 246 | a4 = readlineSync.question('Which color of signal? '); // It's limited again. 247 | readlineSync.setDefaultOptions({limit: ['beef', 'chicken']}); 248 | a5 = readlineSync.question('Beef or Chicken? '); // Input is limited to new 2 things. 249 | a6 = readlineSync.question('And you? '); // It's limited to 2 things yet. 250 | ``` 251 | 252 | The Object as `options` can have following properties. 253 | 254 | ### `prompt` 255 | 256 | _For `prompt*` methods only_ 257 | *Type:* string or others 258 | *Default:* `'> '` 259 | 260 | Set the prompt-sign that is displayed to the user by `prompt*` methods. For example you see `> ` that is Node.js's prompt-sign when you run `node` on the command line. 261 | This may be string, or may not be (e.g. number, Date, Object, etc.). It is converted to string every time (i.e. `toString` method is called) before it is displayed. (see [Note](#note) also) 262 | It can include the [placeholders](#placeholders). 263 | 264 | For example: 265 | 266 | ```js 267 | readlineSync.setDefaultOptions({prompt: '$ '}); 268 | ``` 269 | 270 | ```js 271 | // Display the memory usage always. 272 | readlineSync.setDefaultOptions({ 273 | prompt: { // Simple Object that has toString method. 274 | toString: function() { 275 | var rss = process.memoryUsage().rss; 276 | return '[' + (rss > 1024 ? Math.round(rss / 1024) + 'k' : rss) + 'b]$ '; 277 | } 278 | } 279 | }); 280 | ``` 281 | 282 | ```console 283 | [13148kb]$ foo 284 | [13160kb]$ bar 285 | [13200kb]$ 286 | ``` 287 | 288 | ### `hideEchoBack` 289 | 290 | *Type:* boolean 291 | *Default:* `false` 292 | 293 | If `true` is specified, hide the secret text (e.g. password) which is typed by user on screen by the mask characters (see [`mask`](#basic_options-mask) option). 294 | 295 | For example: 296 | 297 | ```js 298 | password = readlineSync.question('PASSWORD: ', {hideEchoBack: true}); 299 | console.log('Login ...'); 300 | ``` 301 | 302 | ```console 303 | PASSWORD: ******** 304 | Login ... 305 | ``` 306 | 307 | ### `mask` 308 | 309 | *Type:* string 310 | *Default:* `'*'` 311 | 312 | Set the mask characters that are shown instead of the secret text (e.g. password) when `true` is specified to [`hideEchoBack`](#basic_options-hideechoback) option. If you want to show nothing, specify `''`. (But it might be not user friendly in some cases.) 313 | **Note:** In some cases (e.g. when the input stream is redirected on Windows XP), `'*'` or `''` might be used whether other one is specified. 314 | 315 | For example: 316 | 317 | ```js 318 | secret = readlineSync.question('Please whisper sweet words: ', { 319 | hideEchoBack: true, 320 | mask: require('chalk').magenta('\u2665') 321 | }); 322 | ``` 323 | 324 | ![sample](screen_02.gif) 325 | 326 | ### `limit` 327 | 328 | Limit the user's input. 329 | The usage differ depending on the method. 330 | 331 | #### For `question*` and `prompt*` methods 332 | 333 | *Type:* string, number, RegExp, function or Array 334 | *Default:* `[]` 335 | 336 | Accept only the input that matches value that is specified to this. If the user input others, display a string that is specified to [`limitMessage`](#basic_options-limitmessage) option, and wait for reinput. 337 | 338 | * The string is compared with the input. It is affected by [`caseSensitive`](#basic_options-casesensitive) option. 339 | * The number is compared with the input that is converted to number by `parseFloat()`. For example, it interprets `' 3.14 '`, `'003.1400'`, `'314e-2'` and `'3.14PI'` as `3.14`. And it interprets `'005'`, `'5files'`, `'5kb'` and `'5px'` as `5`. 340 | * The RegExp tests the input. 341 | * The function that returns a boolean to indicate whether it matches is called with the input. 342 | 343 | One of above or an Array that includes multiple things (or Array includes Array) can be specified. 344 | 345 | For example: 346 | 347 | ```js 348 | command = readlineSync.prompt({limit: ['add', 'remove', /^clear( all)?$/]}); 349 | // ** But `promptCL` method should be used instead of this. ** 350 | ``` 351 | 352 | ```js 353 | file = readlineSync.question('Text File: ', {limit: /\.txt$/i}); 354 | // ** But `questionPath` method should be used instead of this. ** 355 | ``` 356 | 357 | ```js 358 | ip = readlineSync.question('IP Address: ', {limit: function(input) { 359 | return require('net').isIP(input); // Valid IP Address 360 | }}); 361 | ``` 362 | 363 | ```js 364 | availableActions = []; 365 | if (!blockExists()) { availableActions.push('jump'); } 366 | if (isLarge(place)) { availableActions.push('run'); } 367 | if (isNew(shoes)) { availableActions.push('kick'); } 368 | if (isNearby(enemy)) { availableActions.push('punch'); } 369 | action = readlineSync.prompt({limit: availableActions}); 370 | // ** But `promptCL` method should be used instead of this. ** 371 | ``` 372 | 373 | #### For `keyIn*` method 374 | 375 | *Type:* string, number or Array 376 | *Default:* `[]` 377 | 378 | Accept only the key that matches value that is specified to this, ignore others. 379 | 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']`. 380 | These strings are compared with the input. It is affected by [`caseSensitive`](#basic_options-casesensitive) option. 381 | 382 | The [placeholders](#placeholders) like `'$'` are replaced to an Array that is the character list like `['a', 'b', 'c', 'd', 'e']`. 383 | 384 | For example: 385 | 386 | ```js 387 | direction = readlineSync.keyIn('Left or Right? ', {limit: 'lr'}); // 'l' or 'r' 388 | ``` 389 | 390 | ```js 391 | dice = readlineSync.keyIn('Roll the dice, What will the result be? ', 392 | {limit: '$<1-6>'}); // range of '1' to '6' 393 | ``` 394 | 395 | ### `limitMessage` 396 | 397 | _For `question*` and `prompt*` methods only_ 398 | *Type:* string 399 | *Default:* `'Input another, please.$<( [)limit(])>'` 400 | 401 | Display this to the user when the [`limit`](#basic_options-limit) option is specified and the user input others. 402 | The [placeholders](#placeholders) can be included. 403 | 404 | For example: 405 | 406 | ```js 407 | file = readlineSync.question('Name of Text File: ', { 408 | limit: /\.txt$/i, 409 | limitMessage: 'Sorry, $ is not text file.' 410 | }); 411 | ``` 412 | 413 | ### `defaultInput` 414 | 415 | _For `question*` and `prompt*` methods only_ 416 | *Type:* string 417 | *Default:* `''` 418 | 419 | If the user input empty text (i.e. pressed the Enter key only), return this. 420 | 421 | For example: 422 | 423 | ```js 424 | lang = readlineSync.question('Which language? ', {defaultInput: 'javascript'}); 425 | ``` 426 | 427 | ### `trueValue`, `falseValue` 428 | 429 | *Type:* string, number, RegExp, function or Array 430 | *Default:* `[]` 431 | 432 | If the input matches `trueValue`, return `true`. If the input matches `falseValue`, return `false`. In any other case, return the input. 433 | 434 | * The string is compared with the input. It is affected by [`caseSensitive`](#basic_options-casesensitive) option. 435 | * The number is compared with the input that is converted to number by `parseFloat()`. For example, it interprets `' 3.14 '`, `'003.1400'`, `'314e-2'` and `'3.14PI'` as `3.14`. And it interprets `'005'`, `'5files'`, `'5kb'` and `'5px'` as `5`. Note that in `keyIn*` method, the input is every time one character (i.e. the number that is specified must be an integer within the range of `0` to `9`). 436 | * The RegExp tests the input. 437 | * The function that returns a boolean to indicate whether it matches is called with the input. 438 | 439 | One of above or an Array that includes multiple things (or Array includes Array) can be specified. 440 | 441 | For example: 442 | 443 | ```js 444 | answer = readlineSync.question('How do you like it? ', { 445 | trueValue: ['yes', 'yeah', 'yep'], 446 | falseValue: ['no', 'nah', 'nope'] 447 | }); 448 | if (answer === true) { 449 | console.log('Let\'s go!'); 450 | } else if (answer === false) { 451 | console.log('Oh... It\'s ok...'); 452 | } else { 453 | console.log('Sorry. What does "' + answer + '" you said mean?'); 454 | } 455 | ``` 456 | 457 | ### `caseSensitive` 458 | 459 | *Type:* boolean 460 | *Default:* `false` 461 | 462 | By default, the string comparisons are case-insensitive (i.e. `a` equals `A`). If `true` is specified, it is case-sensitive, the cases are not ignored (i.e. `a` is different from `A`). 463 | It affects: [`limit`](#basic_options-limit), [`trueValue`](#basic_options-truevalue_falsevalue), [`falseValue`](#basic_options-truevalue_falsevalue), some [placeholders](#placeholders), and some [Utility Methods](#utility_methods). 464 | 465 | ### `keepWhitespace` 466 | 467 | _For `question*` and `prompt*` methods only_ 468 | *Type:* boolean 469 | *Default:* `false` 470 | 471 | By default, remove the leading and trailing white spaces from the input text. If `true` is specified, don't remove those. 472 | 473 | ### `encoding` 474 | 475 | *Type:* string 476 | *Default:* `'utf8'` 477 | 478 | Set the encoding method of the input and output. 479 | 480 | ### `bufferSize` 481 | 482 | _For `question*` and `prompt*` methods only_ 483 | *Type:* number 484 | *Default:* `1024` 485 | 486 | When readlineSync reads from a console directly (without [external program](#note-reading_by_external_program)), use a size `bufferSize` buffer. 487 | Even if the input by user exceeds it, it's usually no problem, because the buffer is used repeatedly. But some platforms's (e.g. Windows) console might not accept input that exceeds it. And set an enough size. 488 | Note that this might be limited by [version of Node.js](https://nodejs.org/api/buffer.html#buffer_class_method_buffer_alloc_size_fill_encoding) and environment running your script (Big buffer size is usually not required). (See also: [issue](https://github.com/nodejs/node/issues/4660), [PR](https://github.com/nodejs/node/pull/4682)) 489 | 490 | ### `print` 491 | 492 | *Type:* function or `undefined` 493 | *Default:* `undefined` 494 | 495 | Call the specified function with every output. The function is given two arguments, `display` as an output text, and a value of [`encoding`](#basic_options-encoding) option. 496 | 497 | For example: 498 | 499 | * Pass the plain texts to the Logger (e.g. [log4js](https://github.com/nomiddlename/log4js-node)), after clean the colored texts. 500 | 501 | ![sample](screen_01.png) 502 | 503 | ```js 504 | var readlineSync = require('readline-sync'), 505 | chalk = require('chalk'), 506 | log4js = require('log4js'), 507 | logger, user, pw, command; 508 | 509 | log4js.configure({appenders: [{type: 'file', filename: 'fooApp.log'}]}); 510 | logger = log4js.getLogger('fooApp'); 511 | 512 | readlineSync.setDefaultOptions({ 513 | print: function(display, encoding) 514 | { logger.info(chalk.stripColor(display)); }, // Remove ctrl-chars. 515 | prompt: chalk.red.bold('> ') 516 | }); 517 | 518 | console.log(chalk.black.bold.bgYellow(' Your Account ')); 519 | user = readlineSync.question(chalk.gray.underline(' USER NAME ') + ' : '); 520 | pw = readlineSync.question(chalk.gray.underline(' PASSWORD ') + ' : ', 521 | {hideEchoBack: true}); 522 | // Authorization ... 523 | console.log(chalk.green('Welcome, ' + user + '!')); 524 | command = readlineSync.prompt(); 525 | ``` 526 | 527 | * Output a conversation to a file when an output stream is redirected to record those into a file like `your-script >foo.log`. That is, a conversation isn't outputted to `foo.log` without this code. 528 | 529 | ```js 530 | readlineSync.setDefaultOptions({ 531 | print: function(display, encoding) 532 | { process.stdout.write(display, encoding); } 533 | }); 534 | var name = readlineSync.question('May I have your name? '); 535 | var loc = readlineSync.question('Hi ' + name + '! Where do you live? '); 536 | ``` 537 | 538 | * Let somebody hear our conversation in real time. 539 | It just uses a fifo with above sample code that was named `conv.js`. 540 | 541 | Another terminal: 542 | 543 | ```console 544 | mkfifo /tmp/fifo 545 | cat /tmp/fifo 546 | ``` 547 | 548 | My terminal: 549 | 550 | ```console 551 | node conv.js >/tmp/fifo 552 | ``` 553 | 554 | ```console 555 | May I have your name? Oz 556 | Hi Oz! Where do you live? Emerald City 557 | ``` 558 | 559 | And then, another terminal shows this synchronously: 560 | 561 | ```console 562 | May I have your name? Oz 563 | Hi Oz! Where do you live? Emerald City 564 | ``` 565 | 566 | ### `history` 567 | 568 | _For `question*` and `prompt*` methods only_ 569 | *Type:* boolean 570 | *Default:* `true` 571 | 572 | readlineSync supports a history expansion feature that is similar to that of the shell. If `false` is specified, disable this feature. 573 | *It keeps a previous input only.* That is, only `!!`, `!-1`, `!!:p` and `!-1:p` like bash or zsh etc. are supported. 574 | 575 | * `!!` or `!-1`: Return a previous input. 576 | * `!!:p` or `!-1:p`: Display a previous input but do not return it, and wait for reinput. 577 | 578 | For example: 579 | 580 | ```js 581 | while (true) { 582 | input = readlineSync.prompt(); 583 | console.log('-- You said "' + input + '"'); 584 | } 585 | ``` 586 | 587 | ```console 588 | > hello 589 | -- You said "hello" 590 | > !! 591 | hello 592 | -- You said "hello" 593 | > !!:p 594 | hello 595 | > bye 596 | -- You said "bye" 597 | ``` 598 | 599 | ### `cd` 600 | 601 | _For `question*` and `prompt*` methods only_ 602 | *Type:* boolean 603 | *Default:* `false` 604 | 605 | readlineSync supports the changing the current working directory feature that is similar to the `cd` and `pwd` commands in the shell. If `true` is specified, enable this feature. 606 | This helps the user when you let the user input the multiple local files or directories. 607 | It supports `cd` and `pwd` commands. 608 | 609 | * `cd `: Change the current working directory to ``. The `` can include `~` as the home directory. 610 | * `pwd`: Display the current working directory. 611 | 612 | When these were input, do not return, and wait for reinput. 613 | 614 | For example: 615 | 616 | ```js 617 | while (true) { 618 | file = readlineSync.questionPath('File: '); 619 | console.log('-- Specified file is ' + file); 620 | } 621 | ``` 622 | 623 | ```console 624 | File: cd foo-dir/bar-dir 625 | File: pwd 626 | /path/to/foo-dir/bar-dir 627 | File: file-a.js 628 | -- Specified file is /path/to/foo-dir/bar-dir/file-a.js 629 | File: file-b.png 630 | -- Specified file is /path/to/foo-dir/bar-dir/file-b.png 631 | File: file-c.html 632 | -- Specified file is /path/to/foo-dir/bar-dir/file-c.html 633 | ``` 634 | 635 | ## Utility Methods 636 | 637 | [`questionEMail`](#utility_methods-questionemail), [`questionNewPassword`](#utility_methods-questionnewpassword), [`questionInt`](#utility_methods-questionint), [`questionFloat`](#utility_methods-questionfloat), [`questionPath`](#utility_methods-questionpath), [`promptCL`](#utility_methods-promptcl), [`promptLoop`](#utility_methods-promptloop), [`promptCLLoop`](#utility_methods-promptclloop), [`promptSimShell`](#utility_methods-promptsimshell), [`keyInYN`](#utility_methods-keyinyn), [`keyInYNStrict`](#utility_methods-keyinynstrict), [`keyInPause`](#utility_methods-keyinpause), [`keyInSelect`](#utility_methods-keyinselect) 638 | 639 | These are convenient methods that are extended [Basic Methods](#basic_methods) to be used easily. 640 | 641 | ### `questionEMail` 642 | 643 | ```js 644 | email = readlineSync.questionEMail([query[, options]]) 645 | ``` 646 | 647 | Display a `query` to the user if it's specified, and then accept only a valid e-mail address, and then return it after the Enter key was pressed. 648 | 649 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 650 | The default value of `query` is `'Input e-mail address: '`. 651 | 652 | **Note:** The valid e-mail address requirement is a willful violation of [RFC5322](http://tools.ietf.org/html/rfc5322), this is defined in [HTML5](http://www.w3.org/TR/html5/forms.html). This works enough to prevent the user mistaking. If you want to change it, specify [`limit`](#basic_options-limit) option. 653 | 654 | For example: 655 | 656 | ```js 657 | email = readlineSync.questionEMail(); 658 | console.log('-- E-mail is ' + email); 659 | ``` 660 | 661 | ```console 662 | Input e-mail address: abc 663 | Input valid e-mail address, please. 664 | Input e-mail address: mail@example.com 665 | -- E-mail is mail@example.com 666 | ``` 667 | 668 | #### Options 669 | 670 | The following options have independent default value that is not affected by [Default Options](#basic_options). 671 | 672 | | Option Name | Default Value | 673 | |-------------------|---------------| 674 | | [`hideEchoBack`](#basic_options-hideechoback) | `false` | 675 | | [`limit`](#basic_options-limit) | RegExp by [HTML5](http://www.w3.org/TR/html5/forms.html) | 676 | | [`limitMessage`](#basic_options-limitmessage) | `'Input valid e-mail address, please.'` | 677 | | [`trueValue`](#basic_options-truevalue_falsevalue) | `null` | 678 | | [`falseValue`](#basic_options-truevalue_falsevalue) | `null` | 679 | 680 | The following options work as shown in the [Basic Options](#basic_options) section. 681 | 682 | 683 | 684 | 685 |
maskdefaultInputcaseSensitiveencodingbufferSize
printhistory
686 | 687 | ### `questionNewPassword` 688 | 689 | ```js 690 | password = readlineSync.questionNewPassword([query[, options]]) 691 | ``` 692 | 693 | Display a `query` to the user if it's specified, and then accept only a valid password, and then request same one again, and then return it after the Enter key was pressed. 694 | It's the password, or something that is the secret text like the password. 695 | You can specify the valid password requirement to the options. 696 | 697 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 698 | The default value of `query` is `'Input new password: '`. 699 | 700 | **Note:** Only the form of password is checked. Check it more if you want. For example, [zxcvbn](https://github.com/dropbox/zxcvbn) is password strength estimation library. 701 | 702 | For example: 703 | 704 | ```js 705 | password = readlineSync.questionNewPassword(); 706 | console.log('-- Password is ' + password); 707 | ``` 708 | 709 | ```console 710 | Input new password: ************ 711 | It can include: 0...9, A...Z, a...z, !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ 712 | And the length must be: 12...24 713 | Input new password: ************* 714 | Reinput a same one to confirm it: ************* 715 | It differs from first one. Hit only the Enter key if you want to retry from first one. 716 | Reinput a same one to confirm it: ************* 717 | -- Password is _my_password_ 718 | ``` 719 | 720 | #### Options 721 | 722 | The following options have independent default value that is not affected by [Default Options](#basic_options). 723 | 724 | | Option Name | Default Value | 725 | |-------------------|---------------| 726 | | [`hideEchoBack`](#basic_options-hideechoback) | `true` | 727 | | [`mask`](#basic_options-mask) | `'*'` | 728 | | [`limitMessage`](#basic_options-limitmessage) | `'It can include: $\nAnd the length must be: $'` | 729 | | [`trueValue`](#basic_options-truevalue_falsevalue) | `null` | 730 | | [`falseValue`](#basic_options-truevalue_falsevalue) | `null` | 731 | | [`caseSensitive`](#basic_options-casesensitive) | `true` | 732 | 733 | The following options work as shown in the [Basic Options](#basic_options) section. 734 | 735 | 736 | 737 |
defaultInputkeepWhitespaceencodingbufferSizeprint
738 | 739 | And the following additional options are available. 740 | 741 | ##### `charlist` 742 | 743 | *Type:* string 744 | *Default:* `'$'` 745 | 746 | 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. 747 | The [placeholders](#placeholders) like `'$'` are replaced to the characters like `'abcde'`. 748 | 749 | For example, let the user input a password that is created with alphabet and some symbols: 750 | 751 | ```js 752 | password = readlineSync.questionNewPassword('PASSWORD: ', {charlist: '$#$@%'}); 753 | ``` 754 | 755 | ##### `min`, `max` 756 | 757 | *Type:* number 758 | *Default:* `min`: `12`, `max`: `24` 759 | 760 | `min`: A number as a minimum length of the password. 761 | `max`: A number as a maximum length of the password. 762 | 763 | ##### `confirmMessage` 764 | 765 | *Type:* string or others 766 | *Default:* `'Reinput a same one to confirm it: '` 767 | 768 | A message that lets the user input the same password again. 769 | It can include the [placeholders](#placeholders). 770 | If this is not string, it is converted to string (i.e. `toString` method is called). 771 | 772 | ##### `unmatchMessage` 773 | 774 | *Type:* string or others 775 | *Default:* `'It differs from first one. Hit only the Enter key if you want to retry from first one.'` 776 | 777 | A warning message that is displayed when the second input did not match first one. 778 | This is converted the same as the [`confirmMessage`](#utility_methods-questionnewpassword-options-confirmmessage) option. 779 | 780 | #### Additional Placeholders 781 | 782 | The following additional [placeholder](#placeholders) parameters are available. 783 | 784 | ##### `charlist` 785 | 786 | A current value of [`charlist`](#utility_methods-questionnewpassword-options-charlist) option that is converted to human readable if possible. (e.g. `'A...Z'`) 787 | 788 | ##### `length` 789 | 790 | A current value of [`min` and `max`](#utility_methods-questionnewpassword-options-min_max) option that is converted to human readable. (e.g. `'12...24'`) 791 | 792 | ### `questionInt` 793 | 794 | ```js 795 | numInt = readlineSync.questionInt([query[, options]]) 796 | ``` 797 | 798 | Display a `query` to the user if it's specified, and then accept only an input that can be interpreted as an integer, and then return the number (not string) after the Enter key was pressed. 799 | This parses the input as much as possible by `parseInt()`. For example, it interprets `' 5 '`, `'5.6'`, `'005'`, `'5files'`, `'5kb'` and `'5px'` as `5`. 800 | 801 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 802 | 803 | #### Options 804 | 805 | The following option has independent default value that is not affected by [Default Options](#basic_options). 806 | 807 | | Option Name | Default Value | 808 | |-------------------|---------------| 809 | | [`limitMessage`](#basic_options-limitmessage) | `'Input valid number, please.'` | 810 | 811 | The following options work as shown in the [Basic Options](#basic_options) section. 812 | 813 | 814 | 815 | 816 |
hideEchoBackmaskdefaultInputencodingbufferSize
printhistory
817 | 818 | ### `questionFloat` 819 | 820 | ```js 821 | numFloat = readlineSync.questionFloat([query[, options]]) 822 | ``` 823 | 824 | Display a `query` to the user if it's specified, and then accept only an input that can be interpreted as a floating-point number, and then return the number (not string) after the Enter key was pressed. 825 | This parses the input as much as possible by `parseFloat()`. For example, it interprets `' 3.14 '`, `'003.1400'`, `'314e-2'` and `'3.14PI'` as `3.14`. 826 | 827 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 828 | 829 | #### Options 830 | 831 | The following option has independent default value that is not affected by [Default Options](#basic_options). 832 | 833 | | Option Name | Default Value | 834 | |-------------------|---------------| 835 | | [`limitMessage`](#basic_options-limitmessage) | `'Input valid number, please.'` | 836 | 837 | The following options work as shown in the [Basic Options](#basic_options) section. 838 | 839 | 840 | 841 | 842 |
hideEchoBackmaskdefaultInputencodingbufferSize
printhistory
843 | 844 | ### `questionPath` 845 | 846 | ```js 847 | path = readlineSync.questionPath([query[, options]]) 848 | ``` 849 | 850 | Display a `query` to the user if it's specified, and then accept only a valid local file or directory path, and then return an absolute path after the Enter key was pressed. 851 | The `~` that is input by the user is replaced to the home directory. 852 | You can specify the valid local file or directory path requirement to the options. And you can make it create a new file or directory when it doesn't exist. 853 | 854 | It is recommended to use this method with the [`cd`](#basic_options-cd) option. (Default: `true`) 855 | 856 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 857 | The default value of `query` is `'Input path (you can "cd" and "pwd"): '`. 858 | 859 | For example: 860 | 861 | ```js 862 | sourceFile = readlineSync.questionPath('Read from: ', { 863 | isFile: true 864 | }); 865 | console.log('-- sourceFile: ' + sourceFile); 866 | 867 | saveDir = readlineSync.questionPath('Save to: ', { 868 | isDirectory: true, 869 | exists: null, 870 | create: true 871 | }); 872 | console.log('-- saveDir: ' + saveDir); 873 | ``` 874 | 875 | ```console 876 | Read from: ~/fileA 877 | No such file or directory: /home/user/fileA 878 | Input valid path, please. 879 | Read from: pwd 880 | /path/to/work 881 | Read from: cd ~/project-1 882 | Read from: fileA 883 | -- sourceFile: /home/user/project-1/fileA 884 | Save to: ~/deploy/data 885 | -- saveDir: /home/user/deploy/data 886 | ``` 887 | 888 | #### Options 889 | 890 | The following options have independent default value that is not affected by [Default Options](#basic_options). 891 | 892 | | Option Name | Default Value | 893 | |-------------------|---------------| 894 | | [`hideEchoBack`](#basic_options-hideechoback) | `false` | 895 | | [`limitMessage`](#basic_options-limitmessage) | `'$Input valid path, please.$<( Min:)min>$<( Max:)max>'` | 896 | | [`history`](#basic_options-history) | `true` | 897 | | [`cd`](#basic_options-cd) | `true` | 898 | 899 | The following options work as shown in the [Basic Options](#basic_options) section. 900 | 901 | 902 | 903 |
maskdefaultInputencodingbufferSizeprint
904 | 905 | And the following additional options are available. 906 | 907 | **Note:** It does not check the coherency about a combination of the options as the path requirement. For example, the `{exists: false, isFile: true}` never check that it is a file because it is limited to the path that does not exist. 908 | 909 | ##### `exists` 910 | 911 | *Type:* boolean or others 912 | *Default:* `true` 913 | 914 | If `true` is specified, accept only a file or directory path that exists. If `false` is specified, accept only a file or directory path that does *not* exist. 915 | In any other case, the existence is not checked. 916 | 917 | ##### `min`, `max` 918 | 919 | *Type:* number or others 920 | *Default:* `undefined` 921 | 922 | `min`: A number as a minimum size of the file that is accepted. 923 | `max`: A number as a maximum size of the file that is accepted. 924 | If it is not specified or `0` is specified, the size is not checked. (A size of directory is `0`.) 925 | 926 | ##### `isFile`, `isDirectory` 927 | 928 | *Type:* boolean 929 | *Default:* `false` 930 | 931 | `isFile`: If `true` is specified, accept only a file path. 932 | `isDirectory`: If `true` is specified, accept only a directory path. 933 | 934 | ##### `validate` 935 | 936 | *Type:* function or `undefined` 937 | *Default:* `undefined` 938 | 939 | If a function that returns `true` or an error message is specified, call it with a path that was input, and accept the input when the function returned `true`. 940 | If the function returned a string as an error message, that message is got by the [`error`](#utility_methods-questionpath-additional_placeholders-error) additional [placeholder](#placeholders) parameter. 941 | A path that was input is parsed before it is passed to the function. `~` is replaced to a home directory, and a path is converted to an absolute path. 942 | This is also a return value from this method. 943 | 944 | For example, accept only PNG file or tell it to the user: 945 | 946 | ```js 947 | imageFile = readlineSync.questionPath('Image File: ', { 948 | validate: function(path) { return /\.png$/i.test(path) || 'It is not PNG'; } 949 | }); 950 | ``` 951 | 952 | ##### `create` 953 | 954 | *Type:* boolean 955 | *Default:* `false` 956 | 957 | If `true` is specified, create a file or directory as a path that was input when it doesn't exist. If `true` is specified to the [`isDirectory`](#utility_methods-questionpath-options-isfile_isdirectory) option, create a directory, otherwise a file. 958 | It does not affect the existence check. Therefore, you can get a new file or directory path anytime by specifying: `{exists: false, create: true}` 959 | 960 | #### Additional Placeholders 961 | 962 | The following additional [placeholder](#placeholders) parameters are available. 963 | 964 | ##### `error` 965 | 966 | An error message when the input was not accepted. 967 | This value is set by readlineSync, or the function that was specified to [`validate`](#utility_methods-questionpath-options-validate) option. 968 | 969 | ##### `min`, `max` 970 | 971 | A current value of [`min` and `max`](#utility_methods-questionpath-options-min_max) option. 972 | 973 | ### `promptCL` 974 | 975 | ```js 976 | argsArray = readlineSync.promptCL([commandHandler[, options]]) 977 | ``` 978 | 979 | Display a prompt-sign (see [`prompt`](#basic_options-prompt) option) to the user, and then consider the input as a command-line and parse it, and then return a result after the Enter key was pressed. 980 | A return value is an Array that includes the tokens that were parsed. It parses the input from the user as the command-line, and it interprets whitespaces, quotes, etc., and it splits it to tokens properly. Usually, a first element of the Array is command-name, and remaining elements are arguments. 981 | 982 | For example: 983 | 984 | ```js 985 | argsArray = readlineSync.promptCL(); 986 | console.log(argsArray.join('\n')); 987 | ``` 988 | 989 | ```console 990 | > command arg "arg" " a r g " "" 'a"r"g' "a""rg" "arg 991 | command 992 | arg 993 | arg 994 | a r g 995 | 996 | a"r"g 997 | arg 998 | arg 999 | ``` 1000 | 1001 | #### `commandHandler` 1002 | 1003 | By using the `commandHandler` argument, this method will come into its own. Specifying the Object to this argument has the more merit. And it has the more merit for [`promptCLLoop`](#utility_methods-promptclloop) method. 1004 | 1005 | If a function is specified to `commandHandler` argument, it is just called with a parsed Array as an argument list of the function. And `this` is an original input string, in the function. 1006 | 1007 | For example, the following 2 codes work same except that `this` is enabled in the second one: 1008 | 1009 | ```js 1010 | argsArray = readlineSync.promptCL(); 1011 | if (argsArray[0] === 'add') { 1012 | console.log(argsArray[1] + ' is added.'); 1013 | } else if (argsArray[0] === 'copy') { 1014 | console.log(argsArray[1] + ' is copied to ' + argsArray[2] + '.'); 1015 | } 1016 | ``` 1017 | 1018 | ```js 1019 | readlineSync.promptCL(function(command, arg1, arg2) { 1020 | console.log('You want to: ' + this); // All of command-line. 1021 | if (command === 'add') { 1022 | console.log(arg1 + ' is added.'); 1023 | } else if (command === 'copy') { 1024 | console.log(arg1 + ' is copied to ' + arg2 + '.'); 1025 | } 1026 | }); 1027 | ``` 1028 | 1029 | If an Object that has properties named as the command-name is specified, the command-name is interpreted, and a function as the value of matched property is called. A function is chosen properly by handling case of the command-name in accordance with the [`caseSensitive`](#basic_options-casesensitive) option. 1030 | The function is called with a parsed Array that excludes a command-name (i.e. first element is removed from the Array) as an argument list of the function. 1031 | That is, a structure of the `commandHandler` Object looks like: 1032 | 1033 | ```js 1034 | { 1035 | commandA: function(arg) { ... }, // commandA requires one argument. 1036 | commandB: function(arg1, arg2) { ... }, // readlineSync doesn't care those. 1037 | commandC: function() { ... } // Of course, it can also ignore all. 1038 | } 1039 | ``` 1040 | 1041 | readlineSync just receives the arguments from the user and passes those to these functions without checking. The functions may have to check whether the required argument was input by the user, and more validate those. 1042 | 1043 | For example, the following code works same to the above code: 1044 | 1045 | ```js 1046 | readlineSync.promptCL({ 1047 | add: function(element) { // It's called by also "ADD", "Add", "aDd", etc.. 1048 | console.log(element + ' is added.'); 1049 | }, 1050 | copy: function(from, to) { 1051 | console.log(from + ' is copied to ' + to + '.'); 1052 | } 1053 | }); 1054 | ``` 1055 | 1056 | If the matched property is not found in the Object, a `_` property is chosen, and the function as the value of this property is called with a parsed Array as an argument list of the function. Note that this includes a command-name. That is, the function looks like `function(command, arg1, arg2, ...) { ... }`. 1057 | And if the Object doesn't have a `_` property, any command that the matched property is not found in the Object is refused. 1058 | 1059 | For example: 1060 | 1061 | ```js 1062 | readlineSync.promptCL({ 1063 | copy: function(from, to) { // command-name is not included. 1064 | console.log(from + ' is copied to ' + to + '.'); 1065 | }, 1066 | _: function(command) { // command-name is included. 1067 | console.log('Sorry, ' + command + ' is not available.'); 1068 | } 1069 | }); 1070 | ``` 1071 | 1072 | #### Options 1073 | 1074 | The following options have independent default value that is not affected by [Default Options](#basic_options). 1075 | 1076 | | Option Name | Default Value | 1077 | |-------------------|---------------| 1078 | | [`hideEchoBack`](#basic_options-hideechoback) | `false` | 1079 | | [`limitMessage`](#basic_options-limitmessage) | `'Requested command is not available.'` | 1080 | | [`caseSensitive`](#basic_options-casesensitive) | `false` | 1081 | | [`history`](#basic_options-history) | `true` | 1082 | 1083 | The following options work as shown in the [Basic Options](#basic_options) section. 1084 | 1085 | 1086 | 1087 | 1088 |
promptmaskdefaultInputencodingbufferSize
printcd
1089 | 1090 | ### `promptLoop` 1091 | 1092 | ```js 1093 | readlineSync.promptLoop(inputHandler[, options]) 1094 | ``` 1095 | 1096 | Display a prompt-sign (see [`prompt`](#basic_options-prompt) option) to the user, and then call `inputHandler` function with the input from the user after it has been typed and the Enter key was pressed. Do these repeatedly until `inputHandler` function returns `true`. 1097 | 1098 | For example, the following 2 codes work same: 1099 | 1100 | ```js 1101 | while (true) { 1102 | input = readlineSync.prompt(); 1103 | console.log('-- You said "' + input + '"'); 1104 | if (input === 'bye') { 1105 | break; 1106 | } 1107 | } 1108 | console.log('It\'s exited from loop.'); 1109 | ``` 1110 | 1111 | ```js 1112 | readlineSync.promptLoop(function(input) { 1113 | console.log('-- You said "' + input + '"'); 1114 | return input === 'bye'; 1115 | }); 1116 | console.log('It\'s exited from loop.'); 1117 | ``` 1118 | 1119 | ```console 1120 | > hello 1121 | -- You said "hello" 1122 | > good morning 1123 | -- You said "good morning" 1124 | > bye 1125 | -- You said "bye" 1126 | It's exited from loop. 1127 | ``` 1128 | 1129 | #### Options 1130 | 1131 | The following options have independent default value that is not affected by [Default Options](#basic_options). 1132 | 1133 | | Option Name | Default Value | 1134 | |-------------------|---------------| 1135 | | [`hideEchoBack`](#basic_options-hideechoback) | `false` | 1136 | | [`trueValue`](#basic_options-truevalue_falsevalue) | `null` | 1137 | | [`falseValue`](#basic_options-truevalue_falsevalue) | `null` | 1138 | | [`caseSensitive`](#basic_options-casesensitive) | `false` | 1139 | | [`history`](#basic_options-history) | `true` | 1140 | 1141 | The other options work as shown in the [Basic Options](#basic_options) section. 1142 | 1143 | ### `promptCLLoop` 1144 | 1145 | ```js 1146 | readlineSync.promptCLLoop([commandHandler[, options]]) 1147 | ``` 1148 | 1149 | Execute [`promptCL`](#utility_methods-promptcl) method repeatedly until chosen [`commandHandler`](#utility_methods-promptcl-commandhandler) returns `true`. 1150 | The [`commandHandler`](#utility_methods-promptcl-commandhandler) may be a function that is called like: 1151 | 1152 | ```js 1153 | exit = allCommands(command, arg1, arg2, ...); 1154 | ``` 1155 | 1156 | or an Object that has the functions that are called like: 1157 | 1158 | ```js 1159 | exit = foundCommand(arg1, arg2, ...); 1160 | ``` 1161 | 1162 | See [`promptCL`](#utility_methods-promptcl) method for details. 1163 | This method looks like a combination of [`promptCL`](#utility_methods-promptcl) method and [`promptLoop`](#utility_methods-promptloop) method. 1164 | 1165 | For example: 1166 | 1167 | ```js 1168 | readlineSync.promptCLLoop({ 1169 | add: function(element) { 1170 | console.log(element + ' is added.'); 1171 | }, 1172 | copy: function(from, to) { 1173 | console.log(from + ' is copied to ' + to + '.'); 1174 | }, 1175 | bye: function() { return true; } 1176 | }); 1177 | console.log('It\'s exited from loop.'); 1178 | ``` 1179 | 1180 | ```console 1181 | > add "New Hard Disk" 1182 | New Hard Disk is added. 1183 | > move filesOnOld "New Hard Disk" 1184 | Requested command is not available. 1185 | > copy filesOnOld "New Hard Disk" 1186 | filesOnOld is copied to New Hard Disk. 1187 | > bye 1188 | It's exited from loop. 1189 | ``` 1190 | 1191 | #### Options 1192 | 1193 | The following options have independent default value that is not affected by [Default Options](#basic_options). 1194 | 1195 | | Option Name | Default Value | 1196 | |-------------------|---------------| 1197 | | [`hideEchoBack`](#basic_options-hideechoback) | `false` | 1198 | | [`limitMessage`](#basic_options-limitmessage) | `'Requested command is not available.'` | 1199 | | [`caseSensitive`](#basic_options-casesensitive) | `false` | 1200 | | [`history`](#basic_options-history) | `true` | 1201 | 1202 | The following options work as shown in the [Basic Options](#basic_options) section. 1203 | 1204 | 1205 | 1206 | 1207 |
promptmaskdefaultInputencodingbufferSize
printcd
1208 | 1209 | ### `promptSimShell` 1210 | 1211 | ```js 1212 | input = readlineSync.promptSimShell([options]) 1213 | ``` 1214 | 1215 | Display a prompt-sign that is similar to that of the user's shell to the user, and then return the input from the user after it has been typed and the Enter key was pressed. 1216 | This method displays a prompt-sign like: 1217 | 1218 | On Windows: 1219 | 1220 | ```console 1221 | C:\Users\User\Path\To\Directory> 1222 | ``` 1223 | 1224 | On others: 1225 | 1226 | ```console 1227 | user@host:~/path/to/directory$ 1228 | ``` 1229 | 1230 | #### Options 1231 | 1232 | The following options have independent default value that is not affected by [Default Options](#basic_options). 1233 | 1234 | | Option Name | Default Value | 1235 | |-------------------|---------------| 1236 | | [`hideEchoBack`](#basic_options-hideechoback) | `false` | 1237 | | [`history`](#basic_options-history) | `true` | 1238 | 1239 | The other options other than [`prompt`](#basic_options-prompt) option work as shown in the [Basic Options](#basic_options) section. 1240 | 1241 | ### `keyInYN` 1242 | 1243 | ```js 1244 | boolYesOrEmpty = readlineSync.keyInYN([query[, options]]) 1245 | ``` 1246 | 1247 | Display a `query` to the user if it's specified, and then return a boolean or an empty string immediately a key was pressed by the user, **without pressing the Enter key**. Note that the user has no chance to change the input. 1248 | This method works like the `window.confirm` method of web browsers. A return value means "Yes" or "No" the user said. It differ depending on the pressed key: 1249 | 1250 | * `Y`: `true` 1251 | * `N`: `false` 1252 | * other: `''` 1253 | 1254 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 1255 | The default value of `query` is `'Are you sure? '`. 1256 | 1257 | The keys other than `Y` and `N` are also accepted (If you want to know a user's wish explicitly, use [`keyInYNStrict`](#utility_methods-keyinynstrict) method). Therefore, if you let the user make an important decision (e.g. files are removed), check whether the return value is not *falsy*. That is, a default is "No". 1258 | 1259 | For example: 1260 | 1261 | ```js 1262 | if (!readlineSync.keyInYN('Do you want to install this?')) { 1263 | // Key that is not `Y` was pressed. 1264 | process.exit(); 1265 | } 1266 | // Do something... 1267 | ``` 1268 | 1269 | Or if you let the user stop something that must be done (e.g. something about the security), check whether the return value is `false` explicitly. That is, a default is "Yes". 1270 | 1271 | For example: 1272 | 1273 | ```js 1274 | // Don't use `(!readlineSync.keyInYN())`. 1275 | if (readlineSync.keyInYN('Continue virus scan?') === false) { 1276 | // `N` key was pressed. 1277 | process.exit(); 1278 | } 1279 | // Continue... 1280 | ``` 1281 | 1282 | #### Options 1283 | 1284 | The following options work as shown in the [Basic Options](#basic_options) section. 1285 | 1286 | 1287 | 1288 |
encodingprint
1289 | 1290 | And the following additional option is available. 1291 | 1292 | ##### `guide` 1293 | 1294 | *Type:* boolean 1295 | *Default:* `true` 1296 | 1297 | If `true` is specified, a string `'[y/n]'` as guide for the user is added to `query`. And `':'` is moved to the end of `query`, or it is added. 1298 | 1299 | For example: 1300 | 1301 | ```js 1302 | readlineSync.keyInYN('Do you like me?'); // No colon 1303 | readlineSync.keyInYN('Really? :'); // Colon already exists 1304 | ``` 1305 | 1306 | ```console 1307 | Do you like me? [y/n]: y 1308 | Really? [y/n]: y 1309 | ``` 1310 | 1311 | ### `keyInYNStrict` 1312 | 1313 | ```js 1314 | boolYes = readlineSync.keyInYNStrict([query[, options]]) 1315 | ``` 1316 | 1317 | Display a `query` to the user if it's specified, and then accept only `Y` or `N` key, and then return a boolean immediately it was pressed by the user, **without pressing the Enter key**. Note that the user has no chance to change the input. 1318 | This method works like the `window.confirm` method of web browsers. A return value means "Yes" or "No" the user said. It differ depending on the pressed key: 1319 | 1320 | * `Y`: `true` 1321 | * `N`: `false` 1322 | 1323 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 1324 | The default value of `query` is `'Are you sure? '`. 1325 | 1326 | A key other than `Y` and `N` is not accepted. That is, a return value has no default. Therefore, the user has to tell an own wish explicitly. If you want to know a user's wish easily, use [`keyInYN`](#utility_methods-keyinyn) method. 1327 | 1328 | This method works same to [`keyInYN`](#utility_methods-keyinyn) method except that this accept only `Y` or `N` key (Therefore, a return value is boolean every time). The options also work same to [`keyInYN`](#utility_methods-keyinyn) method. 1329 | 1330 | ### `keyInPause` 1331 | 1332 | ```js 1333 | readlineSync.keyInPause([query[, options]]) 1334 | ``` 1335 | 1336 | Display a `query` to the user if it's specified, and then just wait for a key to be pressed by the user. 1337 | This method works like the `window.alert` method of web browsers. This is used to make the running of script pause and show something to the user, or wait for the user to be ready. 1338 | By default, any key is accepted (See: [Note](#utility_methods-keyinpause-note)). You can change this behavior by specifying [`limit`](#basic_options-limit) option (e.g. accept only a Space Bar). 1339 | 1340 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 1341 | The default value of `query` is `'Continue...'`. 1342 | 1343 | For example: 1344 | 1345 | ```js 1346 | // Have made the preparations for something... 1347 | console.log('==== Information of Your Computer ===='); 1348 | console.log(info); // This can be `query`. 1349 | readlineSync.keyInPause(); 1350 | console.log('It\'s executing now...'); 1351 | // Do something... 1352 | ``` 1353 | 1354 | ```console 1355 | ==== Information of Your Computer ==== 1356 | FOO: 123456 1357 | BAR: abcdef 1358 | Continue... (Hit any key) 1359 | It's executing now... 1360 | ``` 1361 | 1362 | #### Options 1363 | 1364 | The following option has independent default value that is not affected by [Default Options](#basic_options). 1365 | 1366 | | Option Name | Default Value | 1367 | |-------------------|---------------| 1368 | | [`limit`](#basic_options-limit) | `null` | 1369 | 1370 | The following options work as shown in the [Basic Options](#basic_options) section. 1371 | 1372 | 1373 | 1374 |
caseSensitiveencodingprint
1375 | 1376 | And the following additional option is available. 1377 | 1378 | ##### `guide` 1379 | 1380 | *Type:* boolean 1381 | *Default:* `true` 1382 | 1383 | If `true` is specified, a string `'(Hit any key)'` as guide for the user is added to `query`. 1384 | 1385 | For example: 1386 | 1387 | ```js 1388 | readlineSync.keyInPause('It\'s pausing now...'); 1389 | ``` 1390 | 1391 | ```console 1392 | It's pausing now... (Hit any key) 1393 | ``` 1394 | 1395 | #### Note 1396 | 1397 | Control keys including Enter key are not accepted by `keyIn*` methods. 1398 | If you want to wait until the user presses Enter key, use `question*` methods instead of `keyIn*` methods. For example: 1399 | 1400 | ```js 1401 | readlineSync.question('Hit Enter key to continue.', {hideEchoBack: true, mask: ''}); 1402 | ``` 1403 | 1404 | ### `keyInSelect` 1405 | 1406 | ```js 1407 | index = readlineSync.keyInSelect(items[, query[, options]]) 1408 | ``` 1409 | 1410 | Display the list that was created with the `items` Array, and the `query` to the user if it's specified, and then return the number as an index of the `items` Array immediately it was chosen by pressing a key by the user, **without pressing the Enter key**. Note that the user has no chance to change the input. 1411 | 1412 | The `query` is handled the same as that of the [`question`](#basic_methods-question) method. 1413 | The default value of `query` is `'Choose one from list: '`. 1414 | 1415 | The minimum length of `items` Array is 1 and maximum length is 35. These elements are displayed as item list. A key to let the user choose an item is assigned to each item automatically in sequence like "1, 2, 3 ... 9, A, B, C ...". A number as an index of the `items` Array that corresponds to a chosen item by the user is returned. 1416 | 1417 | **Note:** Even if the `items` Array has only less than 35 items, a long Array that forces an user to scroll the list may irritate the user. Remember, the user might be in a console environment that doesn't support scrolling the screen. If you want to use a long `items` Array (e.g. more than 10 items), you should consider a "Pagination". (See [example](https://github.com/anseki/readline-sync/issues/60#issuecomment-324533678).) 1418 | 1419 | For example: 1420 | 1421 | ```js 1422 | frameworks = ['Express', 'hapi', 'flatiron', 'MEAN.JS', 'locomotive']; 1423 | index = readlineSync.keyInSelect(frameworks, 'Which framework?'); 1424 | console.log(frameworks[index] + ' is enabled.'); 1425 | ``` 1426 | 1427 | ```console 1428 | [1] Express 1429 | [2] hapi 1430 | [3] flatiron 1431 | [4] MEAN.JS 1432 | [5] locomotive 1433 | [0] CANCEL 1434 | 1435 | Which framework? [1...5 / 0]: 2 1436 | hapi is enabled. 1437 | ``` 1438 | 1439 | #### Options 1440 | 1441 | The following option has independent default value that is not affected by [Default Options](#basic_options). 1442 | 1443 | | Option Name | Default Value | 1444 | |-------------------|---------------| 1445 | | [`hideEchoBack`](#basic_options-hideechoback) | `false` | 1446 | 1447 | The following options work as shown in the [Basic Options](#basic_options) section. 1448 | 1449 | 1450 | 1451 |
maskencodingprint
1452 | 1453 | And the following additional options are available. 1454 | 1455 | ##### `guide` 1456 | 1457 | *Type:* boolean 1458 | *Default:* `true` 1459 | 1460 | If `true` is specified, a string like `'[1...5]'` as guide for the user is added to `query`. And `':'` is moved to the end of `query`, or it is added. This is the key list that corresponds to the item list. 1461 | 1462 | ##### `cancel` 1463 | 1464 | *Type:* boolean, string or others 1465 | *Default:* `'CANCEL'` 1466 | 1467 | 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. 1468 | 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. 1469 | 1470 | #### Additional Placeholders 1471 | 1472 | The following additional [placeholder](#placeholders) parameters are available. 1473 | 1474 | ##### `itemsCount` 1475 | 1476 | A length of a current `items` Array. 1477 | 1478 | For example: 1479 | 1480 | ```js 1481 | items = ['item-A', 'item-B', 'item-C', 'item-D', 'item-E']; 1482 | index = readlineSync.keyInSelect(items, null, 1483 | {cancel: 'Show more than $ items'}); 1484 | ``` 1485 | 1486 | ```console 1487 | [1] item-A 1488 | [2] item-B 1489 | [3] item-C 1490 | [4] item-D 1491 | [5] item-E 1492 | [0] Show more than 5 items 1493 | ``` 1494 | 1495 | ##### `firstItem` 1496 | 1497 | A first item in a current `items` Array. 1498 | 1499 | For example: 1500 | 1501 | ```js 1502 | index = readlineSync.keyInSelect(items, 'Choose $ or another: '); 1503 | ``` 1504 | 1505 | ##### `lastItem` 1506 | 1507 | A last item in a current `items` Array. 1508 | 1509 | For example: 1510 | 1511 | ```js 1512 | items = ['January', 'February', 'March', 'April', 'May', 'June']; 1513 | index = readlineSync.keyInSelect(items, null, 1514 | {cancel: 'In after $'}); 1515 | ``` 1516 | 1517 | ```console 1518 | [1] January 1519 | [2] February 1520 | [3] March 1521 | [4] April 1522 | [5] May 1523 | [6] June 1524 | [0] In after June 1525 | ``` 1526 | 1527 | ## Placeholders 1528 | 1529 | [`hideEchoBack`, `mask`, `defaultInput`, `caseSensitive`, `keepWhitespace`, `encoding`, `bufferSize`, `history`, `cd`, `limit`, `trueValue`, `falseValue`](#placeholders-parameters-hideechoback_mask_defaultinput_casesensitive_keepwhitespace_encoding_buffersize_history_cd_limit_truevalue_falsevalue), [`limitCount`, `limitCountNotZero`](#placeholders-parameters-limitcount_limitcountnotzero), [`lastInput`](#placeholders-parameters-lastinput), [`history_mN`](#placeholders-parameters-historymn), [`cwd`, `CWD`, `cwdHome`](#placeholders-parameters-cwd_cwd_cwdhome), [`date`, `time`, `localeDate`, `localeTime`](#placeholders-parameters-date_time_localedate_localetime), [`C1-C2`](#placeholders-parameters-c1_c2) 1530 | 1531 | The placeholders in the text are replaced to another string. 1532 | 1533 | For example, the [`limitMessage`](#basic_options-limitmessage) option to display a warning message that means that the command the user requested is not available: 1534 | 1535 | ```js 1536 | command = readlineSync.prompt({ 1537 | limit: ['add', 'remove'], 1538 | limitMessage: '$ is not available.' 1539 | }); 1540 | ``` 1541 | 1542 | ```console 1543 | > delete 1544 | delete is not available. 1545 | ``` 1546 | 1547 | The placeholders can be included in: 1548 | 1549 | * `query` argument 1550 | * [`prompt`](#basic_options-prompt) and [`limitMessage`](#basic_options-limitmessage) options 1551 | * [`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 ([`C1-C2`](#placeholders-parameters-c1_c2) parameter only) 1552 | * And some additional options for the [Utility Methods](#utility_methods). 1553 | 1554 | ### Syntax 1555 | 1556 | ``` 1557 | $ 1558 | ``` 1559 | 1560 | Or 1561 | 1562 | ``` 1563 | $<(text1)parameter(text2)> 1564 | ``` 1565 | 1566 | The placeholder is replaced to a string that is got by a `parameter`. 1567 | Both the `(text1)` and `(text2)` are optional. 1568 | 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'`. 1569 | 1570 | At the each position of `'(text1)'` and `'(text2)'`, `'text1'` and `'text2'` are put when a string that was got by a `parameter` has more than 0 length. If that got string is `''`, a placeholder with or without `'(text1)'` and `'(text2)'` is replaced to `''`. 1571 | 1572 | For example, a warning message that means that the command the user requested is not available: 1573 | 1574 | ```js 1575 | command = readlineSync.prompt({ 1576 | limit: ['add', 'remove'], 1577 | limitMessage: 'Refused $ you requested. Please input another.' 1578 | }); 1579 | ``` 1580 | 1581 | ```console 1582 | > give-me-car 1583 | Refused give-me-car you requested. Please input another. 1584 | ``` 1585 | 1586 | It looks like no problem. 1587 | But when the user input nothing (hit only the Enter key), and then a message is displayed: 1588 | 1589 | ```console 1590 | > 1591 | Refused you requested. Please input another. 1592 | ``` 1593 | 1594 | This goes well: 1595 | 1596 | ```js 1597 | command = readlineSync.prompt({ 1598 | limit: ['add', 'remove'], 1599 | limitMessage: 'Refused $. Please input another.' 1600 | }); 1601 | ``` 1602 | 1603 | ```console 1604 | > 1605 | Refused . Please input another. 1606 | ``` 1607 | 1608 | (May be more better: `'$<(Refused )lastInput( you requested. )>Please input another.'`) 1609 | 1610 | **Note:** The syntax `${parameter}` of older version is still supported, but this should not be used because it may be confused with template string syntax of ES6. And this will not be supported in due course of time. 1611 | 1612 | ### Parameters 1613 | 1614 | The following parameters are available. And some additional parameters are available in the [Utility Methods](#utility_methods). 1615 | 1616 | #### `hideEchoBack`, `mask`, `defaultInput`, `caseSensitive`, `keepWhitespace`, `encoding`, `bufferSize`, `history`, `cd`, `limit`, `trueValue`, `falseValue` 1617 | 1618 | A current value of each option. 1619 | 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. 1620 | 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. 1621 | 1622 | For example: 1623 | 1624 | ```js 1625 | input = readlineSync.question( 1626 | 'Input something or the Enter key as "$": ', 1627 | {defaultInput: 'hello'} 1628 | ); 1629 | ``` 1630 | 1631 | ```console 1632 | Input something or the Enter key as "hello": 1633 | ``` 1634 | 1635 | #### `limitCount`, `limitCountNotZero` 1636 | 1637 | A length of a current value of the [`limit`](#basic_options-limit) option. 1638 | When the value of the [`limit`](#basic_options-limit) option is empty, `'$'` is replaced to `'0'`, `'$'` is replaced to `''`. 1639 | 1640 | For example: 1641 | 1642 | ```js 1643 | action = readlineSync.question( 1644 | 'Choose action$<( from )limitCountNotZero( actions)>: ', 1645 | {limit: availableActions} 1646 | ); 1647 | ``` 1648 | 1649 | ```console 1650 | Choose action from 5 actions: 1651 | ``` 1652 | 1653 | #### `lastInput` 1654 | 1655 | A last input from the user. 1656 | In any case, this is saved. 1657 | 1658 | For example: 1659 | 1660 | ```js 1661 | command = readlineSync.prompt({ 1662 | limit: availableCommands, 1663 | limitMessage: '$ is not available.' 1664 | }); 1665 | ``` 1666 | 1667 | ```console 1668 | > wrong-command 1669 | wrong-command is not available. 1670 | ``` 1671 | 1672 | #### `history_mN` 1673 | 1674 | When the history expansion feature is enabled (see [`history`](#basic_options-history) option), a current command line minus `N`. 1675 | *This feature keeps the previous input only.* That is, only `history_m1` is supported. 1676 | 1677 | For example: 1678 | 1679 | ```js 1680 | while (true) { 1681 | input = readlineSync.question('Something$<( or "!!" as ")history_m1(")>: '); 1682 | console.log('-- You said "' + input + '"'); 1683 | } 1684 | ``` 1685 | 1686 | ```console 1687 | Something: hello 1688 | -- You said "hello" 1689 | Something or "!!" as "hello": !! 1690 | hello 1691 | -- You said "hello" 1692 | ``` 1693 | 1694 | #### `cwd`, `CWD`, `cwdHome` 1695 | 1696 | A current working directory. 1697 | 1698 | * `cwd`: A full-path 1699 | * `CWD`: A directory name 1700 | * `cwdHome`: A path that includes `~` as the home directory 1701 | 1702 | For example, like bash/zsh: 1703 | 1704 | ```js 1705 | command = readlineSync.prompt({prompt: '[$]$ '}); 1706 | ``` 1707 | 1708 | ```console 1709 | [~/foo/bar]$ 1710 | ``` 1711 | 1712 | #### `date`, `time`, `localeDate`, `localeTime` 1713 | 1714 | A string as current date or time. 1715 | 1716 | * `date`: A date portion 1717 | * `time`: A time portion 1718 | * `localeDate`: A locality sensitive representation of the date portion based on system settings 1719 | * `localeTime`: A locality sensitive representation of the time portion based on system settings 1720 | 1721 | For example: 1722 | 1723 | ```js 1724 | command = readlineSync.prompt({prompt: '[$]> '}); 1725 | ``` 1726 | 1727 | ```console 1728 | [04/21/2015]> 1729 | ``` 1730 | 1731 | #### `C1-C2` 1732 | 1733 | _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_ 1734 | 1735 | A character list. 1736 | `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'`. 1737 | 1738 | For example, let the user input a password that is created with alphabet: 1739 | 1740 | ```js 1741 | password = readlineSync.questionNewPassword('PASSWORD: ', {charlist: '$'}); 1742 | ``` 1743 | 1744 | See also [`limit` option for `keyIn*` method](#basic_options-limit-for_keyin_method). 1745 | 1746 | ## Special method `getRawInput` 1747 | 1748 | ```js 1749 | rawInput = readlineSync.getRawInput() 1750 | ``` 1751 | 1752 | Return a raw input data of last method. 1753 | When the input was terminated with no data, a `NULL` is inserted to the data. 1754 | 1755 | This might contain control-codes (e.g. `LF`, `CR`, `EOF`, etc.), therefore, it might be used to get `^D` that was input. But you should understand each environments for that. Or, **you should not use this** if your script is used in multiple environments. 1756 | For example, when the user input `EOF` (`^D` in Unix like system, `^Z` in Windows), `x1A` (`EOF`) is returned in Windows, and `x00` (`NULL`) is returned in Unix like system. And `x04` (`EOT`) is returned in Unix like system with raw-mode. And also, when [external program](#note-reading_by_external_program) is used, nothing is returned. See also [Control characters](#note-control_characters). 1757 | You may examine each environment and you must test your script very much, if you want to handle the raw input data. 1758 | 1759 | ## With Task Runner 1760 | 1761 | The easy way to control a flow of the task runner by the input from the user: 1762 | 1763 | * [Grunt](http://gruntjs.com/) plugin: [grunt-confirm](https://github.com/anseki/grunt-confirm) 1764 | * [gulp](http://gulpjs.com/) plugin: [gulp-confirm](https://github.com/anseki/gulp-confirm) 1765 | 1766 | If you want to control a flow of the task runner (e.g. [Grunt](http://gruntjs.com/)), call readlineSync in a task callback that is called by the task runner. Then a flow of tasks is paused and it is controlled by the user. 1767 | 1768 | For example, by using [grunt-task-helper](https://github.com/anseki/grunt-task-helper): 1769 | 1770 | ```console 1771 | $ grunt 1772 | Running "fileCopy" task 1773 | Files already exist: 1774 | file-a.png 1775 | file-b.js 1776 | Overwrite? [y/n]: y 1777 | file-a.png copied. 1778 | file-b.js copied. 1779 | Done. 1780 | ``` 1781 | 1782 | `Gruntfile.js` 1783 | 1784 | ```js 1785 | grunt.initConfig({ 1786 | taskHelper: { 1787 | fileCopy: { 1788 | options: { 1789 | handlerByTask: function() { 1790 | // Abort the task if user don't want it. 1791 | return readlineSync.keyInYN('Overwrite?'); 1792 | }, 1793 | filesArray: [] 1794 | }, 1795 | ... 1796 | } 1797 | }, 1798 | copy: { 1799 | fileCopy: { 1800 | files: '<%= taskHelper.fileCopy.options.filesArray %>' 1801 | } 1802 | } 1803 | }); 1804 | ``` 1805 | 1806 | ## Note 1807 | 1808 | ### Platforms 1809 | 1810 | TTY interfaces are different by the platforms. If the platform doesn't support the interactively reading from TTY, an error is thrown. 1811 | 1812 | ```js 1813 | try { 1814 | answer = readlineSync.question('What is your favorite food? '); 1815 | } catch (e) { 1816 | console.error(e); 1817 | process.exit(1); 1818 | } 1819 | ``` 1820 | 1821 | ### Control characters 1822 | 1823 | TTY interfaces are different by the platforms. In some environments, ANSI escape sequences might be ignored. For example, in non-POSIX TTY such as Windows CMD does not support it (that of Windows 8 especially has problems). Since readlineSync does not use Node.js library that emulates POSIX TTY (but that is still incomplete), those characters may be not parsed. Then, using ANSI escape sequences is not recommended if you will support more environments. 1824 | Also, control characters user input might be not accepted or parsed. That behavior differs depending on the environment. And current Node.js does not support controlling a readline system library. 1825 | 1826 | ### Reading by external program 1827 | 1828 | readlineSync tries to read from a console by using the external program if it is needed (e.g. when the input stream is redirected on Windows XP). And if the running Node.js doesn't support the [Synchronous Process Execution](http://nodejs.org/api/child_process.html#child_process_synchronous_process_creation) (i.e. Node.js v0.10-), readlineSync uses "piping via files" for the synchronous execution. 1829 | As everyone knows, "piping via files" is no good. It blocks the event loop and a process. It might make the your script be slow. 1830 | 1831 | Why did I choose it? : 1832 | 1833 | * Good modules (native addon) for the synchronous execution exist, but node-gyp can't compile those in some platforms or Node.js versions. 1834 | * I think that the security is important more than the speed. Some modules have problem about security. Those don't protect the data. I think that the speed is not needed usually, because readlineSync is used while user types keys. 1835 | 1836 | ## Deprecated methods and options 1837 | 1838 | See [README-Deprecated.md](README-Deprecated.md). 1839 | -------------------------------------------------------------------------------- /lib/encrypt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * readlineSync 3 | * https://github.com/anseki/readline-sync 4 | * 5 | * Copyright (c) 2019 anseki 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | var cipher = require('crypto').createCipher( 10 | process.argv[2] /*algorithm*/, process.argv[3] /*password*/), 11 | stdin = process.stdin, 12 | stdout = process.stdout, 13 | crypted = ''; 14 | 15 | stdin.resume(); 16 | stdin.setEncoding('utf8'); 17 | stdin.on('data', function(d) { 18 | crypted += cipher.update(d, 'utf8', 'hex'); 19 | }); 20 | stdin.on('end', function() { 21 | stdout.write(crypted + cipher.final('hex'), 'binary', function() { 22 | process.exit(0); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /lib/read.ps1: -------------------------------------------------------------------------------- 1 | # readlineSync 2 | # https://github.com/anseki/readline-sync 3 | # 4 | # Copyright (c) 2019 anseki 5 | # Licensed under the MIT license. 6 | 7 | Param( 8 | [string] $display, 9 | [switch] $displayOnly, 10 | [switch] $keyIn, 11 | [switch] $hideEchoBack, 12 | [string] $mask, 13 | [string] $limit, 14 | [switch] $caseSensitive 15 | ) 16 | 17 | $ErrorActionPreference = 'Stop' # for cmdlet 18 | trap { 19 | # `throw $_` and `Write-Error $_` return exit-code 0 20 | $Host.UI.WriteErrorLine($_) 21 | exit 1 22 | } 23 | 24 | function decodeArg ($arg) { 25 | [Regex]::Replace($arg, '#(\d+);', { [char][int] $args[0].Groups[1].Value }) 26 | } 27 | 28 | $options = @{} 29 | foreach ($arg in @('display', 'displayOnly', 'keyIn', 'hideEchoBack', 'mask', 'limit', 'caseSensitive')) { 30 | $options.Add($arg, (Get-Variable $arg -ValueOnly)) 31 | } 32 | $argList = New-Object string[] $options.Keys.Count 33 | $options.Keys.CopyTo($argList, 0) 34 | foreach ($arg in $argList) { 35 | if ($options[$arg] -is [string] -and $options[$arg]) 36 | { $options[$arg] = decodeArg $options[$arg] } 37 | } 38 | 39 | [string] $inputTTY = '' 40 | [bool] $silent = -not $options.display -and 41 | $options.keyIn -and $options.hideEchoBack -and -not $options.mask 42 | [bool] $isCooked = -not $options.hideEchoBack -and -not $options.keyIn 43 | 44 | # Instant method that opens TTY without CreateFile via P/Invoke in .NET Framework 45 | # **NOTE** Don't include special characters of DOS in $command when $getRes is True. 46 | # [string] $cmdPath = $Env:ComSpec 47 | # [string] $psPath = 'powershell.exe' 48 | function execWithTTY ($command, $getRes = $False, $throwError = $False) { 49 | if ($getRes) { 50 | $res = (cmd.exe /C "CON powershell.exe -Command -" 58 | if ($LastExitCode -ne 0) { 59 | if ($throwError) { throw $LastExitCode } 60 | else { exit $LastExitCode } 61 | } 62 | } 63 | } 64 | 65 | function writeTTY ($text) { 66 | execWithTTY ('Write-Host (''' + 67 | (($text -replace '''', '''''') -replace '[\r\n]', '''+"`n"+''') + ''') -NoNewline') 68 | } 69 | 70 | if ($options.display) { 71 | writeTTY $options.display 72 | } 73 | if ($options.displayOnly) { return "''" } 74 | 75 | if (-not $options.keyIn -and $options.hideEchoBack -and $options.mask -eq '*') { 76 | # It fails when it's not ready. 77 | try { 78 | $inputTTY = execWithTTY ('$text = Read-Host -AsSecureString;' + 79 | '$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($text);' + 80 | '[Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True $True 81 | return '''' + $inputTTY + '''' 82 | } catch {} # ignore 83 | } 84 | 85 | if ($options.keyIn) { $reqSize = 1 } 86 | 87 | if ($options.keyIn -and $options.limit) { 88 | $limitPtn = '[^' + $options.limit + ']' 89 | } 90 | 91 | while ($True) { 92 | if (-not $isCooked) { 93 | $chunk = [char][int] (execWithTTY '[int] [Console]::ReadKey($True).KeyChar' $True) 94 | } else { 95 | $chunk = execWithTTY 'Read-Host' $True 96 | $chunk += "`n" 97 | } 98 | 99 | if ($chunk -and $chunk -match '^(.*?)[\r\n]') { 100 | $chunk = $Matches[1] 101 | $atEol = $True 102 | } else { $atEol = $False } 103 | 104 | # other ctrl-chars 105 | if ($chunk) { $chunk = $chunk -replace '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '' } 106 | if ($chunk -and $limitPtn) { 107 | if ($options.caseSensitive) { $chunk = $chunk -creplace $limitPtn, '' } 108 | else { $chunk = $chunk -ireplace $limitPtn, '' } 109 | } 110 | 111 | if ($chunk) { 112 | if (-not $isCooked) { 113 | if (-not $options.hideEchoBack) { 114 | writeTTY $chunk 115 | } elseif ($options.mask) { 116 | writeTTY ($options.mask * $chunk.Length) 117 | } 118 | } 119 | $inputTTY += $chunk 120 | } 121 | 122 | if ((-not $options.keyIn -and $atEol) -or 123 | ($options.keyIn -and $inputTTY.Length -ge $reqSize)) { break } 124 | } 125 | 126 | if (-not $isCooked -and -not $silent) { execWithTTY 'Write-Host ''''' } # new line 127 | 128 | return "'$inputTTY'" 129 | -------------------------------------------------------------------------------- /lib/read.sh: -------------------------------------------------------------------------------- 1 | # readlineSync 2 | # https://github.com/anseki/readline-sync 3 | # 4 | # Copyright (c) 2019 anseki 5 | # Licensed under the MIT license. 6 | 7 | # Use perl for compatibility of sed/awk of GNU / POSIX, BSD. (and tr) 8 | # Hide "\n" from shell by "\fNL" 9 | 10 | decode_arg() { 11 | printf '%s' "$(printf '%s' "$1" | perl -pe 's/#(\d+);/sprintf("%c", $1)/ge; s/[\r\n]/\fNL/g')" 12 | } 13 | 14 | # getopt(s) 15 | while [ $# -ge 1 ]; do 16 | arg="$(printf '%s' "$1" | grep -E '^-+[^-]+$' | tr '[A-Z]' '[a-z]' | tr -d '-')" 17 | case "$arg" in 18 | 'display') shift; options_display="$(decode_arg "$1")";; 19 | 'displayonly') options_displayOnly=true;; 20 | 'keyin') options_keyIn=true;; 21 | 'hideechoback') options_hideEchoBack=true;; 22 | 'mask') shift; options_mask="$(decode_arg "$1")";; 23 | 'limit') shift; options_limit="$(decode_arg "$1")";; 24 | 'casesensitive') options_caseSensitive=true;; 25 | esac 26 | shift 27 | done 28 | 29 | reset_tty() { 30 | if [ -n "$save_tty" ]; then 31 | stty --file=/dev/tty "$save_tty" 2>/dev/null || \ 32 | stty -F /dev/tty "$save_tty" 2>/dev/null || \ 33 | stty -f /dev/tty "$save_tty" || exit $? 34 | fi 35 | } 36 | trap 'reset_tty' EXIT 37 | save_tty="$(stty --file=/dev/tty -g 2>/dev/null || stty -F /dev/tty -g 2>/dev/null || stty -f /dev/tty -g || exit $?)" 38 | 39 | [ -z "$options_display" ] && [ "$options_keyIn" = true ] && \ 40 | [ "$options_hideEchoBack" = true ] && [ -z "$options_mask" ] && silent=true 41 | [ "$options_hideEchoBack" != true ] && [ "$options_keyIn" != true ] && is_cooked=true 42 | 43 | write_tty() { 44 | # if [ "$2" = true ]; then 45 | # printf '%b' "$1" >/dev/tty 46 | # else 47 | # printf '%s' "$1" >/dev/tty 48 | # fi 49 | printf '%s' "$1" | perl -pe 's/\fNL/\r\n/g' >/dev/tty 50 | } 51 | 52 | replace_allchars() { ( 53 | text='' 54 | for i in $(seq 1 ${#1}) 55 | do 56 | text="$text$2" 57 | done 58 | printf '%s' "$text" 59 | ) } 60 | 61 | if [ -n "$options_display" ]; then 62 | write_tty "$options_display" 63 | fi 64 | if [ "$options_displayOnly" = true ]; then 65 | printf "'%s'" '' 66 | exit 0 67 | fi 68 | 69 | if [ "$is_cooked" = true ]; then 70 | stty --file=/dev/tty cooked 2>/dev/null || \ 71 | stty -F /dev/tty cooked 2>/dev/null || \ 72 | stty -f /dev/tty cooked || exit $? 73 | else 74 | stty --file=/dev/tty raw -echo 2>/dev/null || \ 75 | stty -F /dev/tty raw -echo 2>/dev/null || \ 76 | stty -f /dev/tty raw -echo || exit $? 77 | fi 78 | 79 | [ "$options_keyIn" = true ] && req_size=1 80 | 81 | if [ "$options_keyIn" = true ] && [ -n "$options_limit" ]; then 82 | if [ "$options_caseSensitive" = true ]; then 83 | limit_ptn="$options_limit" 84 | else 85 | # Safe list 86 | # limit_ptn="$(printf '%s' "$options_limit" | sed 's/\([a-z]\)/\L\1\U\1/ig')" 87 | limit_ptn="$(printf '%s' "$options_limit" | perl -pe 's/([a-z])/lc($1) . uc($1)/ige')" 88 | fi 89 | fi 90 | 91 | while : 92 | do 93 | if [ "$is_cooked" != true ]; then 94 | # chunk="$(dd if=/dev/tty bs=1 count=1 2>/dev/null)" 95 | chunk="$(dd if=/dev/tty bs=1 count=1 2>/dev/null | perl -pe 's/[\r\n]/\fNL/g')" 96 | else 97 | IFS= read -r chunk = 0x4020 && charCode <= 0x407F ? 109 | String.fromCharCode(charCode - 0x4000) : ''; 110 | }); 111 | } catch (e) { 112 | WScript.StdErr.WriteLine('ScriptPW.Password Error: ' + e.number + 113 | '\n' + e.description + '\n' + PS_MSG); 114 | WScript.Quit(e.number || 1); 115 | } 116 | writeTTY('\n'); 117 | return text; 118 | } 119 | 120 | function getFso() { 121 | if (!fso) { fso = new ActiveXObject('Scripting.FileSystemObject'); } 122 | return fso; 123 | } 124 | -------------------------------------------------------------------------------- /lib/readline-sync.js: -------------------------------------------------------------------------------- 1 | /* 2 | * readlineSync 3 | * https://github.com/anseki/readline-sync 4 | * 5 | * Copyright (c) 2019 anseki 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var 12 | IS_WIN = process.platform === 'win32', 13 | 14 | ALGORITHM_CIPHER = 'aes-256-cbc', 15 | ALGORITHM_HASH = 'sha256', 16 | DEFAULT_ERR_MSG = 'The current environment doesn\'t support interactive reading from TTY.', 17 | 18 | fs = require('fs'), 19 | TTY = process.binding('tty_wrap').TTY, 20 | childProc = require('child_process'), 21 | pathUtil = require('path'), 22 | 23 | defaultOptions = { 24 | /* eslint-disable key-spacing */ 25 | prompt: '> ', 26 | hideEchoBack: false, 27 | mask: '*', 28 | limit: [], 29 | limitMessage: 'Input another, please.$<( [)limit(])>', 30 | defaultInput: '', 31 | trueValue: [], 32 | falseValue: [], 33 | caseSensitive: false, 34 | keepWhitespace: false, 35 | encoding: 'utf8', 36 | bufferSize: 1024, 37 | print: void 0, 38 | history: true, 39 | cd: false, 40 | phContent: void 0, 41 | preCheck: void 0 42 | /* eslint-enable key-spacing */ 43 | }, 44 | 45 | fdR = 'none', 46 | isRawMode = false, 47 | salt = 0, 48 | lastInput = '', 49 | inputHistory = [], 50 | _DBG_useExt = false, 51 | _DBG_checkOptions = false, 52 | _DBG_checkMethod = false, 53 | fdW, ttyR, extHostPath, extHostArgs, tempdir, rawInput; 54 | 55 | function getHostArgs(options) { 56 | // Send any text to crazy Windows shell safely. 57 | function encodeArg(arg) { 58 | return arg.replace(/[^\w\u0080-\uFFFF]/g, function(chr) { 59 | return '#' + chr.charCodeAt(0) + ';'; 60 | }); 61 | } 62 | 63 | return extHostArgs.concat((function(conf) { 64 | var args = []; 65 | Object.keys(conf).forEach(function(optionName) { 66 | if (conf[optionName] === 'boolean') { 67 | if (options[optionName]) { args.push('--' + optionName); } 68 | } else if (conf[optionName] === 'string') { 69 | if (options[optionName]) { 70 | args.push('--' + optionName, encodeArg(options[optionName])); 71 | } 72 | } 73 | }); 74 | return args; 75 | })({ 76 | /* eslint-disable key-spacing */ 77 | display: 'string', 78 | displayOnly: 'boolean', 79 | keyIn: 'boolean', 80 | hideEchoBack: 'boolean', 81 | mask: 'string', 82 | limit: 'string', 83 | caseSensitive: 'boolean' 84 | /* eslint-enable key-spacing */ 85 | })); 86 | } 87 | 88 | // piping via files (for Node.js v0.10-) 89 | function _execFileSync(options, execOptions) { 90 | 91 | function getTempfile(name) { 92 | var suffix = '', 93 | filepath, fd; 94 | tempdir = tempdir || require('os').tmpdir(); 95 | 96 | while (true) { 97 | filepath = pathUtil.join(tempdir, name + suffix); 98 | try { 99 | fd = fs.openSync(filepath, 'wx'); 100 | } catch (e) { 101 | if (e.code === 'EEXIST') { 102 | suffix++; 103 | continue; 104 | } else { 105 | throw e; 106 | } 107 | } 108 | fs.closeSync(fd); 109 | break; 110 | } 111 | return filepath; 112 | } 113 | 114 | var res = {}, 115 | pathStdout = getTempfile('readline-sync.stdout'), 116 | pathStderr = getTempfile('readline-sync.stderr'), 117 | pathExit = getTempfile('readline-sync.exit'), 118 | pathDone = getTempfile('readline-sync.done'), 119 | crypto = require('crypto'), 120 | hostArgs, shellPath, shellArgs, exitCode, extMessage, shasum, decipher, password; 121 | 122 | shasum = crypto.createHash(ALGORITHM_HASH); 123 | shasum.update('' + process.pid + (salt++) + Math.random()); 124 | password = shasum.digest('hex'); 125 | decipher = crypto.createDecipher(ALGORITHM_CIPHER, password); 126 | 127 | hostArgs = getHostArgs(options); 128 | if (IS_WIN) { 129 | shellPath = process.env.ComSpec || 'cmd.exe'; 130 | process.env.Q = '"'; // The quote (") that isn't escaped. 131 | // `()` for ignore space by echo 132 | shellArgs = ['/V:ON', '/S', '/C', 133 | '(%Q%' + shellPath + '%Q% /V:ON /S /C %Q%' + /* ESLint bug? */ // eslint-disable-line no-path-concat 134 | '%Q%' + extHostPath + '%Q%' + 135 | hostArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') + 136 | ' & (echo !ERRORLEVEL!)>%Q%' + pathExit + '%Q%%Q%) 2>%Q%' + pathStderr + '%Q%' + 137 | ' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' + 138 | ' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' + 139 | ' >%Q%' + pathStdout + '%Q%' + 140 | ' & (echo 1)>%Q%' + pathDone + '%Q%']; 141 | } else { 142 | shellPath = '/bin/sh'; 143 | shellArgs = ['-c', 144 | // Use `()`, not `{}` for `-c` (text param) 145 | '("' + extHostPath + '"' + /* ESLint bug? */ // eslint-disable-line no-path-concat 146 | hostArgs.map(function(arg) { return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') + 147 | '; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' + 148 | ' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' + 149 | ' "' + ALGORITHM_CIPHER + '" "' + password + '"' + 150 | ' >"' + pathStdout + '"' + 151 | '; echo 1 >"' + pathDone + '"']; 152 | } 153 | if (_DBG_checkMethod) { _DBG_checkMethod('_execFileSync', hostArgs); } 154 | try { 155 | childProc.spawn(shellPath, shellArgs, execOptions); 156 | } catch (e) { 157 | res.error = new Error(e.message); 158 | res.error.method = '_execFileSync - spawn'; 159 | res.error.program = shellPath; 160 | res.error.args = shellArgs; 161 | } 162 | 163 | while (fs.readFileSync(pathDone, {encoding: options.encoding}).trim() !== '1') {} // eslint-disable-line no-empty 164 | if ((exitCode = 165 | fs.readFileSync(pathExit, {encoding: options.encoding}).trim()) === '0') { 166 | res.input = 167 | decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}), 168 | 'hex', options.encoding) + 169 | decipher.final(options.encoding); 170 | } else { 171 | extMessage = fs.readFileSync(pathStderr, {encoding: options.encoding}).trim(); 172 | res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); 173 | res.error.method = '_execFileSync'; 174 | res.error.program = shellPath; 175 | res.error.args = shellArgs; 176 | res.error.extMessage = extMessage; 177 | res.error.exitCode = +exitCode; 178 | } 179 | 180 | fs.unlinkSync(pathStdout); 181 | fs.unlinkSync(pathStderr); 182 | fs.unlinkSync(pathExit); 183 | fs.unlinkSync(pathDone); 184 | 185 | return res; 186 | } 187 | 188 | function readlineExt(options) { 189 | var res = {}, 190 | execOptions = {env: process.env, encoding: options.encoding}, 191 | hostArgs, extMessage; 192 | 193 | if (!extHostPath) { 194 | if (IS_WIN) { 195 | if (process.env.PSModulePath) { // Windows PowerShell 196 | extHostPath = 'powershell.exe'; 197 | extHostArgs = ['-ExecutionPolicy', 'Bypass', 198 | '-File', __dirname + '\\read.ps1']; // eslint-disable-line no-path-concat 199 | } else { // Windows Script Host 200 | extHostPath = 'cscript.exe'; 201 | extHostArgs = ['//nologo', __dirname + '\\read.wsh.js']; // eslint-disable-line no-path-concat 202 | } 203 | } else { 204 | extHostPath = '/bin/sh'; 205 | extHostArgs = [__dirname + '/read.sh']; // eslint-disable-line no-path-concat 206 | } 207 | } 208 | if (IS_WIN && !process.env.PSModulePath) { // Windows Script Host 209 | // ScriptPW (Win XP and Server2003) needs TTY stream as STDIN. 210 | // In this case, If STDIN isn't TTY, an error is thrown. 211 | execOptions.stdio = [process.stdin]; 212 | } 213 | 214 | if (childProc.execFileSync) { 215 | hostArgs = getHostArgs(options); 216 | if (_DBG_checkMethod) { _DBG_checkMethod('execFileSync', hostArgs); } 217 | try { 218 | res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions); 219 | } catch (e) { // non-zero exit code 220 | extMessage = e.stderr ? (e.stderr + '').trim() : ''; 221 | res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); 222 | res.error.method = 'execFileSync'; 223 | res.error.program = extHostPath; 224 | res.error.args = hostArgs; 225 | res.error.extMessage = extMessage; 226 | res.error.exitCode = e.status; 227 | res.error.code = e.code; 228 | res.error.signal = e.signal; 229 | } 230 | } else { 231 | res = _execFileSync(options, execOptions); 232 | } 233 | if (!res.error) { 234 | res.input = res.input.replace(/^\s*'|'\s*$/g, ''); 235 | options.display = ''; 236 | } 237 | 238 | return res; 239 | } 240 | 241 | /* 242 | display: string 243 | displayOnly: boolean 244 | keyIn: boolean 245 | hideEchoBack: boolean 246 | mask: string 247 | limit: string (pattern) 248 | caseSensitive: boolean 249 | keepWhitespace: boolean 250 | encoding, bufferSize, print 251 | */ 252 | function _readlineSync(options) { 253 | var input = '', 254 | displaySave = options.display, 255 | silent = !options.display && options.keyIn && options.hideEchoBack && !options.mask; 256 | 257 | function tryExt() { 258 | var res = readlineExt(options); 259 | if (res.error) { throw res.error; } 260 | return res.input; 261 | } 262 | 263 | if (_DBG_checkOptions) { _DBG_checkOptions(options); } 264 | 265 | (function() { // open TTY 266 | var fsB, constants, verNum; 267 | 268 | function getFsB() { 269 | if (!fsB) { 270 | fsB = process.binding('fs'); // For raw device path 271 | constants = process.binding('constants'); 272 | // for v6.3.0+ 273 | constants = constants && constants.fs && typeof constants.fs.O_RDWR === 'number' 274 | ? constants.fs : constants; 275 | } 276 | return fsB; 277 | } 278 | 279 | if (typeof fdR !== 'string') { return; } 280 | fdR = null; 281 | 282 | if (IS_WIN) { 283 | // iojs-v2.3.2+ input stream can't read first line. (#18) 284 | // ** Don't get process.stdin before check! ** 285 | // Fixed v5.1.0 286 | // Fixed v4.2.4 287 | // It regressed again in v5.6.0, it is fixed in v6.2.0. 288 | verNum = (function(ver) { // getVerNum 289 | var nums = ver.replace(/^\D+/, '').split('.'); 290 | var verNum = 0; 291 | if ((nums[0] = +nums[0])) { verNum += nums[0] * 10000; } 292 | if ((nums[1] = +nums[1])) { verNum += nums[1] * 100; } 293 | if ((nums[2] = +nums[2])) { verNum += nums[2]; } 294 | return verNum; 295 | })(process.version); 296 | if (!(verNum >= 20302 && verNum < 40204 || verNum >= 50000 && verNum < 50100 || verNum >= 50600 && verNum < 60200) && 297 | process.stdin.isTTY) { 298 | process.stdin.pause(); 299 | fdR = process.stdin.fd; 300 | ttyR = process.stdin._handle; 301 | } else { 302 | try { 303 | // The stream by fs.openSync('\\\\.\\CON', 'r') can't switch to raw mode. 304 | // 'CONIN$' might fail on XP, 2000, 7 (x86). 305 | fdR = getFsB().open('CONIN$', constants.O_RDWR, parseInt('0666', 8)); 306 | ttyR = new TTY(fdR, true); 307 | } catch (e) { /* ignore */ } 308 | } 309 | 310 | if (process.stdout.isTTY) { 311 | fdW = process.stdout.fd; 312 | } else { 313 | try { 314 | fdW = fs.openSync('\\\\.\\CON', 'w'); 315 | } catch (e) { /* ignore */ } 316 | if (typeof fdW !== 'number') { // Retry 317 | try { 318 | fdW = getFsB().open('CONOUT$', constants.O_RDWR, parseInt('0666', 8)); 319 | } catch (e) { /* ignore */ } 320 | } 321 | } 322 | 323 | } else { 324 | if (process.stdin.isTTY) { 325 | process.stdin.pause(); 326 | try { 327 | fdR = fs.openSync('/dev/tty', 'r'); // device file, not process.stdin 328 | ttyR = process.stdin._handle; 329 | } catch (e) { /* ignore */ } 330 | } else { 331 | // Node.js v0.12 read() fails. 332 | try { 333 | fdR = fs.openSync('/dev/tty', 'r'); 334 | ttyR = new TTY(fdR, false); 335 | } catch (e) { /* ignore */ } 336 | } 337 | 338 | if (process.stdout.isTTY) { 339 | fdW = process.stdout.fd; 340 | } else { 341 | try { 342 | fdW = fs.openSync('/dev/tty', 'w'); 343 | } catch (e) { /* ignore */ } 344 | } 345 | } 346 | })(); 347 | 348 | (function() { // try read 349 | var isCooked = !options.hideEchoBack && !options.keyIn, 350 | atEol, limit, buffer, reqSize, readSize, chunk, line; 351 | rawInput = ''; 352 | 353 | // Node.js v0.10- returns an error if same mode is set. 354 | function setRawMode(mode) { 355 | if (mode === isRawMode) { return true; } 356 | if (ttyR.setRawMode(mode) !== 0) { return false; } 357 | isRawMode = mode; 358 | return true; 359 | } 360 | 361 | if (_DBG_useExt || !ttyR || 362 | typeof fdW !== 'number' && (options.display || !isCooked)) { 363 | input = tryExt(); 364 | return; 365 | } 366 | 367 | if (options.display) { 368 | fs.writeSync(fdW, options.display); 369 | options.display = ''; 370 | } 371 | if (options.displayOnly) { return; } 372 | 373 | if (!setRawMode(!isCooked)) { 374 | input = tryExt(); 375 | return; 376 | } 377 | 378 | reqSize = options.keyIn ? 1 : options.bufferSize; 379 | // Check `allocUnsafe` to make sure of the new API. 380 | buffer = Buffer.allocUnsafe && Buffer.alloc ? Buffer.alloc(reqSize) : new Buffer(reqSize); 381 | 382 | if (options.keyIn && options.limit) { 383 | limit = new RegExp('[^' + options.limit + ']', 384 | 'g' + (options.caseSensitive ? '' : 'i')); 385 | } 386 | 387 | while (true) { 388 | readSize = 0; 389 | try { 390 | readSize = fs.readSync(fdR, buffer, 0, reqSize); 391 | } catch (e) { 392 | if (e.code !== 'EOF') { 393 | setRawMode(false); 394 | input += tryExt(); 395 | return; 396 | } 397 | } 398 | if (readSize > 0) { 399 | chunk = buffer.toString(options.encoding, 0, readSize); 400 | rawInput += chunk; 401 | } else { 402 | chunk = '\n'; 403 | rawInput += String.fromCharCode(0); 404 | } 405 | 406 | if (chunk && typeof (line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') { 407 | chunk = line; 408 | atEol = true; 409 | } 410 | 411 | // other ctrl-chars 412 | // eslint-disable-next-line no-control-regex 413 | if (chunk) { chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); } 414 | if (chunk && limit) { chunk = chunk.replace(limit, ''); } 415 | 416 | if (chunk) { 417 | if (!isCooked) { 418 | if (!options.hideEchoBack) { 419 | fs.writeSync(fdW, chunk); 420 | } else if (options.mask) { 421 | fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask)); 422 | } 423 | } 424 | input += chunk; 425 | } 426 | 427 | if (!options.keyIn && atEol || 428 | options.keyIn && input.length >= reqSize) { break; } 429 | } 430 | 431 | if (!isCooked && !silent) { fs.writeSync(fdW, '\n'); } 432 | setRawMode(false); 433 | })(); 434 | 435 | if (options.print && !silent) { 436 | options.print( 437 | displaySave + ( 438 | options.displayOnly ? '' : ( 439 | options.hideEchoBack ? (new Array(input.length + 1)).join(options.mask) : input 440 | ) + '\n' // must at least write '\n' 441 | ), 442 | options.encoding); 443 | } 444 | 445 | return options.displayOnly ? '' : 446 | (lastInput = options.keepWhitespace || options.keyIn ? input : input.trim()); 447 | } 448 | 449 | function flattenArray(array, validator) { 450 | var flatArray = []; 451 | function _flattenArray(array) { 452 | if (array == null) { return; } 453 | if (Array.isArray(array)) { 454 | array.forEach(_flattenArray); 455 | } else if (!validator || validator(array)) { 456 | flatArray.push(array); 457 | } 458 | } 459 | _flattenArray(array); 460 | return flatArray; 461 | } 462 | 463 | function escapePattern(pattern) { 464 | return pattern.replace(/[\x00-\x7f]/g, // eslint-disable-line no-control-regex 465 | function(s) { return '\\x' + ('00' + s.charCodeAt().toString(16)).substr(-2); }); 466 | } 467 | 468 | // mergeOptions(options1, options2 ... ) 469 | // mergeOptions(true, options1, options2 ... ) 470 | // arg1=true : Start from defaultOptions and pick elements of that. 471 | function mergeOptions() { 472 | var optionsList = Array.prototype.slice.call(arguments), 473 | optionNames, fromDefault; 474 | 475 | if (optionsList.length && typeof optionsList[0] === 'boolean') { 476 | fromDefault = optionsList.shift(); 477 | if (fromDefault) { 478 | optionNames = Object.keys(defaultOptions); 479 | optionsList.unshift(defaultOptions); 480 | } 481 | } 482 | 483 | return optionsList.reduce(function(options, optionsPart) { 484 | if (optionsPart == null) { return options; } 485 | 486 | // ======== DEPRECATED ======== 487 | if (optionsPart.hasOwnProperty('noEchoBack') && 488 | !optionsPart.hasOwnProperty('hideEchoBack')) { 489 | optionsPart.hideEchoBack = optionsPart.noEchoBack; 490 | delete optionsPart.noEchoBack; 491 | } 492 | if (optionsPart.hasOwnProperty('noTrim') && 493 | !optionsPart.hasOwnProperty('keepWhitespace')) { 494 | optionsPart.keepWhitespace = optionsPart.noTrim; 495 | delete optionsPart.noTrim; 496 | } 497 | // ======== /DEPRECATED ======== 498 | 499 | if (!fromDefault) { optionNames = Object.keys(optionsPart); } 500 | optionNames.forEach(function(optionName) { 501 | var value; 502 | if (!optionsPart.hasOwnProperty(optionName)) { return; } 503 | value = optionsPart[optionName]; 504 | /* eslint-disable no-multi-spaces */ 505 | switch (optionName) { 506 | // _readlineSync <- * * -> defaultOptions 507 | // ================ string 508 | case 'mask': // * * 509 | case 'limitMessage': // * 510 | case 'defaultInput': // * 511 | case 'encoding': // * * 512 | value = value != null ? value + '' : ''; 513 | if (value && optionName !== 'limitMessage') { value = value.replace(/[\r\n]/g, ''); } 514 | options[optionName] = value; 515 | break; 516 | // ================ number(int) 517 | case 'bufferSize': // * * 518 | if (!isNaN(value = parseInt(value, 10)) && typeof value === 'number') { 519 | options[optionName] = value; // limited updating (number is needed) 520 | } 521 | break; 522 | // ================ boolean 523 | case 'displayOnly': // * 524 | case 'keyIn': // * 525 | case 'hideEchoBack': // * * 526 | case 'caseSensitive': // * * 527 | case 'keepWhitespace': // * * 528 | case 'history': // * 529 | case 'cd': // * 530 | options[optionName] = !!value; 531 | break; 532 | // ================ array 533 | case 'limit': // * * to string for readlineExt 534 | case 'trueValue': // * 535 | case 'falseValue': // * 536 | options[optionName] = flattenArray(value, function(value) { 537 | var type = typeof value; 538 | return type === 'string' || type === 'number' || 539 | type === 'function' || value instanceof RegExp; 540 | }).map(function(value) { 541 | return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value; 542 | }); 543 | break; 544 | // ================ function 545 | case 'print': // * * 546 | case 'phContent': // * 547 | case 'preCheck': // * 548 | options[optionName] = typeof value === 'function' ? value : void 0; 549 | break; 550 | // ================ other 551 | case 'prompt': // * 552 | case 'display': // * 553 | options[optionName] = value != null ? value : ''; 554 | break; 555 | // no default 556 | } 557 | /* eslint-enable no-multi-spaces */ 558 | }); 559 | return options; 560 | }, {}); 561 | } 562 | 563 | function isMatched(res, comps, caseSensitive) { 564 | return comps.some(function(comp) { 565 | var type = typeof comp; 566 | return type === 'string' 567 | ? (caseSensitive ? res === comp : res.toLowerCase() === comp.toLowerCase()) : 568 | type === 'number' ? parseFloat(res) === comp : 569 | type === 'function' ? comp(res) : 570 | comp instanceof RegExp ? comp.test(res) : false; 571 | }); 572 | } 573 | 574 | function replaceHomePath(path, expand) { 575 | var homePath = pathUtil.normalize( 576 | IS_WIN ? (process.env.HOMEDRIVE || '') + (process.env.HOMEPATH || '') : 577 | process.env.HOME || '').replace(/[/\\]+$/, ''); 578 | path = pathUtil.normalize(path); 579 | return expand ? path.replace(/^~(?=\/|\\|$)/, homePath) : 580 | path.replace(new RegExp('^' + escapePattern(homePath) + 581 | '(?=\\/|\\\\|$)', IS_WIN ? 'i' : ''), '~'); 582 | } 583 | 584 | function replacePlaceholder(text, generator) { 585 | var PTN_INNER = '(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?', 586 | rePlaceholder = new RegExp('(\\$)?(\\$<' + PTN_INNER + '>)', 'g'), 587 | rePlaceholderCompat = new RegExp('(\\$)?(\\$\\{' + PTN_INNER + '\\})', 'g'); 588 | 589 | function getPlaceholderText(s, escape, placeholder, pre, param, post) { 590 | var text; 591 | return escape || typeof (text = generator(param)) !== 'string' ? placeholder : 592 | text ? (pre || '') + text + (post || '') : ''; 593 | } 594 | 595 | return text.replace(rePlaceholder, getPlaceholderText) 596 | .replace(rePlaceholderCompat, getPlaceholderText); 597 | } 598 | 599 | function array2charlist(array, caseSensitive, collectSymbols) { 600 | var group = [], 601 | groupClass = -1, 602 | charCode = 0, 603 | symbols = '', 604 | values, suppressed; 605 | function addGroup(groups, group) { 606 | if (group.length > 3) { // ellipsis 607 | groups.push(group[0] + '...' + group[group.length - 1]); 608 | suppressed = true; 609 | } else if (group.length) { 610 | groups = groups.concat(group); 611 | } 612 | return groups; 613 | } 614 | 615 | values = array.reduce(function(chars, value) { 616 | return chars.concat((value + '').split('')); 617 | }, []).reduce(function(groups, curChar) { 618 | var curGroupClass, curCharCode; 619 | if (!caseSensitive) { curChar = curChar.toLowerCase(); } 620 | curGroupClass = /^\d$/.test(curChar) ? 1 : 621 | /^[A-Z]$/.test(curChar) ? 2 : /^[a-z]$/.test(curChar) ? 3 : 0; 622 | if (collectSymbols && curGroupClass === 0) { 623 | symbols += curChar; 624 | } else { 625 | curCharCode = curChar.charCodeAt(0); 626 | if (curGroupClass && curGroupClass === groupClass && 627 | curCharCode === charCode + 1) { 628 | group.push(curChar); 629 | } else { 630 | groups = addGroup(groups, group); 631 | group = [curChar]; 632 | groupClass = curGroupClass; 633 | } 634 | charCode = curCharCode; 635 | } 636 | return groups; 637 | }, []); 638 | values = addGroup(values, group); // last group 639 | if (symbols) { values.push(symbols); suppressed = true; } 640 | return {values: values, suppressed: suppressed}; 641 | } 642 | 643 | function joinChunks(chunks, suppressed) { 644 | return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/'); 645 | } 646 | 647 | function getPhContent(param, options) { 648 | var resCharlist = {}, 649 | text, values, arg; 650 | if (options.phContent) { 651 | text = options.phContent(param, options); 652 | } 653 | if (typeof text !== 'string') { 654 | switch (param) { 655 | case 'hideEchoBack': 656 | case 'mask': 657 | case 'defaultInput': 658 | case 'caseSensitive': 659 | case 'keepWhitespace': 660 | case 'encoding': 661 | case 'bufferSize': 662 | case 'history': 663 | case 'cd': 664 | text = !options.hasOwnProperty(param) ? '' : 665 | typeof options[param] === 'boolean' ? (options[param] ? 'on' : 'off') : 666 | options[param] + ''; 667 | break; 668 | // case 'prompt': 669 | // case 'query': 670 | // case 'display': 671 | // text = options.hasOwnProperty('displaySrc') ? options.displaySrc + '' : ''; 672 | // break; 673 | case 'limit': 674 | case 'trueValue': 675 | case 'falseValue': 676 | values = options[options.hasOwnProperty(param + 'Src') ? param + 'Src' : param]; 677 | if (options.keyIn) { // suppress 678 | resCharlist = array2charlist(values, options.caseSensitive); 679 | values = resCharlist.values; 680 | } else { 681 | values = values.filter(function(value) { 682 | var type = typeof value; 683 | return type === 'string' || type === 'number'; 684 | }); 685 | } 686 | text = joinChunks(values, resCharlist.suppressed); 687 | break; 688 | case 'limitCount': 689 | case 'limitCountNotZero': 690 | text = options[options.hasOwnProperty('limitSrc') ? 'limitSrc' : 'limit'].length; 691 | text = text || param !== 'limitCountNotZero' ? text + '' : ''; 692 | break; 693 | case 'lastInput': 694 | text = lastInput; 695 | break; 696 | case 'cwd': 697 | case 'CWD': 698 | case 'cwdHome': 699 | text = process.cwd(); 700 | if (param === 'CWD') { 701 | text = pathUtil.basename(text); 702 | } else if (param === 'cwdHome') { 703 | text = replaceHomePath(text); 704 | } 705 | break; 706 | case 'date': 707 | case 'time': 708 | case 'localeDate': 709 | case 'localeTime': 710 | text = (new Date())['to' + 711 | param.replace(/^./, function(str) { return str.toUpperCase(); }) + 712 | 'String'](); 713 | break; 714 | default: // with arg 715 | if (typeof (arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') { 716 | text = inputHistory[inputHistory.length - arg] || ''; 717 | } 718 | } 719 | } 720 | return text; 721 | } 722 | 723 | function getPhCharlist(param) { 724 | var matches = /^(.)-(.)$/.exec(param), 725 | text = '', 726 | from, to, code, step; 727 | if (!matches) { return null; } 728 | from = matches[1].charCodeAt(0); 729 | to = matches[2].charCodeAt(0); 730 | step = from < to ? 1 : -1; 731 | for (code = from; code !== to + step; code += step) { text += String.fromCharCode(code); } 732 | return text; 733 | } 734 | 735 | // cmd "arg" " a r g " "" 'a"r"g' "a""rg" "arg 736 | function parseCl(cl) { 737 | var reToken = new RegExp(/(\s*)(?:("|')(.*?)(?:\2|$)|(\S+))/g), 738 | taken = '', 739 | args = [], 740 | matches, part; 741 | cl = cl.trim(); 742 | while ((matches = reToken.exec(cl))) { 743 | part = matches[3] || matches[4] || ''; 744 | if (matches[1]) { 745 | args.push(taken); 746 | taken = ''; 747 | } 748 | taken += part; 749 | } 750 | if (taken) { args.push(taken); } 751 | return args; 752 | } 753 | 754 | function toBool(res, options) { 755 | return ( 756 | (options.trueValue.length && 757 | isMatched(res, options.trueValue, options.caseSensitive)) ? true : 758 | (options.falseValue.length && 759 | isMatched(res, options.falseValue, options.caseSensitive)) ? false : res); 760 | } 761 | 762 | function getValidLine(options) { 763 | var res, forceNext, limitMessage, 764 | matches, histInput, args, resCheck; 765 | 766 | function _getPhContent(param) { return getPhContent(param, options); } 767 | function addDisplay(text) { options.display += (/[^\r\n]$/.test(options.display) ? '\n' : '') + text; } 768 | 769 | options.limitSrc = options.limit; 770 | options.displaySrc = options.display; 771 | options.limit = ''; // for readlineExt 772 | options.display = replacePlaceholder(options.display + '', _getPhContent); 773 | 774 | while (true) { 775 | res = _readlineSync(options); 776 | forceNext = false; 777 | limitMessage = ''; 778 | 779 | if (options.defaultInput && !res) { res = options.defaultInput; } 780 | 781 | if (options.history) { 782 | if ((matches = /^\s*!(?:!|-1)(:p)?\s*$/.exec(res))) { // `!!` `!-1` +`:p` 783 | histInput = inputHistory[0] || ''; 784 | if (matches[1]) { // only display 785 | forceNext = true; 786 | } else { // replace input 787 | res = histInput; 788 | } 789 | // Show it even if it is empty (NL only). 790 | addDisplay(histInput + '\n'); 791 | if (!forceNext) { // Loop may break 792 | options.displayOnly = true; 793 | _readlineSync(options); 794 | options.displayOnly = false; 795 | } 796 | } else if (res && res !== inputHistory[inputHistory.length - 1]) { 797 | inputHistory = [res]; 798 | } 799 | } 800 | 801 | if (!forceNext && options.cd && res) { 802 | args = parseCl(res); 803 | switch (args[0].toLowerCase()) { 804 | case 'cd': 805 | if (args[1]) { 806 | try { 807 | process.chdir(replaceHomePath(args[1], true)); 808 | } catch (e) { 809 | addDisplay(e + ''); 810 | } 811 | } 812 | forceNext = true; 813 | break; 814 | case 'pwd': 815 | addDisplay(process.cwd()); 816 | forceNext = true; 817 | break; 818 | // no default 819 | } 820 | } 821 | 822 | if (!forceNext && options.preCheck) { 823 | resCheck = options.preCheck(res, options); 824 | res = resCheck.res; 825 | if (resCheck.forceNext) { forceNext = true; } // Don't switch to false. 826 | } 827 | 828 | if (!forceNext) { 829 | if (!options.limitSrc.length || 830 | isMatched(res, options.limitSrc, options.caseSensitive)) { break; } 831 | if (options.limitMessage) { 832 | limitMessage = replacePlaceholder(options.limitMessage, _getPhContent); 833 | } 834 | } 835 | 836 | addDisplay((limitMessage ? limitMessage + '\n' : '') + 837 | replacePlaceholder(options.displaySrc + '', _getPhContent)); 838 | } 839 | return toBool(res, options); 840 | } 841 | 842 | // for dev 843 | exports._DBG_set_useExt = function(val) { _DBG_useExt = val; }; 844 | exports._DBG_set_checkOptions = function(val) { _DBG_checkOptions = val; }; 845 | exports._DBG_set_checkMethod = function(val) { _DBG_checkMethod = val; }; 846 | exports._DBG_clearHistory = function() { lastInput = ''; inputHistory = []; }; 847 | 848 | // ------------------------------------ 849 | 850 | exports.setDefaultOptions = function(options) { 851 | defaultOptions = mergeOptions(true, options); 852 | return mergeOptions(true); // copy 853 | }; 854 | 855 | exports.question = function(query, options) { 856 | /* eslint-disable key-spacing */ 857 | return getValidLine(mergeOptions(mergeOptions(true, options), { 858 | display: query 859 | })); 860 | /* eslint-enable key-spacing */ 861 | }; 862 | 863 | exports.prompt = function(options) { 864 | var readOptions = mergeOptions(true, options); 865 | readOptions.display = readOptions.prompt; 866 | return getValidLine(readOptions); 867 | }; 868 | 869 | exports.keyIn = function(query, options) { 870 | /* eslint-disable key-spacing */ 871 | var readOptions = mergeOptions(mergeOptions(true, options), { 872 | display: query, 873 | keyIn: true, 874 | keepWhitespace: true 875 | }); 876 | /* eslint-enable key-spacing */ 877 | 878 | // char list 879 | readOptions.limitSrc = readOptions.limit.filter(function(value) { 880 | var type = typeof value; 881 | return type === 'string' || type === 'number'; 882 | }).map(function(text) { 883 | return replacePlaceholder(text + '', getPhCharlist); 884 | }); 885 | // pattern 886 | readOptions.limit = escapePattern(readOptions.limitSrc.join('')); 887 | 888 | ['trueValue', 'falseValue'].forEach(function(optionName) { 889 | readOptions[optionName] = readOptions[optionName].reduce(function(comps, comp) { 890 | var type = typeof comp; 891 | if (type === 'string' || type === 'number') { 892 | comps = comps.concat((comp + '').split('')); 893 | } else { comps.push(comp); } 894 | return comps; 895 | }, []); 896 | }); 897 | 898 | readOptions.display = replacePlaceholder(readOptions.display + '', 899 | function(param) { return getPhContent(param, readOptions); }); 900 | 901 | return toBool(_readlineSync(readOptions), readOptions); 902 | }; 903 | 904 | // ------------------------------------ 905 | 906 | exports.questionEMail = function(query, options) { 907 | if (query == null) { query = 'Input e-mail address: '; } 908 | /* eslint-disable key-spacing */ 909 | return exports.question(query, mergeOptions({ 910 | // -------- default 911 | hideEchoBack: false, 912 | // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address 913 | 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])?)*$/, 914 | limitMessage: 'Input valid e-mail address, please.', 915 | trueValue: null, 916 | falseValue: null 917 | }, options, { 918 | // -------- forced 919 | keepWhitespace: false, 920 | cd: false 921 | })); 922 | /* eslint-enable key-spacing */ 923 | }; 924 | 925 | exports.questionNewPassword = function(query, options) { 926 | /* eslint-disable key-spacing */ 927 | var resCharlist, min, max, 928 | readOptions = mergeOptions({ 929 | // -------- default 930 | hideEchoBack: true, 931 | mask: '*', 932 | limitMessage: 'It can include: $\n' + 933 | 'And the length must be: $', 934 | trueValue: null, 935 | falseValue: null, 936 | caseSensitive: true 937 | }, options, { 938 | // -------- forced 939 | history: false, 940 | cd: false, 941 | // limit (by charlist etc.), 942 | phContent: function(param) { 943 | return param === 'charlist' ? resCharlist.text : 944 | param === 'length' ? min + '...' + max : null; 945 | } 946 | }), 947 | // added: charlist, min, max, confirmMessage, unmatchMessage 948 | charlist, confirmMessage, unmatchMessage, 949 | limit, limitMessage, res1, res2; 950 | /* eslint-enable key-spacing */ 951 | options = options || {}; 952 | 953 | charlist = replacePlaceholder( 954 | options.charlist ? options.charlist + '' : '$', getPhCharlist); 955 | if (isNaN(min = parseInt(options.min, 10)) || typeof min !== 'number') { min = 12; } 956 | if (isNaN(max = parseInt(options.max, 10)) || typeof max !== 'number') { max = 24; } 957 | limit = new RegExp('^[' + escapePattern(charlist) + 958 | ']{' + min + ',' + max + '}$'); 959 | resCharlist = array2charlist([charlist], readOptions.caseSensitive, true); 960 | resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed); 961 | 962 | confirmMessage = options.confirmMessage != null ? options.confirmMessage : 963 | 'Reinput a same one to confirm it: '; 964 | unmatchMessage = options.unmatchMessage != null ? options.unmatchMessage : 965 | 'It differs from first one.' + 966 | ' Hit only the Enter key if you want to retry from first one.'; 967 | 968 | if (query == null) { query = 'Input new password: '; } 969 | 970 | limitMessage = readOptions.limitMessage; 971 | while (!res2) { 972 | readOptions.limit = limit; 973 | readOptions.limitMessage = limitMessage; 974 | res1 = exports.question(query, readOptions); 975 | 976 | readOptions.limit = [res1, '']; 977 | readOptions.limitMessage = unmatchMessage; 978 | res2 = exports.question(confirmMessage, readOptions); 979 | } 980 | 981 | return res1; 982 | }; 983 | 984 | function _questionNum(query, options, parser) { 985 | var validValue; 986 | function getValidValue(value) { 987 | validValue = parser(value); 988 | return !isNaN(validValue) && typeof validValue === 'number'; 989 | } 990 | /* eslint-disable key-spacing */ 991 | exports.question(query, mergeOptions({ 992 | // -------- default 993 | limitMessage: 'Input valid number, please.' 994 | }, options, { 995 | // -------- forced 996 | limit: getValidValue, 997 | cd: false 998 | // trueValue, falseValue, caseSensitive, keepWhitespace don't work. 999 | })); 1000 | /* eslint-enable key-spacing */ 1001 | return validValue; 1002 | } 1003 | exports.questionInt = function(query, options) { 1004 | return _questionNum(query, options, function(value) { return parseInt(value, 10); }); 1005 | }; 1006 | exports.questionFloat = function(query, options) { 1007 | return _questionNum(query, options, parseFloat); 1008 | }; 1009 | 1010 | exports.questionPath = function(query, options) { 1011 | /* eslint-disable key-spacing */ 1012 | var error = '', 1013 | validPath, // before readOptions 1014 | readOptions = mergeOptions({ 1015 | // -------- default 1016 | hideEchoBack: false, 1017 | limitMessage: '$Input valid path, please.' + 1018 | '$<( Min:)min>$<( Max:)max>', 1019 | history: true, 1020 | cd: true 1021 | }, options, { 1022 | // -------- forced 1023 | keepWhitespace: false, 1024 | limit: function(value) { 1025 | var exists, stat, res; 1026 | value = replaceHomePath(value, true); 1027 | error = ''; // for validate 1028 | // mkdir -p 1029 | function mkdirParents(dirPath) { 1030 | dirPath.split(/\/|\\/).reduce(function(parents, dir) { 1031 | var path = pathUtil.resolve((parents += dir + pathUtil.sep)); 1032 | if (!fs.existsSync(path)) { 1033 | fs.mkdirSync(path); 1034 | } else if (!fs.statSync(path).isDirectory()) { 1035 | throw new Error('Non directory already exists: ' + path); 1036 | } 1037 | return parents; 1038 | }, ''); 1039 | } 1040 | 1041 | try { 1042 | exists = fs.existsSync(value); 1043 | validPath = exists ? fs.realpathSync(value) : pathUtil.resolve(value); 1044 | // options.exists default: true, not-bool: no-check 1045 | if (!options.hasOwnProperty('exists') && !exists || 1046 | typeof options.exists === 'boolean' && options.exists !== exists) { 1047 | error = (exists ? 'Already exists' : 'No such file or directory') + 1048 | ': ' + validPath; 1049 | return false; 1050 | } 1051 | if (!exists && options.create) { 1052 | if (options.isDirectory) { 1053 | mkdirParents(validPath); 1054 | } else { 1055 | mkdirParents(pathUtil.dirname(validPath)); 1056 | fs.closeSync(fs.openSync(validPath, 'w')); // touch 1057 | } 1058 | validPath = fs.realpathSync(validPath); 1059 | } 1060 | if (exists && (options.min || options.max || 1061 | options.isFile || options.isDirectory)) { 1062 | stat = fs.statSync(validPath); 1063 | // type check first (directory has zero size) 1064 | if (options.isFile && !stat.isFile()) { 1065 | error = 'Not file: ' + validPath; 1066 | return false; 1067 | } else if (options.isDirectory && !stat.isDirectory()) { 1068 | error = 'Not directory: ' + validPath; 1069 | return false; 1070 | } else if (options.min && stat.size < +options.min || 1071 | options.max && stat.size > +options.max) { 1072 | error = 'Size ' + stat.size + ' is out of range: ' + validPath; 1073 | return false; 1074 | } 1075 | } 1076 | if (typeof options.validate === 'function' && 1077 | (res = options.validate(validPath)) !== true) { 1078 | if (typeof res === 'string') { error = res; } 1079 | return false; 1080 | } 1081 | } catch (e) { 1082 | error = e + ''; 1083 | return false; 1084 | } 1085 | return true; 1086 | }, 1087 | // trueValue, falseValue, caseSensitive don't work. 1088 | phContent: function(param) { 1089 | return param === 'error' ? error : 1090 | param !== 'min' && param !== 'max' ? null : 1091 | options.hasOwnProperty(param) ? options[param] + '' : ''; 1092 | } 1093 | }); 1094 | // added: exists, create, min, max, isFile, isDirectory, validate 1095 | /* eslint-enable key-spacing */ 1096 | options = options || {}; 1097 | 1098 | if (query == null) { query = 'Input path (you can "cd" and "pwd"): '; } 1099 | 1100 | exports.question(query, readOptions); 1101 | return validPath; 1102 | }; 1103 | 1104 | // props: preCheck, args, hRes, limit 1105 | function getClHandler(commandHandler, options) { 1106 | var clHandler = {}, 1107 | hIndex = {}; 1108 | if (typeof commandHandler === 'object') { 1109 | Object.keys(commandHandler).forEach(function(cmd) { 1110 | if (typeof commandHandler[cmd] === 'function') { 1111 | hIndex[options.caseSensitive ? cmd : cmd.toLowerCase()] = commandHandler[cmd]; 1112 | } 1113 | }); 1114 | clHandler.preCheck = function(res) { 1115 | var cmdKey; 1116 | clHandler.args = parseCl(res); 1117 | cmdKey = clHandler.args[0] || ''; 1118 | if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); } 1119 | clHandler.hRes = 1120 | cmdKey !== '_' && hIndex.hasOwnProperty(cmdKey) 1121 | ? hIndex[cmdKey].apply(res, clHandler.args.slice(1)) : 1122 | hIndex.hasOwnProperty('_') ? hIndex._.apply(res, clHandler.args) : null; 1123 | return {res: res, forceNext: false}; 1124 | }; 1125 | if (!hIndex.hasOwnProperty('_')) { 1126 | clHandler.limit = function() { // It's called after preCheck. 1127 | var cmdKey = clHandler.args[0] || ''; 1128 | if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); } 1129 | return hIndex.hasOwnProperty(cmdKey); 1130 | }; 1131 | } 1132 | } else { 1133 | clHandler.preCheck = function(res) { 1134 | clHandler.args = parseCl(res); 1135 | clHandler.hRes = typeof commandHandler === 'function' 1136 | ? commandHandler.apply(res, clHandler.args) : true; // true for break loop 1137 | return {res: res, forceNext: false}; 1138 | }; 1139 | } 1140 | return clHandler; 1141 | } 1142 | 1143 | exports.promptCL = function(commandHandler, options) { 1144 | /* eslint-disable key-spacing */ 1145 | var readOptions = mergeOptions({ 1146 | // -------- default 1147 | hideEchoBack: false, 1148 | limitMessage: 'Requested command is not available.', 1149 | caseSensitive: false, 1150 | history: true 1151 | }, options), 1152 | // -------- forced 1153 | // trueValue, falseValue, keepWhitespace don't work. 1154 | // preCheck, limit (by clHandler) 1155 | clHandler = getClHandler(commandHandler, readOptions); 1156 | /* eslint-enable key-spacing */ 1157 | readOptions.limit = clHandler.limit; 1158 | readOptions.preCheck = clHandler.preCheck; 1159 | exports.prompt(readOptions); 1160 | return clHandler.args; 1161 | }; 1162 | 1163 | exports.promptLoop = function(inputHandler, options) { 1164 | /* eslint-disable key-spacing */ 1165 | var readOptions = mergeOptions({ 1166 | // -------- default 1167 | hideEchoBack: false, 1168 | trueValue: null, 1169 | falseValue: null, 1170 | caseSensitive: false, 1171 | history: true 1172 | }, options); 1173 | /* eslint-enable key-spacing */ 1174 | while (true) { if (inputHandler(exports.prompt(readOptions))) { break; } } 1175 | // return; // nothing is returned 1176 | }; 1177 | 1178 | exports.promptCLLoop = function(commandHandler, options) { 1179 | /* eslint-disable key-spacing */ 1180 | var readOptions = mergeOptions({ 1181 | // -------- default 1182 | hideEchoBack: false, 1183 | limitMessage: 'Requested command is not available.', 1184 | caseSensitive: false, 1185 | history: true 1186 | }, options), 1187 | // -------- forced 1188 | // trueValue, falseValue, keepWhitespace don't work. 1189 | // preCheck, limit (by clHandler) 1190 | clHandler = getClHandler(commandHandler, readOptions); 1191 | /* eslint-enable key-spacing */ 1192 | readOptions.limit = clHandler.limit; 1193 | readOptions.preCheck = clHandler.preCheck; 1194 | while (true) { 1195 | exports.prompt(readOptions); 1196 | if (clHandler.hRes) { break; } 1197 | } 1198 | // return; // nothing is returned 1199 | }; 1200 | 1201 | exports.promptSimShell = function(options) { 1202 | /* eslint-disable key-spacing */ 1203 | return exports.prompt(mergeOptions({ 1204 | // -------- default 1205 | hideEchoBack: false, 1206 | history: true 1207 | }, options, { 1208 | // -------- forced 1209 | prompt: (function() { 1210 | return IS_WIN ? '$>' : 1211 | // 'user@host:cwd$ ' 1212 | (process.env.USER || '') + 1213 | (process.env.HOSTNAME ? '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') + 1214 | ':$$ '; 1215 | })() 1216 | })); 1217 | /* eslint-enable key-spacing */ 1218 | }; 1219 | 1220 | function _keyInYN(query, options, limit) { 1221 | var res; 1222 | if (query == null) { query = 'Are you sure? '; } 1223 | if ((!options || options.guide !== false) && (query += '')) { 1224 | query = query.replace(/\s*:?\s*$/, '') + ' [y/n]: '; 1225 | } 1226 | /* eslint-disable key-spacing */ 1227 | res = exports.keyIn(query, mergeOptions(options, { 1228 | // -------- forced 1229 | hideEchoBack: false, 1230 | limit: limit, 1231 | trueValue: 'y', 1232 | falseValue: 'n', 1233 | caseSensitive: false 1234 | // mask doesn't work. 1235 | })); 1236 | // added: guide 1237 | /* eslint-enable key-spacing */ 1238 | return typeof res === 'boolean' ? res : ''; 1239 | } 1240 | exports.keyInYN = function(query, options) { return _keyInYN(query, options); }; 1241 | exports.keyInYNStrict = function(query, options) { return _keyInYN(query, options, 'yn'); }; 1242 | 1243 | exports.keyInPause = function(query, options) { 1244 | if (query == null) { query = 'Continue...'; } 1245 | if ((!options || options.guide !== false) && (query += '')) { 1246 | query = query.replace(/\s+$/, '') + ' (Hit any key)'; 1247 | } 1248 | /* eslint-disable key-spacing */ 1249 | exports.keyIn(query, mergeOptions({ 1250 | // -------- default 1251 | limit: null 1252 | }, options, { 1253 | // -------- forced 1254 | hideEchoBack: true, 1255 | mask: '' 1256 | })); 1257 | // added: guide 1258 | /* eslint-enable key-spacing */ 1259 | // return; // nothing is returned 1260 | }; 1261 | 1262 | exports.keyInSelect = function(items, query, options) { 1263 | /* eslint-disable key-spacing */ 1264 | var readOptions = mergeOptions({ 1265 | // -------- default 1266 | hideEchoBack: false 1267 | }, options, { 1268 | // -------- forced 1269 | trueValue: null, 1270 | falseValue: null, 1271 | caseSensitive: false, 1272 | // limit (by items), 1273 | phContent: function(param) { 1274 | return param === 'itemsCount' ? items.length + '' : 1275 | param === 'firstItem' ? (items[0] + '').trim() : 1276 | param === 'lastItem' ? (items[items.length - 1] + '').trim() : null; 1277 | } 1278 | }), 1279 | // added: guide, cancel 1280 | keylist = '', 1281 | key2i = {}, 1282 | charCode = 49 /* '1' */, 1283 | display = '\n'; 1284 | /* eslint-enable key-spacing */ 1285 | if (!Array.isArray(items) || !items.length || items.length > 35) { 1286 | throw '`items` must be Array (max length: 35).'; 1287 | } 1288 | 1289 | items.forEach(function(item, i) { 1290 | var key = String.fromCharCode(charCode); 1291 | keylist += key; 1292 | key2i[key] = i; 1293 | display += '[' + key + '] ' + (item + '').trim() + '\n'; 1294 | charCode = charCode === 57 /* '9' */ ? 97 /* 'a' */ : charCode + 1; 1295 | }); 1296 | if (!options || options.cancel !== false) { 1297 | keylist += '0'; 1298 | key2i['0'] = -1; 1299 | display += '[0] ' + 1300 | (options && options.cancel != null && typeof options.cancel !== 'boolean' 1301 | ? (options.cancel + '').trim() : 'CANCEL') + '\n'; 1302 | } 1303 | readOptions.limit = keylist; 1304 | display += '\n'; 1305 | 1306 | if (query == null) { query = 'Choose one from list: '; } 1307 | if ((query += '')) { 1308 | if (!options || options.guide !== false) { 1309 | query = query.replace(/\s*:?\s*$/, '') + ' [$]: '; 1310 | } 1311 | display += query; 1312 | } 1313 | 1314 | return key2i[exports.keyIn(display, readOptions).toLowerCase()]; 1315 | }; 1316 | 1317 | exports.getRawInput = function() { return rawInput; }; 1318 | 1319 | // ======== DEPRECATED ======== 1320 | function _setOption(optionName, args) { 1321 | var options; 1322 | if (args.length) { options = {}; options[optionName] = args[0]; } 1323 | return exports.setDefaultOptions(options)[optionName]; 1324 | } 1325 | exports.setPrint = function() { return _setOption('print', arguments); }; 1326 | exports.setPrompt = function() { return _setOption('prompt', arguments); }; 1327 | exports.setEncoding = function() { return _setOption('encoding', arguments); }; 1328 | exports.setMask = function() { return _setOption('mask', arguments); }; 1329 | exports.setBufferSize = function() { return _setOption('bufferSize', arguments); }; 1330 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "readline-sync", 3 | "version": "1.4.10", 4 | "title": "readlineSync", 5 | "description": "Synchronous Readline for interactively running to have a conversation with the user via a console(TTY).", 6 | "keywords": [ 7 | "readline", 8 | "synchronous", 9 | "interactive", 10 | "prompt", 11 | "question", 12 | "password", 13 | "cli", 14 | "tty", 15 | "command", 16 | "repl", 17 | "keyboard", 18 | "wait", 19 | "block" 20 | ], 21 | "main": "./lib/readline-sync.js", 22 | "files": [ 23 | "lib/*.@(js|ps1|sh)", 24 | "README-Deprecated.md" 25 | ], 26 | "engines": { 27 | "node": ">= 0.8.0" 28 | }, 29 | "homepage": "https://github.com/anseki/readline-sync", 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/anseki/readline-sync.git" 33 | }, 34 | "bugs": "https://github.com/anseki/readline-sync/issues", 35 | "license": "MIT", 36 | "author": { 37 | "name": "anseki", 38 | "url": "https://github.com/anseki" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /screen_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anseki/readline-sync/0aeb612603ddf2319d8a1c796700bc7d809487b2/screen_01.png -------------------------------------------------------------------------------- /screen_02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anseki/readline-sync/0aeb612603ddf2319d8a1c796700bc7d809487b2/screen_02.gif -------------------------------------------------------------------------------- /screen_03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anseki/readline-sync/0aeb612603ddf2319d8a1c796700bc7d809487b2/screen_03.gif --------------------------------------------------------------------------------