Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,163 @@ Otherwise, returns `false`.
See [`assert.deepStrictEqual()`][] for more information about deep strict
equality.

## `util.parseArgs([argv][, options])`
<!-- YAML
added: REPLACEME
-->

* `argv` {string[]} (Optional) Array of argument strings; defaults
to [`process.argv.slice(2)`](process_argv). If an Object, the default is used,
and this parameter is considered to be the `options` parameter.
* `options` {Object} (Optional) The `options` parameter is an
object supporting the following properties:
* `optionsWithValue` {string[]|string} (Optional) One or more argument
strings which _expect a value_ when present in `argv` (see [Options][]
for details)
* `multiOptions` {string[]|string} (Optional) One or more argument
strings which, when appearing multiple times in `argv`, will be concatenated
into an Array
* Returns: {Object} An object having properties:
* `options` {Object}, having properties and values corresponding to parsed
[Options][] and [Flags][]
* `positionals` {string[]}, containing [Positionals][]

The `util.parseArgs` function parses command-line arguments from an Array of
strings and returns an object representation.

Example using [`process.argv`][]:

```js
// script.js
// called via `node script.js --foo bar baz`
const argv = util.parseArgs();

if (argv.foo === true) {
console.log(argv.positionals); // prints [ 'bar', 'baz' ]
}
```

Example using a custom `argv` and the `optionsWithValue` option:

```js
const argv = util.parseArgs(
['--foo', 'bar', 'baz'],
{ optionsWithValue: ['foo'] }
);

// argv.foo === 'bar'
if (argv.foo === 'bar') {
console.log(argv.positionals); // prints [ 'baz' ]
}
```

Example using custom `argv`, `optionsWithValue`, and the `multiOptions` option:

```js
const argv = util.parseArgs(
['--foo', 'bar', '--foo', 'baz'],
{ optionsWithValue: 'foo', multiOptions: 'foo' }
);

console.log(argv.options.bar); // prints [ 'bar', 'baz' ]
```

Example with custom `argv` and `multiOptions`:

```js
const argv = util.parseArgs(
['-v', '-v', '-v'],
{ multiOptions: 'v' }
);

console.log(argv.options.v); // prints [ true, true, true ]
```

[`ERR_INVALID_ARG_TYPE`][] will be thrown if the `argv` parameter is not an
Array.

Arguments fall into one of three catgories:

### Flags

_Flags_ are arguments which begin with one or more dashes (`-`), and _do not_
have an associated string value (e.g., `node app.js --verbose`).

* These will be parsed automatically; you do not need to "declare" them
* The Flag _name_ is the string following the prefix of _one or more_ dashes,
e.g., the name of `--foo` is `foo`
* Flag names become property names in the `options` property of the returned
object
* By default, when appearing any number of times in the `argv` Array, the value
of the property will be `true`. To get a "count" of the times a Flag is
repeated, specify the Flag name in the `multiOptions` option; this will be
parsed to an Array of `true` values, and you can derive the "count" from the
`length` property of this Array
* When a Flag appears in `multiOptions`, and when provided in `argv`, the value
in the returned object will _always_ be an Array (even if it is only provided
once)
* A Flag appearing in `multiOptions` but not in the `argv` Array will be omitted
from the `options` property of the returned object
* _Special case for negated Flags_: If a string value of `false` is provided in
`argv` for a Flag via the `=` separator, the value will become boolean
`false`, e.g., `['--verbose=false']` becomes `{options: {verbose: false}},
positionals: []}`; any value other than the string `false` will become `true`,
and if `=` is not provided, the value is interpreted as a
[Positional][Positionals]

### Options

_Options_ are arguments which begin with one or more dashes (`-`), and _expect_
an associated value (e.g., `node app.js --require script.js`).

