UNPKG

13.6 kBMarkdownView Raw
1![logo](https://user-images.githubusercontent.com/675812/61961326-88346a80-afc7-11e9-9853-f4ef66ce686c.png)
2
3The *de facto* standard input/output manager for Node.js
4
5[![Build Status](https://secure.travis-ci.org/sgmonda/stdio.png)](http://travis-ci.org/sgmonda/stdio)
6[![NPM version](https://img.shields.io/npm/v/stdio.svg)](https://www.npmjs.com/package/stdio)
7[![Known Vulnerabilities](https://snyk.io/test/github/sgmonda/stdio/badge.svg?targetFile=package.json)](https://snyk.io/test/github/sgmonda/stdio?targetFile=package.json)
8[![Maintainability](https://api.codeclimate.com/v1/badges/70cf9b4cdd6a7849b6d1/maintainability)](https://codeclimate.com/github/sgmonda/stdio/maintainability)
9[![codecov](https://codecov.io/gh/sgmonda/stdio/branch/master/graph/badge.svg)](https://codecov.io/gh/sgmonda/stdio)
10
11After a very long time, finally version 2 is here. The cool `stdio` module you cannot live without has been rewritten and improved a lot, with Typescript support, promise-based usage and much more.
12
13**Note**: Version 2 stops supporting non promise-based usage. Some functions have been renamed, too. So it is not compatible with older versions. If you're using an older version of `stdio` please, read this documentation carefully before upgrading.
14
15Table of contents:
16
17- [Installation](#installation)
18- [Usage](#usage)
19 - [getopt()](#getopt)
20 - [read()](#read)
21 - [readLine()](#readline)
22 - [ask()](#ask)
23 - [ProgressBar](#progressbar)
24
25# Installation
26
27To install this module you can use `npm`:
28
29```
30$ npm install stdio
31```
32
33Then you can import it from your project, as a whole module or any of its parts independently:
34
35```javascript
36import stdio from 'stdio';
37```
38
39```javascript
40import { getopt, read } from 'stdio';
41```
42
43# Usage
44
45This module contains the following static functions:
46
47- `getopt()`: a function to parse command-line arguments.
48- `read()`: an async function to read the standard input (or huge files) by lines, without having to worry about system resources.
49- `readLine()`: an async function to read a single line from the standard input.
50- `ask()`: an async function to ask questions in a terminal and wait for a user's response.
51
52And the following classes:
53
54- `ProgressBar`: a class to create command-line progress bars.
55
56Next sections will show how to use all of them.
57
58## getopt()
59
60This function gives parsed UNIX-like command-line and options, preprocessed and ready to be used in an easy way. It is inspired by C standard library under UNIX.
61
62```javascript
63import { getopt } from 'stdio';
64const options = getopt({
65 <option_name_1>: {<definition>},
66 <option_name_2>: {<definition>},
67 <option_name_3>: {<definition>},
68 ...
69});
70```
71
72Where `<definition>` is an object describing each option. These are the supported fields to define an option:
73
74- `key` (`string`): The short name for the option. It is a single-letter string.
75- `description` (`string`): The option description. A text for humans to understand what the option means.
76- `required` (`boolean`): If the option is mandatory or not.
77- `args` (`number|string`): The expected arguments count for the option (if the option expects arguments). This can be a number or the special string `"*"` when it is variable.
78- `multiple` (`boolean`): If the option should be specified for each value. This makes it mandatory to write things like `-m 1 -m 2 -m 3` instead of `-m 1 2 3`.
79- `default`: (`string[]` or `string`): The default value for an option, in case it is not provided in the command.
80
81Positional arguments (those not precedeed by an option) can be customized, too, using the special option `_meta_`, which supports some limits in the amount of required args:
82
83```javascript
84import { getopt } from 'stdio';
85const options = getopt({
86 <option_name_1>: {<definition>},
87 <option_name_2>: {<definition>},
88 <option_name_3>: {<definition>},
89 ...
90 _meta_: { minArgs: <number>, maxArgs: <number>, args: <number> },
91});
92```
93
94In case a required option is not defined or any option is not well used at runtime, an automatic help/usage message is printed, aborting the execution. This message is also shown automatically in case one of the special options `-h, --help` is provided.
95
96```
97USAGE: node example.js [OPTION1] [OPTION2]... arg1 arg2...
98The following options are supported:
99 -<key_1>, --<option_name_1>
100 -<key_2>, --<option_name_2>
101 ...
102```
103
104<details>
105<summary>Behavior customization</summary>
106<p>
107
108In case you want to customize the automatic behavior when a command is wrong using your program, a second argument is supported by the `getopt()` call:
109
110```
111const options = getopt({...}, {<behavior_customizations>});
112```
113
114Here are the supported customizations:
115
116- `printOnFailure` (`boolean`): Print the usage/help message when your user writes a wrong command. This is `true` by default.
117- `exitOnFailure` (`boolean`): Kill the process with an exit code of failure. This is `true` by default.
118- `throwOnFailure` (`boolean`): Throw an exception in the `getopt()` call you can catch. This is `false` by default.
119
120Please, note that `exitOnFailure` and `throwOnFailure` behavior customizations are not compatible. Only one of them is allowed at the same time.
121
122</p>
123</details>
124
125The response of a `getopt()` call is a simple plain object with a value per option specified in the command. Each value can be one of the following:
126
127- `boolean`, for options not needing arguments.
128- `string`, for options expecting a single argument.
129- `string[]`, for options expecting more than one argument.
130
131See the following example for a better understanding of how to use `getopt()` and the expected resoponse:
132
133<details>
134<summary>Example</summary>
135<p>
136
137Here is a basic example of how to use `getopt()`. Please, note you'll find many more examples in the tests folder.
138
139```javascript
140import { getopt } from 'stdio';
141
142const options = getopt({
143 name: { key: 'n', description: 'A name for the project', args: 1, required: true },
144 keywords: { key: 'k', description: 'Some keywords to describe something', args: '*', multiple: true },
145 languages: { args: '*' },
146 finished: { description: 'If the project is finished' },
147});
148
149console.log('Stdio rocks!\n', options);
150```
151
152Here's a valid command for the previous options definition and the result of the `getopt()` response:
153
154```
155$ node example.js -n 'hello world' -k leisure -k health -k sport --languages javascript typescript c++ --finished
156```
157```
158Stdio rocks!
159 {
160 name: 'hello world',
161 keywords: [ 'leisure', 'health', 'sport' ],
162 languages: [ 'javascript', 'typescript', 'c++' ],
163 finished: true
164}
165```
166
167On the other hand, if any option is not well used, the execution of our program will exit with an error result and the usage message will be shown. In this case, we omit the mandatory option `--name, -n`:
168
169```
170$ node example.js -k leisure -k health -k sport --languages javascript typescript c++ --finished
171```
172```
173Missing option: "--name"
174USAGE: node example.js [OPTION1] [OPTION2]... arg1 arg2...
175The following options are supported:
176 -n, --name <ARG1> A name for the project (required)
177 -k, --keywords <ARG1> Some keywords to describe something (multiple)
178 --languages <ARG1>...<ARGN>
179 --finished If the project is finished
180```
181
182Remember the same happens when `--help` or `-h` options are passed. They are reserved to be used to request help.
183
184</p>
185</details>
186
187## read()
188
189This function reads the whole standard input by lines, waiting for a line to be processed successfully before reading the next one. This is perfect for huge files as lines are read only as you process them, so you don't have to worry about system resources.
190
191```javascript
192import { read } from 'stdio';
193
194async function onLine (line, index) {
195 console.log('Processing line %d: %s', index, line);
196 // Do your async stuff
197}
198
199read(onLine)
200 .then(stats => console.log('Finished', stats))
201 .catch(error => console.warn('Error', error));
202```
203
204Note that `onLine` is an `async` function, what means it returns a promise. `read()` call itself also returns a promise. In case one line fails being processed (its promise is rejected) the full `read()` promise will be rejected, too.
205
206Once a `read()` successful call finishes (when all lines have been processed successfully), a small object with some stats is returned:
207
208```typescript
209{
210 length: number; // Number of lines
211 times: Array<number>; // Duration of each line processor
212 timesAverage: number; // Average duration of line processing
213}
214```
215
216<details>
217<summary>Example</summary>
218<p>
219
220The following command reads a huge file and pipes it to a simple program:
221
222```
223$ cat hugefile.txt | node myprogram.js
224```
225
226Where `myprogram.js` prints one line per second, including the line number at the begining:
227
228```javascript
229import { read } from 'stdio';
230
231function sleep (delay) {
232 return new Promise((resolve) => {
233 setTimeout(resolve, delay);
234 });
235}
236
237async function onLine (line, index) {
238 console.log(`#${index}: ${line}`);
239 await sleep(1000);
240}
241
242read(onLine)
243 .then((stats) => console.log('Finished', stats))
244 .catch((error) => console.warn('Error', error));
245```
246
247The output is something like this:
248
249```
250#1: This is the first line of hugefile.txt
251#2: Here the second one
252#3: A third line...
253```
254
255</p>
256</details>
257
258## readLine()
259
260This function reads a single line from standard input. This is perfect for interactive terminal-based programs or just to read standard input on demand.
261
262```javascript
263import { readLine } from 'stdio';
264
265(async () => {
266 ...
267 const line = await readLine(<options>);
268 ...
269})()
270```
271
272Where `<options>` is an optional object with the following properties:
273
274- `stream` (`Readable`): An object implementing `NodeJS.Readable` interface, like a stream. By default, `process.stdin` is used.
275- `close` (`boolean`): An optional flag to close the reader after returning the line. This is useful if you want to stop listening before finishing your program execution.
276
277<details>
278<summary>Example</summary>
279<p>
280
281The following simple program lets the user introduce basic instructions and responds interactively:
282
283```javascript
284import { readLine } from 'stdio';
285
286(async () => {
287 let command;
288 do {
289 command = await readLine();
290 if (command === 'SAY_A') {
291 console.log('A');
292 } else if (command === 'SAY_B') {
293 console.log('B');
294 } else if (command === 'EXIT') {
295 console.log('Good bye');
296 await readLine({ close: true });
297 }
298 } while (command !== 'EXIT')
299})()
300```
301
302Note we're closing the line reader. In this case it could be replaced by a simple `process.exit(0)`, as our program doesn't do anything else.
303
304</p>
305</details>
306
307
308## ask()
309
310This simple function let you ask questions to the user through the command line and wait for an answer:
311
312```javascript
313
314import { ask } from 'stdio';
315...
316const answer = await ask(QUESTION_STRING, QUESTION_CONFIG);
317...
318```
319
320Where `QUESTION_STRING` is just a string and `QUESTION_CONFIG` is an optional object including the following properties:
321
322- `options` (`string[]`): List of allowed values for the answer. If it is not provided, then any answer is accepted.
323- `maxRetries` (`number`): Only makes sense when `options` is provided. After `maxRetries` of wrong answers, the `ask()` returning promise is rejected with an error explaining that all retries have been spent with no successfull answer.
324
325<details>
326<summary>Example</summary>
327<p>
328
329Take a look at the following code
330
331```javascript
332import { ask } from 'stdio';
333
334async function main () {
335 const name = await ask('What is your name?');
336 const age = await ask('How old are you?');
337 const gender = await ask('What is your gender?', { options: ['male', 'female'] });
338 console.log('Your name is "%s". You are a "%s" "%s" years old.', name, gender, age);
339}
340
341main()
342 .then(() => console.log('Finished'))
343 .catch(error => console.warn(error));
344```
345
346Here is an example of the execution:
347
348```
349$ node example.js
350
351What is your name?: John Doe
352How old are you?: 34
353What is your gender? [male/female]: other
354Unexpected answer. 2 retries left.
355What is your gender? [male/female]: male
356Your name is "john doe". You are a "male" "34" years old.
357Finished
358```
359
360</p>
361</details>
362
363
364## ProgressBar
365
366This utility let you create progress bar instances that are printed automatically in the terminal, using a beautiful format and estimating the remaining time of a task. Using it is as simple as follows:
367
368```javascript
369import { ProgressBar } from 'stdio';
370
371const bar = new ProgressBar(BAR_SIZE);
372...
373bar.tick();
374bar.onFinish(() => console.log('FINISHED'));
375```
376Note that progress bars take the 100% of the terminal width where your code runs. No matter if you use a size of 10 or 10000 ticks. `stdio` takes care about the formatting so you don't have to worry about it. Your terminal will show something like the following:
377
378```
37900:01:12 23% [##############························································] ETA 08:10:48
380```
381
382<details>
383<summary>Example</summary>
384<p>
385
386The following code will create a progress bar of 345 pieces. It means the progress bar will be at 100% when we've called `.tick()` 345 times.
387
388```javascript
389import { ProgressBar } from 'stdio';
390
391var pbar = new ProgressBar(345);
392var i = setInterval(() => pbar.tick(), 1000);
393pbar.onFinish(() => {
394 console.log('Finished!');
395 clearInterval(i);
396});
397```
398
399If you run the previous code, the following will be shown:
400
401```
40200:00:12 3% [###····································································] ETA 00:05:35
403```
404
405</p>
406</details>