Command-line parser based on https://github.com/vercel/arg/tree/5.0.0
TOC
- ArgParser
- ArgValidator
- Describe usage
- Faq
Example
import arg from 'ext/arg.js';
const MOODS = ['happy', 'hangry', 'bored'];
const args = arg
.parser({
'--email': arg.str().fmt('email').req().desc('user email'),
'--mood': arg.str('happy').enum(MOODS).desc('greeting mood'),
'--random': arg.flag().desc('choose a random mood'),
'--intensity': [arg.flag().no(false).count().desc('increase intensity')],
'-e': '--email',
'-m': '--mood',
'-i': '--intensity',
})
.desc('Greeting program example')
.ex([
'-e [email protected] -m happy',
'--email [email protected] -iii',
'--email [email protected] --random'
])
.ver('0.1.0')
.parse();
let mood = args.get('--mood');
if (args.get('--random')) {
const index = Math.floor(Math.random() * MOODS.length);
mood = MOODS[index];
}
let message = `Hello ${args.get('--email')}. I am `;
if (args.get('--intensity') > 0) {
for (let i = 0; i < args.get('--intensity'); ++i) {
if (i > 0) {
message += '-';
}
message += 'very';
}
message += ' ';
}
message += `${mood} today.`
console.log(message);
Running program with --help
will produce following output
Greeting program example
Usage: greet [ARG] ...
-e, --email EMAIL (*) : user email
-m, --mood VAL : greeting mood (default: happy)
- it can be one of [happy, hangry, bored]
--(no-)random : choose a random mood
-i, --intensity (+) : increase intensity
- it can be set multiple times
-h, --help : print help
--version : print version
EXAMPLES
$ greet -e [email protected] -m happy
$ greet --email [email protected] -iii
$ greet --email [email protected] --random
arg.parser(validators)
An ArgParser
is instanciated using the arg.parser
function. It takes a Record<string, ArgValidator|ArgValidator[]>
where
- each key is the command line argument (ex:
--email
) - each value is an
ArgValidator
object (orArgValidator[]
if setting the argument multiple times if possible)
Example
const parser = arg.parser({
'--name': arg.str().req(),
});
.desc(description)
Defines the program description (displayed when --help
flag is used)
- description (string) : program description
returns self
Example
const parser = arg.parser({
'--name': arg.str().req(),>
}).desc('This is a program');
.ver(version)
Defines the version (displayed when --version
flag is used)
- version (string) : program version
returns self
Example
const parser = arg.parser({
'--name': arg.str().req(),
}).ver('0.1.0');
.ex(examples)
Sets examples (displayed when --help
flag is used)
- examples (string[]) : examples to display in help
returns self
Example
const parser = arg.parser({
'--name': arg.str().req(),
}).ex([
'--name john'
]);
.parse(opt)
Parses and validates command-line arguments
- opt (ArgParserOptions) : options
returns Record<string, any> (parsed arguments)
- if validation fail (missing required argument or invalid argument), program will display usage and exit with an error code (
2
) - if program is called with
--help
flag, it will display help and exit without error code (0
) - if program is called with
--version
flag (and a version has been defined), it will display the version and exit without error code
Types definition
/**
* @typedef {Object} ArgUsageLayoutOptions
* @property {number} [maxLength=110] - maximum length of a line
* @property {number} [argNameIndent=4] - number of spaces before argument names
* @property {number} [usageIndent=4] - number of spaces between argument names and usage
* @property {boolean} [ignoreTtySize=false] - if true, don't adapt maximum length to tty size
*/
/**
* @typedef {Object} ArgParserOptions
* @property {ArgUsageLayoutOptions} [usageLayout]
* @property {string[]} [helpFlags=["--help","-h"]] - list of flags to display help
* @property {string[]} [versionFlags=["--version"]] - list of flags to display version
* @property {boolean} [capitalizeUsage=false] - if set to true, capitalize usage for --help and --version messages
* @property {string} [scriptName] - script name to use when displaying usage
*/
Example
const args = arg.parser({
'--name': arg.str().req(),
}).parse({ usageLayout: { maxLength: 80 } });
ArgValidator
is an abstract class inspired by Joi with following inherited classes
StringArgValidator
which supports- enum validation
- string length validation
- regexp validation
- format validation (ex: email)
- trimming
PathArgValidator
which supports- file/directory validation
- automatic file/directory creation
- json content validation
NumberArgValidator
which supports- positive/negative validation
- range validation
- integer validation
FlagArgValidator
which supports- counting the number occurrences of a flag
- both
--xx
and--no-xx
flags
All ArgValidator
inherited classes also support
- setting a default value
- marking an argument as required
- retrieving the value using an environment variable
- defining custom validator
- defining a function to map result (once value has been successfully validated)
In order to indicate that an argument can be set multiple times, the validator should be wrapped in an array
Examples
In below example, argument --num
can be set only once (only the last value will be kept)
const args = arg
.parser({
'--num': arg.num().pos().int(),
})
.parse();
/*
$ program --num 5 --num 6
=> 6
*/
console.log(JSON.stringify(args.get('--num')));
In below example, argument --num
can be set multiple times
const args = arg
.parser({
'--num': [arg.num().pos().int()],
})
.parse();
/*
$ program --num 5 --num 6
=> [5,6]
*/
console.log(JSON.stringify(args.get('--num')));
.trim(trimContent = true)
Indicates whether or not argument value should be trimmed
- trimContent (boolean) : (default =
true
)
returns self
Example
const args = arg.parser({
'--name': arg.str().trim(),
}).parse();
.reg(regexp)
Indicates argument value should match a regular expression
- regexp (RegExp)
returns self
Example
Below example accepts john
, john1
and john2
const args = arg
.parser({
'--name': arg.str().reg(/^john[12]?$/),
})
.parse();
.min(minLength)
Defines the minimum length of the argument value
- minLength (number)
returns self
Example
const args = arg
.parser({
'--pass': arg.str().req().min(20),
})
.parse();
.max(maxLength)
Defines the maximum length of the argument value
- maxLength (number)
returns self
Example
const args = arg
.parser({
'--pass': arg.str().req().max(25),
})
.parse();
.fmt(formatName)
Ensures argument value matches a given format (ex: email
)
- formatName (string)
returns self
NB : new formats can be added using arg.registerFormat(...)
Example
const args = arg
.parser({
'--email': arg.str().fmt('email'),
})
.parse();
.enum(possibleValues, message)
Defines the list of possible values
- possibleValues (string[])
- message (string) : message to display before enum values in usage (default =
it can be one of
)
NB : possible values will be listed in usage
returns self
Example
const args = arg
.parser({
'--cmd': arg.str().enum(['date', 'uptime']),
})
.parse();
arg.registerFormat(...)
can be used to register new string formats, which can be used by StringArgValidator.fmt(...)
function
Examples
Below example registers a new format using a RegExp
arg.registerFormat('test', /^test1|test2$/);
const args = arg
.parser({
'--val': arg.str().fmt('test'),
})
.parse();
Below example registers a new format using a FormatValidatorFunc
/**
* @callback FormatValidatorFunc
* @param {string} argValue
*
* @returns {boolean}
*/
arg.registerFormat('test', (value) => {
return ['test1', 'test2'].includes(value);
});
const args = arg
.parser({
'--val': arg.str().fmt('test'),
})
.parse();
.check(shouldExist = true)
Indicates whether or not path should exists
- shouldExist (boolean) (default =
true
)
returns self
Examples
Below example returns an error if path does not exist
const args = arg
.parser({
'-p': arg.path().check(),
})
.parse();
Below example returns an error if path exists
const args = arg
.parser({
'-p': arg.path().check(false),
})
.parse();
.checkParent()
Indicates parent directory should exists
returns self
Example
Below example returns an error if parent directory does not exist
const args = arg
.parser({
'-p': arg.path().checkParent(),
})
.parse();
.dir(isDir = true)
Indicates whether or not path represents a directory
- isDir (boolean) (default =
true
)
returns self
Example
Below example returns an error if path does not exist or is not a directory
const args = arg
.parser({
'-d': arg.path().check().dir(),
})
.parse();
.ensure()
Ensures path exists and create it (and all its ancestors) if needed
returns self
Example
const args = arg
.parser({
'-p': arg.path().ensure(),
})
.parse();
.ensureParent()
Ensures parent directory exists and create it (and all its ancestors) if needed
returns self
Example
const args = arg
.parser({
'-p': arg.path().ensureParent(),
})
.parse();
.read(opt)
Reads file content at the end of validation
- opt (object) : options
- opt.json (boolean) : if
true
, parse the content as json (default =false
) - opt.trim (boolean) : if
true
, trim the content (default =false
)
- opt.json (boolean) : if
returns self
NB : if a mapping function has been defined (ie: using .map(...)
), file content will be passed to it (string or object)
Example
const args = arg
.parser({
'-f': arg.path().read({ json: true }),
})
.parse();
console.log(typeof args.get('-f'));
.std(allowStd = true)
Allows reading from stdin and writing to stdout using -
- allowStd (boolean) : (default =
true
)
returns self
Example
Below example parses the content of stdin as json and puts the result in args['-f']
const args = arg
.parser({
'-f': arg.path().std().read({ json: true }),
})
.parse();
.pos(isPositive = true)
Indicates whether or not number should be positive
- isPositive (boolean) : (default =
true
)
returns self
Example
Below example returns an error if argument value is <= 0
const args = arg
.parser({
'--num': arg.num().pos()
})
.parse();
.pos(isNegative = true)
Indicates whether or not number should be negative
- isNegative (boolean) : (default =
true
)
returns self
Example
Below example returns an error if argument value is >= 0
NB : program should be called using --num='-1.5'
, not --num -1.5
const args = arg
.parser({
'--num': arg.num().neg()
})
.parse();
.min(minValue)
Defines the minimum argument value
- minValue (number)
returns self
Example
const args = arg
.parser({
'--num': arg.num().min(5),
})
.parse();
.max(minValue)
Defines the maximum argument value
- maxValue (number)
returns self
Example
const args = arg
.parser({
'--num': arg.num().max(5),
})
.parse();
.int()
Indicates that argument value should be an integer
returns self
Example
const args = arg
.parser({
'--num': arg.num().int(),
})
.parse();
.no(allow = true)
Indicates whether or not --no-x
flag version should be allowed (allowed by default)
- allow (boolean) : (default =
true
)
returns self
Examples
Below example accepts both --verbose
and --no-verbose
flags
const args = arg
.parser({
'--verbose': arg.flag()
})
.parse();
console.log(`Flag is ${args.get('--verbose') ? 'enabled' : 'disabled'}`);
Below example only accepts --verbose
flag
const args = arg
.parser({
'--verbose': arg.flag().no(false)
})
.parse();
console.log(`Flag is ${args.get('--verbose') ? 'enabled' : 'disabled'}`);
.count()
Returns the number of times flag was set at the end of validation
returns self
Example
const args = arg
.parser({
'-v': [arg.flag().count()]
})
.parse();
console.log(`Flag was set ${args.get('-v')} times`);
.def(defaultValue)
Updates default value (can also be defined directly in constructor)
- defaultValue (type depends on the validator class)
returns self
Example
const args = arg.parser({
'--name': arg.str('john').def('jane'),
}).parse();
.env(varName)
Defines the environment variable to retrieve argument value from
- varName (string)
returns self
Example
Below example accepts both program --name john
and NAME=john program
const args = arg
.parser({
'--name': arg
.str()
.env('NAME')
})
.parse();
.desc(description)
Defines the description to use when displaying usage
- description (string)
returns self
Example
const args = arg
.parser({
'--name': arg
.str()
.desc('user name')
})
.parse();
.err(message)
Used to override the error message upon validation failure
- message (string)
returns self
Example
const args = arg
.parser({
'--pass': arg
.str()
.cust((value) => {
if(value !== 'password') {
throw new Error();
}
})
.err('wrong password')
.desc('user password')
})
.parse();
.val(text)
Defines the text which will be displayed after argument names in usage. By default, VAL
will be used
- text (string)
returns self
Example
const args = arg
.parser({
'--name': arg
.str()
.val('NAME')
.desc('user name')
})
.parse();
.req(isRequired = true)
Indicates whether or not an argument is required
- isRequired (boolean) : (default =
true
)
returns self
Example
const args = arg
.parser({
'--name': arg
.str()
.req()
})
.parse();
.cust(validatorFn)
Defines a custom validator
- validatorFn (ValueValidatorFunc)
returns self
/**
* @template [T=string]
* @callback ValueValidatorFunc
* @param {T} argValue
*
* @returns {false|void}
*/
A custom validator is a function which takes the argument value as first argument and returns one of the following
- returns
false
: argument value is valid, subsequent validators will be ignored - throws an exception : argument value is invalid (exception will be used as error message)
- otherwise : argument value is valid, subsequent validators will be processed
NB : multiple custom validator can be defined
Example
Below example accepts john
, jane
or an empty string
const args = arg
.parser({
'--name': arg
.str()
.cust((value) => (!value ? false : undefined))
.enum(['john', 'jane']),
})
.parse();
Below example accepts a number < 5
or > 6
const args = arg
.parser({
'--num': arg
.num()
.cust((value) => {
if (value < 5 || value > 6) {
return;
}
throw new Error('value should be < 5 or > 6');
}),
})
.parse();
.map(mappingFn)
Defines a mapping function
- mapperFn (ValueMapper)
returns self
/**
* @template T
* @callback ValueMapper
* @param {T} argValue
*
* @returns {any}
*/
A mapping function is a function which takes the argument value as first argument and returns anything
Example
const args = arg
.parser({
'--name': arg
.str()
.map((value) => ({name: value}))
})
.parse();
.clone()
Clones an existing validator (deep copy)
returns ArgValidator
Example
const validator1 = arg.str('john').min(4);
const validator2 = validator1.clone().def('jane').min(3);
const args = arg
.parser({
'--name1': validator1,
'--name2': validator2
})
.parse();
When DESCRIBE_USAGE
environment variable is set, program will output a json object instead of executing
/**
* @typedef {Object} DescribeUsageItem
* @property {string} name - argument name, starting with - or --
* @property {(string|number|boolean)} [default] - only defined if argument has a default value
* @property {string} valueText - text to display after argument names in usage (can be empty if argument does not accept a value)
* @property {DescribeUsageItemType} type - argument type
* @property {string[]} values - list of possible values (can be empty)
* @property {string} format - name of the value format (ex: uuid or email) (empty if argument has no specific format)
* @property {boolean} required - whether not argument is required (always false for flags)
* @property {boolean} allowMany - whether or not argument can be set multiple times
* @property {string} varName - name of the environment variable linked to this argument (can be empty)
* @property {string[]} aliases - list of aliases for this argument (can be empty)
* @property {string} description - argument description (can be empty)
* @property {string} shortDescription - first line of the description (can be empty)
* @property {boolean} allowNoFlags - whether or not --no-- flags are supported for this flag (always false for non flags arguments)
*/
Example
const args = arg
.parser({
'--age': arg.num().pos().int().req().desc('user age'),
'--name': arg
.str('john')
.enum(['john', 'jane'])
.desc('user name')
.val('NAME'),
'--verbose': [arg.flag().no(false).desc('increase verbosity')],
'-n': '--name',
'-a': '--age',
'-v': '--verbose',
})
.parse();
Running above program using DESCRIBE_USAGE=1 program
will output the following
[
{
"name": "--age",
"type": "number",
"required": true,
"valueText": "NUM",
"allowMany": false,
"description": "user age",
"shortDescription": "user age",
"values": [],
"format": "",
"varName": "",
"aliases": [
"-a"
],
"allowNoFlags": false
},
{
"name": "--name",
"type": "string",
"required": false,
"valueText": "NAME",
"allowMany": false,
"description": "user name",
"shortDescription": "user name",
"values": [
"john",
"jane"
],
"format": "",
"varName": "",
"aliases": [
"-n"
],
"allowNoFlags": false,
"default": "john"
},
{
"name": "--verbose",
"type": "flag",
"required": false,
"valueText": "",
"allowMany": true,
"description": "increase verbosity",
"shortDescription": "increase verbosity",
"values": [],
"format": "",
"varName": "",
"aliases": [
"-v"
],
"allowNoFlags": false,
"default": false
},
{
"name": "--help",
"type": "flag",
"required": false,
"valueText": "",
"allowMany": false,
"description": "print help",
"shortDescription": "print help",
"values": [],
"format": "",
"varName": "",
"aliases": [
"-h"
],
"allowNoFlags": false,
"default": false
}
]
No 😊. It's outside of the scope of the module, and is unlikely to happen anytime soon
Not yet, but using DESCRIBE_USAGE
variable generates a json object which could be use to generate completion