* Use the `optionsWithValue` option to `util.parseArgs` to declare Options
* The Option _name_ is the string following the prefix of one-or-more dashes,
e.g., the name of `--foo` is `foo`
* The Option _value_ is the next string following the name, e.g., the Option
value of `['--foo' 'bar']` is `bar`
* Option values may be provided _with or without_ a `=` separator (e.g.,
`['--require=script.js']` is equivalent to `['--require', 'script.js']`)
* If an Option value is not found in `argv`, the associated value will be parsed
to an empty string (`''`)
* An argument-like value (a value beginning with one or more dashes) immediately
following an Option in the `argv` Array will cause the Option to be omitted
from the `options` property of the returned object _unless_ the `=` separator
is used; e.g., `['--foo', '--bar']` where `foo` is an Option will return
`{options: {bar: true}, positionals: []}`, but `['--foo=--bar']` will return
`{options: {foo: '--bar'}, positionals: []}`
* When an Option name appears in the Array (or string) of `optionsWithValue`,
and does _not_ appear in the `argv` Array, the resulting object _will not_
contain a property with this Option name (e.g., `util.parseArgs(['--bar'], {
optionsWithValue: 'foo' })` will result in `{options: {bar: true},
positionals: [] }`
* When an Option appears in `multiOptions`, and when provided in `argv`, the
value in the returned object will _always_ be an Array (even if it is only
provided once)

### Positionals

_Positionals_ (or "positional arguments") are arguments which _do not_ begin
with one or more dashes (e.g., `['script.js']`), _and/or_ all items in the
`argv` Array following a `--` (e.g., `['--', 'script.js']`).

* Positionals appear in the Array property `positionals` of the returned object
* The `positionals` property will _always_ be present and an Array, even if
empty
* If present in the `argv` Array, `--` is discarded and is omitted from the
returned object
* Positionals will _always_ be parsed verbatim (e.g., `['--', '--foo']` will
result in an object of `{positionals: ['--foo'], options: {}}`)

### Additional Considerations

* `util.parseArgs` does not consider "short" arguments (e.g., `-v`) to be
different than "long" arguments (e.g., `--verbose`). Furthermore, it does not
allow concatenation of short arguments (e.g., `-v -D` cannot be expressed as
`-vD`).
* _No_ conversion to or from "camelCase" occurs; a Flag or Option name of
`no-color` results in an object with a `no-color` property. A Flag or Option
name of `noColor` results in an object with a `noColor` property.

## `util.promisify(original)`
<!-- YAML
added: v8.0.0
Expand Down Expand Up @@ -2506,7 +2663,12 @@ util.log('Timestamped message.');
[compare function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Parameters
[constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor
[default sort]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
[`ERR_INVALID_ARG_TYPE`]: errors.html#ERR_INVALID_ARG_TYPE
[Flags]: #util_flags
[global symbol registry]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for
[list of deprecated APIS]: deprecations.md#deprecations_list_of_deprecated_apis
[`process.argv`]: process.md#process_process_argv
[Options]: #util_options
[Positionals]: #util_positionals
[semantically incompatible]: https://github.com/nodejs/node/issues/4179
[util.inspect.custom]: #util_util_inspect_custom
149 changes: 149 additions & 0 deletions lib/internal/util/parse_args.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
'use strict';

const {
ArrayIsArray,
ArrayPrototypePush,
ArrayPrototypeSlice,
SafeSet,
StringPrototypeReplace,
StringPrototypeSplit,
StringPrototypeStartsWith,
} = primordials;
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;

/**
* Returns an Object representation of command-line arguments.
*
* Default behavior:
* - All arguments are considered "boolean flags"; in the `options` property of
* returned object, the key is the argument (if present), and the value will
* be `true`.
* - Uses `process.argv.slice(2)`, but can accept an explicit array of strings.
* - Argument(s) specified in `opts.optionsWithValue` will have `string` values
* instead of `true`; the subsequent item in the `argv` array will be consumed
* if a `=` is not used
* - "Bare" arguments (positionals) are those which do not begin with a `-` or
* `--` and those after a bare `--`; these will be returned as items of the
* `positionals` array
* - The `positionals` array will always be present, even if empty.
* - The `options` Object will always be present, even if empty.
* @param {string[]} [argv=process.argv.slice(2)] - Array of script arguments as
* strings
* @param {Object} [options] - Options
* @param {string[]|string} [options.optionsWithValue] - This argument (or
* arguments) expect a value
* @param {string[]|string} [options.multiOptions] - This argument (or
* arguments) can be specified multiple times and its values will be
* concatenated into an array
* @returns {{options: Object, positionals: string[]}} Parsed arguments
* @example
* parseArgs(['--foo', '--bar'])
* // {options: { foo: true, bar: true }, positionals: []}
* parseArgs(['--foo', '-b'])
* // {options: { foo: true, b: true }, positionals: []}
* parseArgs(['---foo'])
* // {options: { foo: true }, positionals: []}
* parseArgs(['--foo=bar'])
* // {options: { foo: true }, positionals: []}
* parseArgs([--foo', 'bar'])
* // {options: {foo: true}, positionals: ['bar']}
* parseArgs(['--foo', 'bar'], {optionsWithValue: 'foo'})
* // {options: {foo: 'bar'}, positionals: []}
* parseArgs(['foo'])
* // {options: {}, positionals: ['foo']}
* parseArgs(['--foo', '--', '--bar'])
* // {options: {foo: true}, positionals: ['--bar']}
* parseArgs(['--foo=bar', '--foo=baz'])
* // {options: {foo: true}, positionals: []}
* parseArgs(['--foo=bar', '--foo=baz'], {optionsWithValue: 'foo'})
* // {options: {foo: 'baz'}, positionals: []}
* parseArgs(['--foo=bar', '--foo=baz'], {
* optionsWithValue: 'foo', multiOptions: 'foo'
* }) // {options: {foo: ['bar', 'baz']}, positionals: []}
* parseArgs(['--foo', '--foo'])
* // {options: {foo: true}, positionals: []}
* parseArgs(['--foo', '--foo'], {multiOptions: ['foo']})
* // {options: {foo: [true, true]}, positionals: []}
* parseArgs(['--very-wordy-option'])
* // {options: {'very-wordy-option': true}, positionals: []}
* parseArgs(['--verbose=false'])
* // {options: {verbose: false}, positionals: []}
* parseArgs(['--verbose', 'false'])
* // {options: {verbose: true}, positionals: ['false']}
*/
const parseArgs = (
argv = ArrayPrototypeSlice(process.argv, 2),
options = { optionsWithValue: [] }
) => {
if (!ArrayIsArray(argv)) {
options = argv;
argv = ArrayPrototypeSlice(process.argv, 2);
}
if (typeof options !== 'object' || options === null) {
throw new ERR_INVALID_ARG_TYPE(
'options',
'object',
options);
}
if (typeof options.optionsWithValue === 'string') {
options.optionsWithValue = [options.optionsWithValue];
}
if (typeof options.multiOptions === 'string') {
options.multiOptions = [options.multiOptions];
}
const optionsWithValue = new SafeSet(options.optionsWithValue || []);
const multiOptions = new SafeSet(options.multiOptions || []);
const result = { positionals: [], options: {} };

let pos = 0;
while (pos < argv.length) {
const arg = argv[pos];
if (StringPrototypeStartsWith(arg, '-')) {
// Everything after a bare '--' is considered a positional argument
// and is returned verbatim
if (arg === '--') {
ArrayPrototypePush(
result.positionals, ...ArrayPrototypeSlice(argv, ++pos)
);
return result;
}
// Any number of leading dashes are allowed
const argParts = StringPrototypeSplit(StringPrototypeReplace(arg, /^-+/, ''), '=');
const optionName = argParts[0];
let optionValue = argParts[1];

// Consume the next item in the array if `=` was not used
// and the next item is not itself a flag or option
if (optionsWithValue.has(optionName)) {
if (optionValue === undefined) {
optionValue = (
argv[pos + 1] && StringPrototypeStartsWith(argv[pos + 1], '-')
) || argv[++pos] || '';
}
} else {
// To get a `false` value for a Flag, use e.g., ['--flag=false']
optionValue = optionValue !== 'false';
}

if (multiOptions.has(optionName)) {
// Consume the next item in the array if `=` was not used
// and the next item is not itself a flag or option
if (result.options[optionName] === undefined) {
result.options[optionName] = [optionValue];
} else {
ArrayPrototypePush(result.options[optionName], optionValue);
}
} else if (optionValue !== undefined) {
result.options[optionName] = optionValue;
}
} else {
ArrayPrototypePush(result.positionals, arg);
}
pos++;
}
return result;
};

module.exports = {
parseArgs
};
2 changes: 2 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const { validateNumber } = require('internal/validators');
const { TextDecoder, TextEncoder } = require('internal/encoding');
const { isBuffer } = require('buffer').Buffer;
const types = require('internal/util/types');
const { parseArgs } = require('internal/util/parse_args');

const {
deprecate,
Expand Down Expand Up @@ -282,6 +283,7 @@ module.exports = {
isFunction,
isPrimitive,
log,
parseArgs,
promisify,
TextDecoder,
TextEncoder,
Expand Down
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
'lib/internal/util/inspect.js',
'lib/internal/util/inspector.js',
'lib/internal/util/iterable_weak_map.js',
'lib/internal/util/parse_args.js',
'lib/internal/util/types.js',
'lib/internal/http2/core.js',
'lib/internal/http2/compat.js',
Expand Down
1 change: 1 addition & 0 deletions test/parallel/test-bootstrap-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const expectedModules = new Set([
'NativeModule internal/util/debuglog',
'NativeModule internal/util/inspect',
'NativeModule internal/util/iterable_weak_map',
'NativeModule internal/util/parse_args',
'NativeModule internal/util/types',
'NativeModule internal/validators',
'NativeModule internal/vm/module',
Expand Down
Loading