-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
util: add tokens to parseArgs #43459
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 1 commit
1060df0
ed54c30
fb2cd00
2570047
33dbe57
27d9e4d
454bd33
baccf9b
d51dcb7
f8bc77d
0bd9da6
25cb98c
a097b80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1102,79 +1102,109 @@ console.log(values, positionals); | |
| ``` | ||
|
|
||
| Detailed parse information is available for adding custom behaviours by | ||
| specifying `tokens: true` in the configuration. The returned tokens have | ||
| specifying `tokens: true` in the configuration. | ||
| The returned tokens have | ||
| properties describing: | ||
|
|
||
| * all tokens | ||
|
Contributor
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. Just a thought, I would be tempted to make tokens a class rather than a POJO with a class Token {}
class OptionTerminator extends Token {}
class PositionalToken extends Token {}With the classes exposed on the Then I think you could just document the tokens as classes, similar to
Contributor
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. Even if these are made classes they should definitely expose a
Contributor
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.
Sounds good to me. I think it might be worth the class based approach, so that people could opt to check instanceof, if they're so inclined? Should we consider making kind a Symbol, or is a string preferable?
Contributor
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. Strings are traditional - they're a lot easier to work with, since you don't have to import them.
Member
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.
It did cross my mind as soon as I added the The examples of I'm not against classes as such, but not seeing benefits in this case.
Contributor
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. My 2 cents is we should go with the first one and make it less verbose, the "existing" version can be a bit confusing I think. But feel free to disagree, none option is perfect anyway, and we can always edit the docs later if we find a better layout.
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. I definitely find the existing one massively clearer; muddling things up with unnecessary TS/Java-ish terms and structure makes things more confusing.
Member
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. Mixed opinions straight away! Thanks both for feedback. I will leave it as is, barring further feedback.
Contributor
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.
Member
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:
(Duplicate post of: pkgjs/parseargs#129 (comment)) |
||
| * `kind` { string } One of 'option', 'positional', or 'option-terminator'. | ||
| * `index` { number } Index of element in `args` containing token. | ||
| * `index` { number } Index of element in `args` containing token. So the source argument for a token is `args[token.index]`. | ||
| * option tokens | ||
| * `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. | ||
| Undefined for boolean options. | ||
| * `inlineValue` { boolean | undefined } Whether option value specified inline, | ||
| like `--foo=bar`. | ||
| like `--foo=bar`. | ||
| * positional tokens | ||
| * `value` { string } the value of the positional argument in args (i.e. `args[index]`). | ||
| * `value` { string } The value of the positional argument in args (i.e. `args[index]`). | ||
| * option-terminator token | ||
|
|
||
| For example, assuming the following script which uses | ||
| automatic detection of options and no error checking: | ||
| 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'; | ||
| console.log(parseArgs({ strict: false, tokens: true })); | ||
|
|
||
| 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) => { | ||
| 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 | ||
| const { parseArgs } = require('node:util'); | ||
|
shadowspawn marked this conversation as resolved.
|
||
| console.log(parseArgs({ strict: false, tokens: true })); | ||
|
|
||
| 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) => { | ||
| 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 }); | ||
| ``` | ||
|
|
||
| This call shows the three kinds of token and their properties: | ||
| Example usage showing negated options, and when option use multiple times then last one wins. | ||
|
|
||
| ```console | ||
| $ node tokens.cjs -xy --foo=BAR -- file.txt | ||
| { | ||
| values: [Object: null prototype] { d: true, foo: 'BAR' }, | ||
| positionals: [ 'file.txt' ], | ||
| tokens: [ | ||
| { | ||
| kind: 'option', | ||
| name: 'x', | ||
| rawName: '-x', | ||
| index: 0, | ||
| value: undefined, | ||
| inlineValue: undefined | ||
| }, | ||
| { | ||
| kind: 'option', | ||
| name: 'y', | ||
| rawName: '-y', | ||
| index: 0, | ||
| value: undefined, | ||
| inlineValue: undefined | ||
| }, | ||
| { | ||
| kind: 'option', | ||
| name: 'foo', | ||
| rawName: '--foo', | ||
| index: 1, | ||
| value: 'BAR', | ||
| inlineValue: true | ||
| }, | ||
| { kind: 'option-terminator', index: 2 }, | ||
| { kind: 'positional', index: 3, value: 'file.txt' } | ||
| ] | ||
| } | ||
| $ 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 } | ||
| ``` | ||
|
|
||
| The source argument for a token is `args[token.index]`. | ||
| Short option groups like `-xy` expand to a token for each option. | ||
| The `x` and `y` tokens above have the same index, since | ||
| they come from the same argument. | ||
|
|
||
| `util.parseArgs` is experimental and behavior may change. Join the | ||
| conversation in [pkgjs/parseargs][] to contribute to the design. | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.