Skip to content

Commit acfc33c

Browse files
shadowspawndanielleadams
authored andcommitted
util: add tokens to parseArgs
Offer additional meta-data for building custom and additional behaviour on top of parseArgs. PR-URL: #43459 Reviewed-By: Ben Coe <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 8a8de94 commit acfc33c

File tree

3 files changed

+515
-104
lines changed

3 files changed

+515
-104
lines changed

doc/api/util.md

+121-2
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,11 @@ equality.
10241024

10251025
<!-- YAML
10261026
added: v18.3.0
1027+
changes:
1028+
- version: REPLACEME
1029+
pr-url: https://github.com/nodejs/node/pull/43459
1030+
description: add support for returning detailed parse information
1031+
using `tokens` in input `config` and returned properties.
10271032
-->
10281033

10291034
> Stability: 1 - Experimental
@@ -1040,18 +1045,24 @@ added: v18.3.0
10401045
times. If `true`, all values will be collected in an array. If
10411046
`false`, values for the option are last-wins. **Default:** `false`.
10421047
* `short` {string} A single character alias for the option.
1043-
* `strict`: {boolean} Should an error be thrown when unknown arguments
1048+
* `strict` {boolean} Should an error be thrown when unknown arguments
10441049
are encountered, or when arguments are passed that do not match the
10451050
`type` configured in `options`.
10461051
**Default:** `true`.
1047-
* `allowPositionals`: {boolean} Whether this command accepts positional
1052+
* `allowPositionals` {boolean} Whether this command accepts positional
10481053
arguments.
10491054
**Default:** `false` if `strict` is `true`, otherwise `true`.
1055+
* `tokens` {boolean} Return the parsed tokens. This is useful for extending
1056+
the built-in behavior, from adding additional checks through to reprocessing
1057+
the tokens in different ways.
1058+
**Default:** `false`.
10501059

10511060
* Returns: {Object} The parsed command line arguments:
10521061
* `values` {Object} A mapping of parsed option names with their {string}
10531062
or {boolean} values.
10541063
* `positionals` {string\[]} Positional arguments.
1064+
* `tokens` {Object\[] | undefined} See [parseArgs tokens](#parseargs-tokens)
1065+
section. Only returned if `config` includes `tokens: true`.
10551066

10561067
Provides a higher level API for command-line argument parsing than interacting
10571068
with `process.argv` directly. Takes a specification for the expected arguments
@@ -1100,6 +1111,114 @@ console.log(values, positionals);
11001111
`util.parseArgs` is experimental and behavior may change. Join the
11011112
conversation in [pkgjs/parseargs][] to contribute to the design.
11021113

1114+
### `parseArgs` `tokens`
1115+
1116+
Detailed parse information is available for adding custom behaviours by
1117+
specifying `tokens: true` in the configuration.
1118+
The returned tokens have properties describing:
1119+
1120+
* all tokens
1121+
* `kind` {string} One of 'option', 'positional', or 'option-terminator'.
1122+
* `index` {number} Index of element in `args` containing token. So the
1123+
source argument for a token is `args[token.index]`.
1124+
* option tokens
1125+
* `name` {string} Long name of option.
1126+
* `rawName` {string} How option used in args, like `-f` of `--foo`.
1127+
* `value` {string | undefined} Option value specified in args.
1128+
Undefined for boolean options.
1129+
* `inlineValue` {boolean | undefined} Whether option value specified inline,
1130+
like `--foo=bar`.
1131+
* positional tokens
1132+
* `value` {string} The value of the positional argument in args (i.e. `args[index]`).
1133+
* option-terminator token
1134+
1135+
The returned tokens are in the order encountered in the input args. Options
1136+
that appear more than once in args produce a token for each use. Short option
1137+
groups like `-xy` expand to a token for each option. So `-xxx` produces
1138+
three tokens.
1139+
1140+
For example to use the returned tokens to add support for a negated option
1141+
like `--no-color`, the tokens can be reprocessed to change the value stored
1142+
for the negated option.
1143+
1144+
```mjs
1145+
import { parseArgs } from 'node:util';
1146+
1147+
const options = {
1148+
'color': { type: 'boolean' },
1149+
'no-color': { type: 'boolean' },
1150+
'logfile': { type: 'string' },
1151+
'no-logfile': { type: 'boolean' },
1152+
};
1153+
const { values, tokens } = parseArgs({ options, tokens: true });
1154+
1155+
// Reprocess the option tokens and overwrite the returned values.
1156+
tokens
1157+
.filter((token) => token.kind === 'option')
1158+
.forEach((token) => {
1159+
if (token.name.startsWith('no-')) {
1160+
// Store foo:false for --no-foo
1161+
const positiveName = token.name.slice(3);
1162+
values[positiveName] = false;
1163+
delete values[token.name];
1164+
} else {
1165+
// Resave value so last one wins if both --foo and --no-foo.
1166+
values[token.name] = token.value ?? true;
1167+
}
1168+
});
1169+
1170+
const color = values.color;
1171+
const logfile = values.logfile ?? 'default.log';
1172+
1173+
console.log({ logfile, color });
1174+
```
1175+
1176+
```cjs
1177+
const { parseArgs } = require('node:util');
1178+
1179+
const options = {
1180+
'color': { type: 'boolean' },
1181+
'no-color': { type: 'boolean' },
1182+
'logfile': { type: 'string' },
1183+
'no-logfile': { type: 'boolean' },
1184+
};
1185+
const { values, tokens } = parseArgs({ options, tokens: true });
1186+
1187+
// Reprocess the option tokens and overwrite the returned values.
1188+
tokens
1189+
.filter((token) => token.kind === 'option')
1190+
.forEach((token) => {
1191+
if (token.name.startsWith('no-')) {
1192+
// Store foo:false for --no-foo
1193+
const positiveName = token.name.slice(3);
1194+
values[positiveName] = false;
1195+
delete values[token.name];
1196+
} else {
1197+
// Resave value so last one wins if both --foo and --no-foo.
1198+
values[token.name] = token.value ?? true;
1199+
}
1200+
});
1201+
1202+
const color = values.color;
1203+
const logfile = values.logfile ?? 'default.log';
1204+
1205+
console.log({ logfile, color });
1206+
```
1207+
1208+
Example usage showing negated options, and when an option is used
1209+
multiple ways then last one wins.
1210+
1211+
```console
1212+
$ node negate.js
1213+
{ logfile: 'default.log', color: undefined }
1214+
$ node negate.js --no-logfile --no-color
1215+
{ logfile: false, color: false }
1216+
$ node negate.js --logfile=test.log --color
1217+
{ logfile: 'test.log', color: true }
1218+
$ node negate.js --no-logfile --logfile=test.log --color --no-color
1219+
{ logfile: 'test.log', color: false }
1220+
```
1221+
11031222
## `util.promisify(original)`
11041223
11051224
<!-- YAML

0 commit comments

Comments
 (0)