cli
Simple interface for the command line.
Provides a simple command-line interface using the Node.js readline
module.
const { createCLI } = require('@arnesfield/cli');
const cli = createCLI();
cli.on('data', data => console.log('Hello %s!', data));
cli.start();
Installation
npm install @arnesfield/cli
Usage
Create a CLI using the createCLI
function and start it with start()
. Add and remove event listeners via the on
and off
methods.
// create the interface
const cli = createCLI();
// listen for data and do stuff
cli.on('data', data => {
console.log('Hello %s!', data);
});
// start the CLI
cli.start();
Output:
> World
Hello World!
Input Flow
This flow is used for every input:
- A
line
event is emitted by thereadline
interface. - Call
rl.pause()
. - Parse
input
if a parser is provided and pass it to thedata
event listeners. - Call the
error
event listeners for eachdata
event listener error. - Finally, call
rl.resume()
andrl.prompt(true)
. - Since
rl.resume()
is called, the next line in the queue is emitted and the flow repeats.
CLIOptions
You can pass options
to createCLI()
. You can pass your own readline
Interface
and a custom parser
function.
interface CLIOptions<T> {
/** The `readline` interface to use. */
rl?: Interface;
/** Custom parser for parsing raw input. */
parser?(input: string): T | Promise<T>;
}
The parser
option accepts a function to transform the input.
In the example below, we create a CLI with a parser that trims and splits the input by spaces.
const cli = createCLI({
parser: input => input.trim().split(' ')
});
cli.on('data', data => {
console.log('Array:', data);
});
cli.start();
Output:
> Hello World!
Array: [ 'Hello', 'World!' ]
CLI
object
The interface CLI<T = string> {
/** The `readline` interface. */
readonly rl: Interface;
/** Starts the CLI. Accepts data or input to parse. */
start(): this;
start(data: T, parse?: false): this;
start(input: string, parse: true): this;
/** Accepts data and emits the data to the listeners. */
data(data: T): this;
/** Accepts raw input to parse and emits the data to the listeners. */
input(input: string): this;
/** Calls `rl.prompt(true)` if not closed. */
prompt(): this;
/**
* Ignores incoming `line` event data, and removes ignored
* lines from `rl`'s `history` until `data` event is resolved.
*/
ignore(ignore?: boolean): this;
/** Adds listener. */
on(event: 'data', listener: DataListener<T>): this;
on(event: 'error', listener: ErrorListener): this;
/** Removes listener. */
off(event: 'data', listener: DataListener<T>): this;
off(event: 'error', listener: ErrorListener): this;
}
data
events
Emit You can emit data
events through these methods:
cli.input(input); // accepts input to parse
cli.data(data); // accepts parsed input
cli.start(input, true); // accepts input to parse before starting
cli.start(data); // accepts parsed input before starting
cli.ignore()
Using The cli.ignore()
method does the following:
- Calls
rl.resume()
. - Ignores incoming
line
event data including queued lines fromrl.pause()
. - Removes ignored lines from
rl
'shistory
. - Will automatically unignore after
data
event listeners are emitted and resolved.
A use case for cli.ignore()
is when the data
listener takes a while to finish (async) and you don't want to accept any input until the process has finished.
const cli = createCLI();
cli.on('data', async data => {
// ignore incoming lines
cli.ignore();
console.log('Processing "%s"', data);
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('Done "%s"', data);
});
cli.start();
After entering Hello World!
, input foo
, bar
, baz
while the listener is not yet resolved. Output:
> Hello World!
Processing "Hello World!"
foo
bar
baz
Done "Hello World!"