-
Notifications
You must be signed in to change notification settings - Fork 11
feat: add parsed meta-data to returned properties #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 61 commits
52c3a2e
5ec71b1
f711187
cce90bb
e4bc5fe
87624a1
2925639
412287e
1897dac
45d12e7
1d19fb2
9d1267d
c9c4d4c
ed4168d
16a9f51
915e6c3
641197e
18470bd
38d1d6c
cfbd436
397dcf1
938bdb0
c5da345
4e1ec2d
bd3f574
b703ee8
82339f9
fab1dc4
99165c9
9bd039d
6a3f637
4cfbc29
9a9a740
7cd685c
6f93632
642d8c0
e70609a
fdaa553
c293307
041459d
7aafaf6
1b6e585
20eacb0
81239d6
84dde8e
40a6201
07fbb91
35c16f1
038480a
d4f96cc
122727e
5b3518d
c8c2f84
2b119b0
1b3c076
e9e30a7
46ab295
01baee5
46903af
9b74c05
89e4532
3914949
d8126d1
87bab7b
02ea585
56fe4ca
722f05e
ae9eca4
f9dcc4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -25,18 +25,24 @@ added: REPLACEME | |||||
| times. If `true`, all values will be collected in an array. If | ||||||
| `false`, values for the option are last-wins. **Default:** `false`. | ||||||
| * `short` {string} A single character alias for the option. | ||||||
| * `strict`: {boolean} Should an error be thrown when unknown arguments | ||||||
| * `strict` {boolean} Should an error be thrown when unknown arguments | ||||||
| are encountered, or when arguments are passed that do not match the | ||||||
| `type` configured in `options`. | ||||||
| **Default:** `true`. | ||||||
| * `allowPositionals`: {boolean} Whether this command accepts positional | ||||||
| * `allowPositionals` {boolean} Whether this command accepts positional | ||||||
| arguments. | ||||||
| **Default:** `false` if `strict` is `true`, otherwise `true`. | ||||||
| * `tokens` {boolean} Return an array with the | ||||||
| parsed tokens. This is useful for extending the built-in behaviour, | ||||||
| from adding additional checks through to reprocessing the tokens | ||||||
| in different ways. | ||||||
| **Default:** `false`. | ||||||
|
|
||||||
| * Returns: {Object} The parsed command line arguments: | ||||||
| * `values` {Object} A mapping of parsed option names with their {string} | ||||||
| or {boolean} values. | ||||||
| * `positionals` {string\[]} Positional arguments. | ||||||
| * `tokens` {Object} Parsed tokens. Only present if requested. | ||||||
|
|
||||||
| Provides a higher level API for command-line argument parsing than interacting | ||||||
| with `process.argv` directly. Takes a specification for the expected arguments | ||||||
|
|
@@ -79,10 +85,114 @@ const { | |||||
| positionals | ||||||
| } = parseArgs({ args, options }); | ||||||
| console.log(values, positionals); | ||||||
| // Prints: [Object: null prototype] { foo: true, bar: 'b' } []ss | ||||||
| // Prints: [Object: null prototype] { foo: true, bar: 'b' } [] | ||||||
| ``` | ||||||
|
|
||||||
| Detailed parse information is available for adding custom behaviours by | ||||||
| specifying `tokens: true` in the configuration. | ||||||
| The returned tokens have | ||||||
| properties describing: | ||||||
|
|
||||||
| * all tokens | ||||||
| * `kind` { string } One of 'option', 'positional', or 'option-terminator'. | ||||||
| * `index` { number } Index of element in `args` containing token. So the source argument for a token is `args[token.index]`. | ||||||
| * option tokens | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To improve the readability I would explain that we mean the
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rather than quotes, which i find confusing, how about linking the term to the appropriate section?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that works too. I use the same syntax in the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The compact POJO description is a bit subtle to read. How about an expanded version with all the properties listed? Proposed expandedA returned token has two properties which are always defined,
An option token has additional parse details
A positional token has just one additional property with the positional value:
An option-terminator token has only the base properties:
Old compactThe returned tokens have properties describing:
(Also asked in: nodejs/node#43459 (comment)) |
||||||
| * `name` { string } Long name of option. | ||||||
| * `rawName` { string } How option used in args, like `-f` of `--foo`. | ||||||
| * `value` { string | undefined } Option value specified in args. | ||||||
| Undefined for boolean options. | ||||||
| * `inlineValue` { boolean | undefined } Whether option value specified inline, | ||||||
| like `--foo=bar`. | ||||||
| * positional tokens | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| * `value` { string } The value of the positional argument in args (i.e. `args[index]`). | ||||||
| * option-terminator token | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| The returned tokens are in the order encountered in the input args. Options that appear | ||||||
| more than once in args produce a token for each use. | ||||||
| Short option groups like `-xy` expand to a token for each option. So `-xxx` produces | ||||||
| three tokens. | ||||||
|
|
||||||
| For example to use the returned tokens to add support for a negated option like `--no-color`, the tokens | ||||||
| can be reprocessed to change the value stored for the negated option. | ||||||
|
|
||||||
| ```mjs | ||||||
| import { parseArgs } from 'node:util'; | ||||||
|
|
||||||
| const options = { | ||||||
| ['color']: { type: 'boolean' }, | ||||||
| ['no-color']: { type: 'boolean' }, | ||||||
| ['logfile']: { type: 'string' }, | ||||||
| ['no-logfile']: { type: 'boolean' }, | ||||||
|
shadowspawn marked this conversation as resolved.
Outdated
|
||||||
| }; | ||||||
| const { values, tokens } = parseArgs({ options, tokens: true }); | ||||||
|
|
||||||
| // Reprocess the option tokens and overwrite the returned values. | ||||||
| tokens | ||||||
| .filter((token) => token.kind === 'option') | ||||||
| .forEach((token) => { | ||||||
| if (token.name.startsWith('no-')) { | ||||||
| // Store foo:false for --no-foo | ||||||
| const positiveName = token.name.slice(3); | ||||||
| values[positiveName] = false; | ||||||
| delete values[token.name]; | ||||||
| } else { | ||||||
| // Resave value so last one wins if both --foo and --no-foo. | ||||||
| values[token.name] = token.value ?? true; | ||||||
| } | ||||||
| }); | ||||||
|
|
||||||
| const color = values.color; | ||||||
| const logfile = values.logfile ?? 'default.log'; | ||||||
|
|
||||||
| console.log({ logfile, color }); | ||||||
| ``` | ||||||
|
|
||||||
| ```cjs | ||||||
|
shadowspawn marked this conversation as resolved.
|
||||||
| const { parseArgs } = require('node:util'); | ||||||
|
|
||||||
| const options = { | ||||||
| ['color']: { type: 'boolean' }, | ||||||
| ['no-color']: { type: 'boolean' }, | ||||||
| ['logfile']: { type: 'string' }, | ||||||
| ['no-logfile']: { type: 'boolean' }, | ||||||
|
shadowspawn marked this conversation as resolved.
Outdated
|
||||||
| }; | ||||||
| const { values, tokens } = parseArgs({ options, tokens: true }); | ||||||
|
|
||||||
| // Reprocess the option tokens and overwrite the returned values. | ||||||
| tokens | ||||||
| .filter((token) => token.kind === 'option') | ||||||
| .forEach((token) => { | ||||||
| if (token.name.startsWith('no-')) { | ||||||
| // Store foo:false for --no-foo | ||||||
| const positiveName = token.name.slice(3); | ||||||
| values[positiveName] = false; | ||||||
| delete values[token.name]; | ||||||
| } else { | ||||||
| // Resave value so last one wins if both --foo and --no-foo. | ||||||
| values[token.name] = token.value ?? true; | ||||||
| } | ||||||
| }); | ||||||
|
|
||||||
| const color = values.color; | ||||||
| const logfile = values.logfile ?? 'default.log'; | ||||||
|
|
||||||
| console.log({ logfile, color }); | ||||||
| ``` | ||||||
|
|
||||||
| Example usage showing negated options, and when option use multiple times then last one wins. | ||||||
|
|
||||||
| ```console | ||||||
| $ node negate.js | ||||||
| { logfile: 'default.log', color: undefined } | ||||||
| $ node negate.js --no-logfile --no-color | ||||||
| { logfile: false, color: false } | ||||||
| $ node negate.js --logfile=test.log --color | ||||||
| { logfile: 'test.log', color: true } | ||||||
| $ node negate.js --no-logfile --logfile=test.log --color --no-color | ||||||
| { logfile: 'test.log', color: false } | ||||||
| ``` | ||||||
|
|
||||||
| `util.parseArgs` is experimental and behavior may change. Join the | ||||||
| `util.parseArgs()` is experimental and behavior may change. Join the | ||||||
| conversation in [pkgjs/parseargs][] to contribute to the design. | ||||||
|
|
||||||
| ----- | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| 'use strict'; | ||
|
|
||
| // How might I require long options with values use '='? | ||
| // So allow `--foo=bar`, and not allow `--foo bar`. | ||
|
|
||
| // 1. const { parseArgs } = require('node:util'); // from node | ||
| // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package | ||
| const { parseArgs } = require('..'); // in repo | ||
|
|
||
| const options = { | ||
| file: { short: 'f', type: 'string' }, | ||
| log: { type: 'string' }, | ||
| }; | ||
|
|
||
| const { values, tokens } = parseArgs({ options, tokens: true }); | ||
|
|
||
| const badToken = tokens.find((token) => token.kind === 'option' && | ||
| token.value != null && | ||
| token.rawName.startsWith('--') && | ||
| !token.inlineValue | ||
| ); | ||
| if (badToken) { | ||
| throw new Error(`Option value for '${badToken.rawName}' must be inline, like '${badToken.rawName}=VALUE'`); | ||
| } | ||
|
|
||
| console.log(values); | ||
|
|
||
| // Try the following: | ||
| // node limit-long-syntax.js -f FILE --log=LOG | ||
| // node limit-long-syntax.js --file FILE |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| 'use strict'; | ||
|
|
||
| // How might I add my own support for --no-foo? | ||
|
|
||
| // 1. const { parseArgs } = require('node:util'); // from node | ||
| // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package | ||
| const { parseArgs } = require('..'); // in repo | ||
|
|
||
| const options = { | ||
| ['color']: { type: 'boolean' }, | ||
| ['no-color']: { type: 'boolean' }, | ||
| ['logfile']: { type: 'string' }, | ||
| ['no-logfile']: { type: 'boolean' }, | ||
| }; | ||
| const { values, tokens } = parseArgs({ options, tokens: true }); | ||
|
|
||
| // Reprocess the option tokens and overwrite the returned values. | ||
| tokens | ||
| .filter((token) => token.kind === 'option') | ||
| .forEach((token) => { | ||
|
shadowspawn marked this conversation as resolved.
|
||
| if (token.name.startsWith('no-')) { | ||
| // Store foo:false for --no-foo | ||
| const positiveName = token.name.slice(3); | ||
| values[positiveName] = false; | ||
| delete values[token.name]; | ||
| } else { | ||
| // Resave value so last one wins if both --foo and --no-foo. | ||
| values[token.name] = token.value ?? true; | ||
| } | ||
| }); | ||
|
|
||
| const color = values.color; | ||
| const logfile = values.logfile ?? 'default.log'; | ||
|
|
||
| console.log({ logfile, color }); | ||
|
|
||
| // Try the following: | ||
| // node negate.js | ||
| // node negate.js --logfile=test.log --color | ||
| // node negate.js --no-logfile --no-color | ||
| // node negate.js --no-logfile --logfile=test.log --color --no-color | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| 'use strict'; | ||
|
|
||
| // How might I throw if an option is repeated? | ||
|
|
||
| // 1. const { parseArgs } = require('node:util'); // from node | ||
| // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package | ||
| const { parseArgs } = require('..'); // in repo | ||
|
|
||
| const options = { | ||
| ding: { type: 'boolean', short: 'd' }, | ||
| beep: { type: 'boolean', short: 'b' } | ||
| }; | ||
| const { values, tokens } = parseArgs({ options, tokens: true }); | ||
|
|
||
| const seenBefore = new Set(); | ||
| tokens.forEach((token) => { | ||
| if (token.kind !== 'option') return; | ||
| if (seenBefore.has(token.name)) { | ||
| throw new Error(`option '${token.name}' used multiple times`); | ||
| } | ||
| seenBefore.add(token.name); | ||
| }); | ||
|
|
||
| console.log(values); | ||
|
|
||
| // Try the following: | ||
| // node no-repeated-options --ding --beep | ||
| // node no-repeated-options --beep -b | ||
| // node no-repeated-options -ddd |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // Now might I enforce that two flags are specified in a specific order? | ||
|
|
||
| import { parseArgs } from '../index.js'; | ||
|
shadowspawn marked this conversation as resolved.
Outdated
|
||
|
|
||
| function findTokenIndex(tokens, target) { | ||
| return tokens.findIndex((token) => token.kind === 'option' && | ||
| token.name === target | ||
| ); | ||
| } | ||
|
|
||
| const experimentalName = 'enable-experimental-options'; | ||
| const unstableName = 'some-unstable-option'; | ||
|
|
||
| const options = { | ||
| [experimentalName]: { type: 'boolean' }, | ||
| [unstableName]: { type: 'boolean' }, | ||
| }; | ||
|
|
||
| const { values, tokens } = parseArgs({ options, tokens: true }); | ||
|
|
||
| const experimentalIndex = findTokenIndex(tokens, experimentalName); | ||
| const unstableIndex = findTokenIndex(tokens, unstableName); | ||
| if (unstableIndex !== -1 && | ||
| ((experimentalIndex === -1) || (unstableIndex < experimentalIndex))) { | ||
| throw new Error(`'--${experimentalName}' must be specified before '--${unstableName}'`); | ||
| } | ||
|
|
||
| console.log(values); | ||
|
|
||
| /* eslint-disable max-len */ | ||
| // Try the following: | ||
| // node ordered-options.mjs | ||
| // node ordered-options.mjs --some-unstable-option | ||
| // node ordered-options.mjs --some-unstable-option --enable-experimental-options | ||
| // node ordered-options.mjs --enable-experimental-options --some-unstable-option | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| 'use strict'; | ||
|
|
||
| // This example is used in the documentation. | ||
|
|
||
| // 1. const { parseArgs } = require('node:util'); // from node | ||
| // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package | ||
| const { parseArgs } = require('..'); // in repo | ||
|
|
||
| console.log(parseArgs({ strict: false, tokens: true })); | ||
|
|
||
| // Try the following: | ||
| // node tokens.js -xy --foo=BAR -- file.txt | ||
| // node tokens.js one -abc two |
Uh oh!
There was an error while loading. Please reload this page.