Skip to content

Commit d0dbb75

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

File tree

3 files changed

+515
-104
lines changed

3 files changed

+515
-104
lines changed

doc/api/util.md

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,11 @@ equality.
10281028

10291029
<!-- YAML
10301030
added: v18.3.0
1031+
changes:
1032+
- version: REPLACEME
1033+
pr-url: https://github.com/nodejs/node/pull/43459
1034+
description: add support for returning detailed parse information
1035+
using `tokens` in input `config` and returned properties.
10311036
-->
10321037

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

10551064
* Returns: {Object} The parsed command line arguments:
10561065
* `values` {Object} A mapping of parsed option names with their {string}
10571066
or {boolean} values.
10581067
* `positionals` {string\[]} Positional arguments.
1068+
* `tokens` {Object\[] | undefined} See [parseArgs tokens](#parseargs-tokens)
1069+
section. Only returned if `config` includes `tokens: true`.
10591070

10601071
Provides a higher level API for command-line argument parsing than interacting
10611072
with `process.argv` directly. Takes a specification for the expected arguments
@@ -1104,6 +1115,114 @@ console.log(values, positionals);
11041115
`util.parseArgs` is experimental and behavior may change. Join the
11051116
conversation in [pkgjs/parseargs][] to contribute to the design.
11061117

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

0 commit comments

Comments
 (0)