Reargs
RegExp based command line arguments parser.
So why another argument parser library ?
None of the ones I found leverage the power of RegExp ! :wink:
With great power comes great responsability !
$ prog -m swagger dump api from https://api.endpoint.io -- do not parse
Or having multiple ways of asking for the same action:
$ prog -s
$ prog sh
$ prog shell
Last but not least, I wanted to have a descriptive help text out of the box. Thanks to nunjucks you just have to call generateHelp to have the magic :rainbow: !
How does it work ?
Behind the scenes are Regular Expressions - hence the name - and the fact that they are both very flexible and more than enough for what we want.
Instantiation
const Reargs = require('reargs')
const myArgs = new Reargs(params = {}, opts = null, debug = false)
import Reargs from 'reargs'
const myArgs = new Reargs(params = {}, opts = null, debug = false)
Parameters
This object contains all the parameters, which have the following properties:
attribute | optional ? | accepted types | default value | meaning | example |
---|
group | yes | string | _ | groups commands, see note below | 'command' |
short | yes if long option is set | string or RegExp | '' | short version of parameter | '-a' |
long | yes if short option is set | string or RegExp | '' | longer version of parameter | '--append' |
humanReadable | yes | string | autogenerated from short and long properties | human readable version of the parameters for help | '-a, --append' |
help | yes | string | 'no help' | help text | 'append to file' |
stopParse | yes | boolean | false | stop parsing when found (see note below) | |
hidden | yes | boolean | false | do not show parameter if default help template (see note below) | |
multiple | yes | boolean | false | accept multiple occurrences of a parameter (see below) | |
captureMultiple | yes | string or RegExp | undefined | allow capture of multiple values inside a parameter (see below) | |
values | yes | any including Object | false | default value for the parameter | see notes below |
A typical example for help
would be:
const params = {
help: {
group: 'command',
short: '-h',
long: '--help',
humanReadable: '-h, --help',
help: 'this help',
stopParse: true,
hidden: false,
multiple: false,
values: false
}
}
Indeed, no default help entry is provided. If you want to provide such functionality (which you might want), then you have to set an entry like the one above.
Notes
- All commands can be grouped together using the
group
property. It can be useful to distinguish between command
s and option
s for example. - All arguments can have two forms,
short
and long
. Although they do not have to be shorter or longer one to the other, you can provide alternate versions if you need. At least one of them is required, an exception is thrown otherwise. - These properties can be
string
or instances of RegExp
. If you prefer using strings, please make sure you use double backslashs to escape.
E.g. the following are equivalent:
const exampleWithStrings = {
help: {
help: 'this help or additional help on a given topic',
short: '((?<topic>[\\w|\\/]+) )?-h',
long: 'help( (?<topic>[\\w|\\/]+))?',
}
}
const exampleWithRegExp = {
help: {
help: 'this help or additional help on a given topic',
short: /((?<topic>[\w|/]+) )?-h/,
long: /help( (?<topic>[\w|/]+))?/,
}
}
Notes
A humanReadable
text is computed from both short
and long
inputs (with the help of an option longShortDelimiter). If this does not suit the way you want it to appear when calling generateHelp, feel free to customize !
The stopParse
parameter implies that everything after this parameter is discarded from the input.
See it as the usual --
in your shell.
The hidden
flag will still parse the parameter but it will not be shown in the help generated with generateHelp. Of course you will have to implement the same logic if you want to customize your help output.
See Help templates below.
Options
A few options are also available to adjust Reargs
behavior. Below are the default values:
const opts = {
longShortDelimiter: '\n',
paramDescriptionSpacer: '.',
prePaddingSpaces: 2,
alignLongIfNoShort: 4,
exitOnStop: false,
nunjucksAutoEscape: false,
}
-
longShortDelimiter
will be used to generate the humanReadable
documentation from join
ing both short
and long
parameters variations.
-
paramDescriptionSpacer
is also used when calling generateHelp, and changes the spacing character used between parameters and the related help.
-
prePaddingSpaces
adds that many spaces (
) at the beginning of each line describing a parameter.
-
alignLongIfNoShort
adds that many spaces before arguments that have no short
property, to have a cleaner alignment with arguments having both short
and long
properties set.
-
exitOnStop
if set, stops immediately parsing when a parameter with stopParse
property set to true is found, not evaluating anything else.
-
nunjucksAutoEscape
is set to false by default in Reargs
but by default set to true upstream. Since we are dealing with CLI, there should not be any mishaps by disabling safety guards.
Note
Although discouraged, paramDescriptionSpacer
and prePaddingSpaces
can be overriden ultimately in the template, because they are arguments of padEnd Jinja2
-like modifier.
Debug
To help with debugging your configuration, a debug mechanism has been set up.
At several places in the code, some debug messages will be generated. Depending on the value of debug
they will be outputted ... or not.
debug value | meaning |
---|
false (default) | no debug output at all |
true | debug will be output with console.log |
(...) => { do_whatever_you_want() } | a custom function taking as many arguments like console.log to suit your needs. |
Parsing
Parsing is done as simply calling the following code :
const unparsable = myArgs.parse(process.argv.slice(2))
Yep that's it ! It takes an Array
as input argument, and returns the unparsed tokens.
Note
The difference between unparsed
and remain
lies in that remain
has never been given a chance to be parsed.
On the contrary, what is returned by the parse
function has been given a chance to be parsed, unsuccesfully.
Both remain
and unparsable
will be returned as string with \x00
special character. This is by design to allow arguments to have spaces in them. When you want to deal with positional arguments, you just have to split('\x00')
to get a proper Array
for whatever your needs would be.
Retrieving values
There are three different functions to retrieve the result after a call to parse.
- getValue : get the value of a single argument (and if present the capture group)
- getGroupValues : get all the values from a specific group (see groups)
- getAllValues : as the name suggests, retrieve all the values from all the parameters
Note
By design, values are flattened to a simple Object.
This means that if you happen to have multiple values having the same name, unexpected behavior can arise.
This is done to avoid confusing and usually this should not happen (or must not happen!).
const value = myArgs.getValue(askforId, captureGroupName = null)
const groupValues = myArgs.getGroupValues(group)
const allValues = myArgs.getAllValues()
Let's go with an example. Consider this little snippet :
const myArgs = new Reargs({
help: {
help: 'this help or additional help on a given topic',
short: '((?<topic>[\\w|\\/]+) )?-h',
long: 'help( (?<topic>[\\w|\\/]+))?',
humanReadable: 'help [topic], [topic] -h'
}
})
const unparsable = myArgs.parse(process.argv.slice(2))
const valueOfParam = myArgs.getValue('help', 'topic')
The capturing group topic
will store the value, if present.
If the program is called with only help
as argument, the following will be true:
const topic = myArgs.getValue('help', 'topic')
const help = myArgs.getValue('help')
const groupValues = myArgs.getGroupValues('_')
const allValues = myArgs.getAllValues()
Notes
- If an argument has a capturing group, its value is propagated
- If no value could be found on the command line the default one, if it exists, will be propagated instead
- You can have the same name between capturing group name and parameter name
Default values
Now let's consider the following example with default values :
const myArgs = new Reargs({
help: {
help: 'this help or additional help on a given topic',
short: '((?<topic>[\\w|\\/]+) )?-h',
long: 'help( (?<topic>[\\w|\\/]+))?',
humanReadable: 'help [topic], [topic] -h',
values: {
topic: 'general'
}
}
})
const unparsable = myArgs.parse(process.argv.slice(2))
const valueOfParam = myArgs.getValue('help', 'topic')
If the program is called with only help
as argument, the following will be true:
const topic = myArgs.getValue('help', 'topic')
const help = myArgs.getValue('help')
const groupValues = myArgs.getGroupValues('_')
const allValues = myArgs.getAllValues()
Multiple values
Sometimes you want to capture multiple values, let's see two examples of parsing key:value
pairs.
A single capture group, multiple arguments
Consider the following command line arguments :
$ prog -e key1:value1 -e key2:value2 -e key3: -e :value4
The definition would be :
const oneKvPerParameter = {
kv1: {
help: 'set key value pairs one at a time',
short: '-e (?<key>[\\w]+)?:(?<value>[\\w]+)?',
humanReadable: '-e key:value',
multiple: true,
values: {
key: 'defaultKey',
value: 'defaultValue'
}
}
})
When multiple
property is set to false
(default), only the latest occurrence of that parameter is taken into account, previous occurrences are simply discarded.
const kv1keys = myArgs.getValue('kv1', 'key')
const kv1 = myArgs.getValue('kv1')
If you set multiple: true
then the following will happen :
const kv1keys = myArgs.getValue('kv1', 'key')
const kv1 = myArgs.getValue('kv1')
Note
If you need mapping between keys and values, this work must be done in your program.
Mutiple capture groups in a single argument
The situation is a bit different :
$ prog -a key1:value1,key2:value2,key3:,:value4
The definition would be :
const manyKvPerParameter = {
kv2: {
help: 'set key value pairs all together',
short: '-a ',
humanReadable: '-a key:value,key:value,...',
captureMultiple: '(?<key2>[\\w]+)?:(?<value2>[\\w]+)?,?',
values: {
key2: 'defaultKey2',
value2: 'defaultValue2'
}
}
}
Note
As you can see the short
property has a
space at the end. This is to force a space between -a
and the rest of parameter attributes.
And therefore:
const kv2keys = myArgs.getValue('kv2', 'key')
const kv2 = myArgs.getValue('kv2')
Combining both !
And yes the previous situations, can be generalized !
$ prog -b key0:value0,key1:value2 -b key1:value1,key2:value2,key3:,:value4
The definition would be :
kv3: {
help: 'set key value pairs all together ... multiple times !',
short: '-b ',
humanReadable: '-b key:value,key:value,...',
captureMultiple: '(?<key3>[\\w]+)?:(?<value3>[\\w]+)?,?',
multiple: true,
values: {
key3: 'defaultKey3',
value3: 'defaultValue3'
}
}
And therefore:
const kv3keys = myArgs.getValue('kv3', 'key')
const kv3 = myArgs.getValue('kv3')
Note
Here again, Reargs
do not make any assumption on whether keys or values should be merged and how (e.g double definition of the key key1
). It is up to the responsability of the caller to manage that.
Generate Help
As you have seen, many options and properties are meant to ease the generation of help message.
Everything is handled by the generateHelp with the following parameters:
parameter name | default value | description | example |
---|
contextHelp | {} | some properties can be overriden by default and you can add your own custom variables as well but you will have to use your own template | { name: "foobar", version: "1.0rc1" } |
templateSource | defaultHelpTemplate | a nunjucks template | you can view the default below |
const helpMessage = myArgs.generateHelp()
const context = JSON.parse('package.json')
const customContext = myArgs.generateHelp(context)
const helpTemplate = `...`
const customHelp = myArgs.generateHelp(context, helpTemplate)
This is the default template :
const defaultHelpTemplate = `
{{name}} v{{version}} - {{description}} - by {{author|safe}}
Usage:
{{name}}{% for group,params in groups %} [{{ group }}]{% endfor %}
{% for group,params in groups %}
{{group|title}}s:
{% for param in params %}
{% if param.hidden != true -%}
{{ param.humanReadable|padEnd(params.padding, params.prepadding, opts.paramDescriptionSpacer)|safe }} {{param.help}}
{%- endif %}
{%- endfor %}
{% endfor -%}
`
It is pretty much self explanatory, please have a look a nunjucks for details.
Contributing
Licence is MIT, feel free to raise issues, provide PR or just drop a word about how you use it, what would be the next features you would like this piece of software to have.