User Input
There are a number of function to help with processing command line arguments and getting input from users. Some of them are quick and easy functions of convenience to make simple things simple. At the other end of the scale the options()
function provides an integrated way to process command line arguments (using commander) and prompt the user for any missing configuration options (using prompts).
User Input
prompt()
The prompt()
function is a quick-and-easy way to ask a user to enter some input. It is implemented as a wrapper around the prompt module.
The first argument is the question to ask the user.
import { prompt } from '@abw/badger'
const name = await prompt("What is your name?");
console.log('Hello ', name);
An optional second argument can be the default answer:
const name = await prompt("What is your name?", 'Mr Badger');
This is a convenient short-hand for passing an object with a default
property.
const name = await prompt("What is your name?", { default: 'Mr Badger' });
confirm()
The confirm()
function allows you to prompt the user to confirm an action by pressing y
for yes
or n
for no
.
import { confirm } from '@abw/badger'
const yes = await confirm("Are you sure?");
console.log('You said "%s"', yes ? 'YES' : 'NO');
The default answer is N
, returning a false
value, but you can pass true
as the second argument to make Y
the default.
const yes = await confirm("Are you sure?", true);
console.log('You said "%s"', yes ? 'YES' : 'NO');
select()
The select()
function is another wrapper of convenience for getting the user to select an option from a list.
Here the second argument should be an array of options, with each as an object containing a title
and value
, or you can pass an object where each keys is a value
mapped to a title
.
import { select } from '@abw/badger'
const animal = await select(
'What is your favourite animal?',
{
aardvark: 'An amazing aardvark',
badger: 'A brilliant badger',
cat: 'A cool cat',
dog: 'A dapper dog'
},
1
);
console.log('You chose:', animal); // aardvark, badger, cat or dog
These are the three functions that I find myself using a lot. For anything more complicated you should probably cut out the middle-man and go straight to prompts.
Command Line Arguments
cmdLineArg()
If you've got a script that expects a command line argument then the cmdLineArg()
function is your friend. It will return the first argument from the command line after your script name.
import { cmdLineArg } from '@abw/badger'
const arg = await cmdLineArg();
If the script is run like so:
$ node your-script.js foo
Then the arg
variable will be set to foo
. If you don't specify any command line arguments after the script name then it will be undefined
. If the program can't continue without the argument then you might want to use the quit()
function to exit with a helpful message.
const arg = await cmdLineArg();
|| quit('No argument provided')
If you pass a message prompt as an argument then the function will prompt the user to enter a value if there isn't one provided on the command line.
const name = await cmdLineArg('What is your name?')
|| quit('No name provided')
console.log(`Hello ${name}`)
Running the script would look something like this:
$ node your-script.js
✔ What is your name? … Bobby Badger
Hello Bobby Badger
cmdLineArgs()
If you want to process several command line arguments then you can use the cmdLineArgs()
function (note the extra "s" on the end). Here the first argument should be an array of prompts for each argument.
const [forename, surname] = await cmdLineArgs(
['What is your first name?', 'What is your surname']
) || quit('No name provided')
console.log(`Hello ${forename} ${surname}`);
If you don't specify any prompts then it returns all arguments.
Note that both of the above functions take a copy of the command line arguments to work with (process.argv.slice(2)
). They don't modify the original list of argument so if you call either function multiple times then you'll end up with the same arguments being returned, starting from process.argv[2]
each time.
In this case you should take your own copy of the arguments and pass that as the second option to the function(s). You can use the process.argv.slice(2)
trick, or call the cmdLine()
function to do it for you with less typing.
cmdLine()
import { cmdLine, cmdLineArg, cmdLineArgs, quit } from '@abw/badger';
// take a copy of the command line arguments to work with
const args = cmdLine();
const [forename, surname] = await cmdLineArgs(
['What is your first name?', 'What is your surname'],
args
) || quit('No name provided')
const age = await cmdLineArg(
`Hello ${forename} ${surname}, how old are you?`,
args
) || quit('No age provided')
console.log(`${age} is a fine age`);
You can run this script with no arguments, one argument (forename), two arguments (forename, surname) or three (forename, surname, age). It will prompt you to enter any that you omitted.
cmdLineFlags()
If your script supports command line flags (e.g. -v
or --verbose
) then the cmdLineFlags()
function can be used to remove them from an argument list in advance. This should only be used in simple cases where you want to detect the presence of a boolean flag. It doesn't have any built in support for flags that expect an argument, although you can handle this yourself with an on
handler, as shown below.
import { cmdLineFlags } from '@abw/badger';
const { flags, args } = await cmdLineFlags()
console.log('flags:', flags);
console.log('args:', args);
Here's an example of the script being run:
$ node flags-example.js -d --verbose foo -x bar
flags: { d: true, verbose: true, x: true }
args: [ 'foo', 'bar' ]
There are a number of configuration options you can pass to the function. The short
option allows you to map short options (e.g. -v
) to longer equivalents (e.g. --verbose
).
const { flags, args } = cmdLineFlags(
{
short: {
v: 'verbose',
d: 'debug',
}
},
);
In this case, the -v
or --verbose
option will result in verbose
being set true
in the returned flags
.
The options
option allows you to specify which options are valid. You can specify this as an object, where the valid options are the keys and corresponding values should be true
:
const { flags, args } = cmdLineFlags(
{
options: {
verbose: true,
debug: true,
}
},
);
Or you can use the short-hand form where you pass an array:
const { flags, args } = cmdLineFlags(
{
options: ['verbose', 'debug'],
},
);
Or the even shorter-hand form where you pass a whitespace delimited string:
const { flags, args } = cmdLineFlags(
{
options: 'verbose debug',
},
);
If you provide the options
option then any flags that aren't explicitly named will cause an error to be throw.
You can specify the others
option to change this behaviour. Set it to keep
to keep the rogue flag in the argument list, remove
to remove it altogether and pretend it never happened, or collect
to accept it and return it in the flags. The default value is error
.
const { flags, args } = cmdLineFlags(
{
options: 'verbose debug',
others: 'keep',
},
);
Here's an example of the earlier script being run with this configuration:
$ node flags-example.js --verbose foo -x bar
flags: { verbose: true }
args: [ 'foo','-x', 'bar' ]
You can specify --
as an argument to indicate that the function should stop processing any further arguments. In this case any subsequent arguments will be kept in the args
list.
$ node flags-example.js --verbose -- -x foo -y bar -z baz
flags: { verbose: true }
args: [ '-x', 'foo', '-y', 'bar', '-z', 'baz' ]
The on
option can be used to trigger a function when a flag is detected.
const { flags, args } = cmdLineFlags(
{
options: 'verbose debug',
on: {
help: () => quit("Help message..."),
version: () => quit("Version 1.2.3"),
n: (name, arg, args, flags) => {
// Handle something like: -n <number>
flags.number = args.shift();
return true
}
}
},
);
The on
handler will be passed four arguments. The first is the argument name with the leading hyphens removed. If it's a short
option (e.g. -v
) which is mapped to a longer name (e.g. verbose
) then the longer name will be passed. The second argument is the flag as it was specified (e.g. -v
or --verbose
). The third argument is the array of remaining arguments remaining after the current argument. The fourth argument is an object containing the flags collected so far. You can modify the arguments list or the flags if you want to.
The function should return true
if it has handled the argument and no further processing needs to be done. If it doesn't return a true value then processing of the flag continues as normal.
Options that have on
handlers don't need to be explicitly listed in the options
list.
Command Line Options
options()
When you have multiple arguments and/or flags then options()
function may be a better choice. It handles the logic of processing flags, arguments and prompting the user to enter any that are missing.
This simple example shows how options are specified.
const config = await options({
name: 'options.js',
version: '0.0.1',
description: 'Example showing command line options and prompting',
options: [
{
name: 'debug',
short: 'D',
about: 'Enable debugging'
},
{
name: 'database',
short: 'n',
about: 'Database',
type: 'text',
prompt: 'What is the name of the database?',
required: true,
},
{
name: 'username',
short: 'u',
about: 'Username',
type: 'text',
prompt: 'What is the database username?',
},
{
name: 'password',
short: 'p',
about: 'Password',
type: 'password',
prompt: 'What is the database password?',
},
]
});
console.log('config: ', config);
Run the script with the -h
option to view the help.
Usage: options.js [options]
Example showing command line options and prompting
Options:
-V, --version output the version number
-D, --debug Enable debugging
-n, --database <text> Database
-u, --username <text> Username
-p, --password <password> Password
-h, --help display help for command
The name
, version
and description
are optional items that will be displayed in the help.
The options
array defines the valid options. Each can have a name
which is accessible as the "long option", e.g. --debug
, --database
, --username
and --password
, and a short
option, e.g. -D
, -d
, -u
and -p
. The about
item provides information about the option.
After processing the command line arguments, the function will prompt the user to confirm any values specified as arguments, and enter any missing values. The prompt
item is used to prompt the user. If this is omitted then the user will not be prompted to enter a value. The type
can be set to one of the types provided by the prompts package. The required
option can be set true
to force the user to enter a value.
$ node examples/options.js -d wibble
✔ What is the name of the database? … wibble
✔ What is the database username? …
✔ What is the database password? …
Special Options
The yes
configuration item can be specified to have the -y
/ --yes
optional automatically added. When this is specifed on the command line the function will automatically accept the default answer.
The verbose
configuration item can be specified to have the -v
/ --verbose
optional automatically added. When this is specifed on the command line the function will print additional output.
The quiet
configuration item can be specified to have the -q
/ --quiet
optional automatically added. When this is specifed on the command line the function will suppress any optional output.
const config = await options({
name: 'options.js',
description: 'Example showing command line options and prompting',
version: '0.0.1',
yes: true,
verbose: true,
quiet: true,
options: [
...
]
})
$ node examples/options.js -h
Usage: options.js [options]
Example showing command line options and prompting
Options:
-V, --version output the version number
-y, --yes Accept default answers
-v, --verbose Verbose output
-q, --quiet Quiet output
-d, --database <text> Database
-u, --username <text> Username
-p, --password <password> Password
-h, --help display help for command
commands
You can also add commands
to the configuration. For example, you might have a script where you want to start
or stop
one or more services.
const config = await options({
name: 'commands.js',
description: 'Example showing command line commands',
version: '0.0.1',
verbose: true,
quiet: true,
commands: [
{
name: 'start',
pattern: '<service>',
about: 'Start a service'
},
{
name: 'stop',
pattern: '<service>',
about: 'Stop a service'
},
{
name: 'status',
about: 'Show service status'
}
]
})
When run with the -h
option the output will be:
$ node examples/commands.js -h
Usage: commands.js [options] [command]
Example showing command line commands.
Options:
-V, --version output the version number
-v, --verbose Verbose output
-q, --quiet Quiet output
-h, --help display help for command
Commands:
start <service> Start a service
stop <service> Stop a service
status Show service status
help [command] display help for command
If a command should accept multiple arguments then define the pattern using ellipses.
const config = await options({
// ...etc...
commands: [
{
name: 'start',
pattern: '<service...>',
about: 'Start one or more services'
},
// ...etc...
]
})
Sections
If you have lots of questions then you might want to break them up into sections. Add an item to the options
array like the following to print a section title and information. Both title
and info
are optional, so you can omit either one.
options: [
{
title: "Configuration Options",
info: "Please answer the following questions.\nPress RETURN to accept defaults."
},
...
]
This generates the following output:
Configuration Options
---------------------
Please answer the following questions.
Press RETURN to accept defaults.
Generated Configuration
The function returns a Promise which resolves to an object containing the configuration values. Each key will be the name
of the option or command, and the corresponding value will be that read from the command line or by prompting the user. In the case of commands
with multiple values they will be stored as an array.