Skip to content
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

refactor!: restructure configuration to take options bag #63

Merged
merged 23 commits into from
Mar 2, 2022

Conversation

aaronccasanova
Copy link
Collaborator

@aaronccasanova aaronccasanova commented Feb 9, 2022

Per the discussion in #45 this PR restructures the current options API where each option is configured in three separate list and instead allows options to be configured in a single object.

The goal being to make the API more intuitive for configuring options (e.g. short, withValue, and multiples) while creating a path forward for introducing more configuration options in the future (e.g. default).

Before:

parseArgs(process.argv.slice(2), {
  withValue: ['foo'],
  multiples: ['foo'],
  shorts: {
    f: 'foo',
  },
})

After:

parseArgs({
  args: process.argv.slice(2),
  options: {
    foo: {
      short: 'f',
      type: 'string', // defaults to 'boolean'
      multiple: true,
      // default: 'bar' - Future iteration
    }
  },
})

While the above mentioned discussion left off with a tentative interest in moving forward with the suggestion, I wanted to at least make an official PR with the proposed changes and have an implementation we could reference.

Note: All tests are passing and I've introduced a few new validation helpers to check the new structure

TODO: Assuming we decide to move forward with this approach

  • Update README examples

@bcoe
Copy link
Collaborator

bcoe commented Feb 11, 2022

I'm at least partially sold on this approach, especially if it would help us reach consensus on some other features .. such as my thinking that we could figure out a better API surface for strict if we configure as an options bag.

@bcoe
Copy link
Collaborator

bcoe commented Feb 11, 2022

@aaronccasanova I'd be interested in seeing the updated README if you have the time.

One open question for me, if an argument is a flag, would you just set withValue: false? is this confusing (@ljharb, @shadowspawn). I like that we could support strict with this approach, without adding an additional configuration -- I just wonder if withValue is the right variable name.

@ljharb
Copy link
Member

ljharb commented Feb 11, 2022

It is a bit confusing; I’d prefer an enum of “value” or “flag” if that were possible.

@aaronccasanova
Copy link
Collaborator Author

@aaronccasanova I'd be interested in seeing the updated README if you have the time.

Will do 👍

if an argument is a flag, would you just set withValue: false? is this confusing

I agree this is a bit confusing. I was assuming users would omit withValue and leverage the zero config behavior where options without withValue are flags.

One alternative solution is to replace withValue with type (e.g. type: string | number | boolean). Not only does this allow users to explicitly define the option type, but provides more meaningful metadata to the parser. For example, If option was set to type: 'number' the parser could confidently capture negative numbers (-100) instead of expanding them as a short options group (-1 -0 -0) as mentioned here.

@bcoe
Copy link
Collaborator

bcoe commented Feb 12, 2022

One alternative solution is to replace withValue with type (e.g. type: string | number | boolean)

I don't hate that idea, but for MVP think starting with string and boolean would be best, in the future we could decide whether we should add additional types, or leave this to implementers.

@aaronccasanova
Copy link
Collaborator Author

for MVP think starting with string and boolean would be best

Agreed, I went ahead and replaced the withValue option with type, added validation checks for string|boolean, and updated the tests: 082bec5

@aaronccasanova
Copy link
Collaborator Author

aaronccasanova commented Feb 12, 2022

@bcoe Updated the README to reflect the most recent implementation (with the type option): 0909008

@bnb bnb added this to the Merge into Node.js milestone Feb 18, 2022
@bnb
Copy link

bnb commented Feb 18, 2022

Added the Merge into Node.js milestone, not because this needs to be merged by then but because IMO we should definitely figure this out - merge or close - before that IMO.

@isaacs
Copy link

isaacs commented Feb 23, 2022

I like this a lot. It also leaves the door open for things like description, options, conflictsWith, validate: () => Boolean, etc. And it's much more declarative/readable.

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
@bcoe
Copy link
Collaborator

bcoe commented Feb 26, 2022

I will make an effort to review this tomorrow ❤️

index.js Outdated
for (const key of ['withValue', 'multiples']) {
if (ObjectHasOwn(options, key)) {
validateArray(options[key], `options.${key}`);
for (const [option, optionConfig] of ObjectEntries(options)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for..of is never robust, because of Symbol.iterator; this should use ArrayPrototypeForEach(ObjectEntries(options), ([option, optionConfig]) => { instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! ecf1fb0

index.js Outdated
Comment on lines 140 to 145
for (const [option, optionConfig] of ObjectEntries(options)) {
if (optionConfig.short === arg) {
arg = option; // now long!
break;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (const [option, optionConfig] of ObjectEntries(options)) {
if (optionConfig.short === arg) {
arg = option; // now long!
break;
}
}
const foundLong = ArrayPrototypeFind(ObjectEntries(options), ([, optionConfig]) => optionConfig.short === arg);
arg = foundLong?.[0] ?? StringPrototypeCharAt(arg, 1);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌 ecf1fb0

validators.js Outdated

function validateUnion(value, name, union) {
if (!union.includes(value)) {
throw new ERR_INVALID_ARG_TYPE(name, `[${union.join('|')}]`, value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new ERR_INVALID_ARG_TYPE(name, `[${union.join('|')}]`, value);
throw new ERR_INVALID_ARG_TYPE(name, `[${StringPrototypeJoin(union, '|')}]`, value);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌 e0d0c33

@shadowspawn
Copy link
Collaborator

shadowspawn commented Feb 28, 2022

Light comment: in terms of shim correctness, I am guessing the new validation helpers need to be outside validators.js which is a Node.js internal file?

Non-blocking. Interested in whether this is the case, but can be remedied in a separate PR.

@shadowspawn

This comment was marked as outdated.

index.js Outdated Show resolved Hide resolved
Copy link
Collaborator

@shadowspawn shadowspawn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only action items on my list are the two for loops noted by @ljharb
Approving assuming either fix those now, or open an issue to do later. 😄

@aaronccasanova
Copy link
Collaborator Author

aaronccasanova commented Mar 1, 2022

Thanks everyone for the feedback and helping improve the robustness of the implementation!! I think the PR is in a really good spot and has laid a great foundation for future iterations. Here are some notable changes that have recently been introduced since the initial reviews:

  • The argv parser config has been renamed to args
  • The multiples option config has been renamed to multiple
  • Internal usages of the name option have been updated to longOption and shortOption respectively
  • Added validation to ensure a provided short option config is a single character (and test case)

@shadowspawn shadowspawn self-requested a review March 1, 2022 03:55
Copy link
Collaborator

@shadowspawn shadowspawn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for the enthusiasm and work @aaronccasanova

index.js Outdated Show resolved Hide resolved
Co-authored-by: Jordan Harband <ljharb@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants