[go: up one dir, main page]

reargs

1.2.0 • Public • Published

Reargs

RegExp based command line arguments parser.

NPM NPM Downloads Node.js CI GitHub Project API Documentation Dependency Status devDependency Status

So why another argument parser library ?

None of the ones I found leverage the power of RegExp ! 😉

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 🌈 !

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

// CommonJS
const Reargs = require('reargs')
const myArgs = new Reargs(params = {}, opts = null, debug = false)
// ES2016
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,
    // captureMultiple: '',
    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 commands and options 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 joining 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 :

// Definition
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') // topic: undefined
const help = myArgs.getValue('help') // returns { topic: undefined }
const groupValues = myArgs.getGroupValues('_') // returns { topic: undefined }
const allValues = myArgs.getAllValues() // returns { topic: undefined }

Notes

  1. If an argument has a capturing group, its value is propagated
  2. If no value could be found on the command line the default one, if it exists, will be propagated instead
  3. 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 :

// Definition
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') // topic: 'general'
const help = myArgs.getValue('help') // returns { topic: 'general' }
const groupValues = myArgs.getGroupValues('_') // returns { topic: 'general' }
const allValues = myArgs.getAllValues() // returns { topic: 'general' }

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 :

// Definition
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')
// ['defaultKey']

const kv1 = myArgs.getValue('kv1')
// {
//   key: ['defaultKey'],
//   value: ['value4']
// }

If you set multiple: true then the following will happen :

const kv1keys = myArgs.getValue('kv1', 'key')
// ['key1', 'key2', 'key3', 'defaultKey']

const kv1 = myArgs.getValue('kv1')
// {
//   key: ['key1', 'key2', 'key3', 'defaultKey'],
//   value: ['value1', 'value2', 'defaultValue', 'value4']
// }

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 :

// Definition
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')
// ['key1', 'key2', 'key3', 'defaultKey']

const kv2 = myArgs.getValue('kv2')
// {
//   key: ['key1', 'key2', 'key3', 'defaultKey'],
//   value: ['value1', 'value2', 'defaultValue', 'value4']
// }

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 :

// Definition
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')
// ['key0', 'key1', 'key2', 'key3', 'defaultKey']

const kv3 = myArgs.getValue('kv3')
// {
//   key: ['key0', 'key1', 'key2', 'key3', 'defaultKey'],
//   value: ['value0', 'value2', 'value1', 'value2', 'defaultValue', 'value4']
// }

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
// This will generate a help message with all parameters but default context values using the default template.
const helpMessage = myArgs.generateHelp()

// You can use your package.json to grab some useful values
const context = JSON.parse('package.json')
const customContext = myArgs.generateHelp(context)

// Or you can go further by providing your own template
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.

Package Sidebar

Install

npm i reargs

Weekly Downloads

1

Version

1.2.0

License

MIT

Unpacked Size

103 kB

Total Files

19

Last publish

Collaborators

  • oorabona