Repository navigation
util: Extended parseArgs validation settings #45062
Copy link
Copy link
Closed as not planned
Labels
feature requestIssues requesting new Node.js features.Issues requesting new Node.js features.staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.Issues and PRs marked stale due to inactivity and scheduled for automatic closure.utilIssues and PRs related to the built-in util module.Issues and PRs related to the built-in util module.
Description
On issue #44564, a required setting was proposed, but frowned upon due to the ideal optional nature of commandline flags and options, in that same linked issue I have made a counter proposal (#44564 (comment)) about incompatible and requires fields instead? This is a dedicated followup issue to that proposal for further discussion.
The proposal
The addition of 3 new fields to parseArgs' options objects: incompatible, requires and requiresOne.
While unconditionally required options are rare and frowned upon, dependent and incompatible options are far more common. These fields would be of type string[], containing names of other options in the current parseArgs object:
parseArgs({
args,
options: {
owner: {
type: 'string',
requires: ['repo'],
incompatible: ['org'],
},
org: {
type: 'string',
requires: ['repo'],
// no need to specify incompatible: ['owner']
// it should work both ways on either one
// but it can also be explicitly on both if desired
},
repo: {
type: 'string',
// details on requiresOne further down
requiresOne: ['owner', 'org'],
},
url: {
type: 'string',
incompatible: ['repo', 'org', 'owner'],
}
}
});The above code would result in:
# Allowed
foo --url=https://gh.com/user/proj
foo --owner=user --repo=proj
foo --org=nodejs --repo=node
foo
# Not allowed
foo --url=https://... --owner=user
foo --org=nodejs --url=https://...
foo --repo=proj --owner=user --url=https://...
foo --owner=user --org=nodejs --repo=proj
foo --repo=node
foo --owner=user
foo --org=nodejs- All of these new fields would default to an empty array
[]. If any of them specify either their own option or a non-existent option it should throw aTypeError. requiresis directional, if--arequires--bbut--bdoesn't require--a,foo --bis valid butfoo --ais not.requiresOneis a variation ofrequireswhere the array is treated as a list of ORs instead of ANDs.requiresandrequiresOnecan be used together without issue, but naturally overlapping options between the two will have higher precedence onrequires.incompatibleis bidirectional, if--adeclared itself incompatible with--b, then--bis also implicitly incompatible with--a. Explicitly marking both incompatible with each other is fine and does nothing extra.- If a conflict between the fields
requires*andincompatibleof two options or one by itself happens, it should throw aTypeError.
Notes
- The names of the 3 fields are not final and could be discussed further, some possible alternatives I have though of:
requiresAllmight be considered more consistent withrequiresOnethanrequires.- Finding a way to somehow merge
requiresandrequiresOneinto a single option might be a possibility too but I couldn't think of a good way. incompatible->incompatibleWith, slightly longer but more self-explanatory.incompatible->conflicts, compatibility with Commander and Yargs (util: ExtendedparseArgsvalidation settings #45062 (comment))
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
feature requestIssues requesting new Node.js features.Issues requesting new Node.js features.staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.Issues and PRs marked stale due to inactivity and scheduled for automatic closure.utilIssues and PRs related to the built-in util module.Issues and PRs related to the built-in util module.
Activity
jhmaster2000 commentedon Oct 19, 2022
Replying to these here to move the discussion.
Previous discussion for reference: #44564 (comment) #44564 (comment)
I don't see this as added complexity to the API surface since as previously mentioned all of these are 100% optional and do not need to be used, the current simple API would remain usable with no change. As for added complexity to the implementation, yes that's true, but I also see it as an argument in favor of adding it once more, given that if it isn't done by parseArgs, userland code will need to do said complex heavy lifting on its own, which can be prone to mistakes and results in hundreds of different implementations each with their quirks, and having users not need to do such heavy lifting themselves is precisely the goal of utility builtins.
It is actually a little funny how there is seemingly a paradox here, since as mentioned, there have been arguments in the case of the default values for parseArgs, that it was "too simple" of an addition to be worthwhile to add to the utility since userland code could easily implement such functionality on its own, and now the problem is "too complex", which contradicts the previous, something to think about I suppose...
shadowspawn commentedon Oct 19, 2022
(There are so many possible additions to argument parsing. I am checking whether proposed changes are a clean addition and widely desired, whether simple or complex to add. No paradox from my point of view, but yes gentle pushback on additions.)
shadowspawn commentedon Oct 19, 2022
Commander and Yargs both use
conflicts()as the name for the suggestedincompatible.(The naming is not independent. Commander only recently added
conflicts()after 10 years without any such routines, and I looked at the Yargs naming.)shadowspawn commentedon Oct 19, 2022
An implementation detail is whether such routines consider just the options from the command-line (or supplied
args), or consider the returned value of the option. The difference is the default values.From the wording referencing
--aI am guessing you are thinking of the command-line option and ignoring the default value.The reason for considering the default value is the author might stick in an environment variable as the default value. This means the option default value may be from the end-user.
jhmaster2000 commentedon Oct 19, 2022
conflictssounds fine to me aswell, I'd be happy with either, I'll add it as a possible alternative 👍I did overlook defining this behavior in the main post, but I think
requires*anddefaultshould simply be incompatible with each other, thinking about it from a JS/TS functions perspective, when you give an argument a default value, it becomes entirely optional and thus cannot be required, so an option with adefaultwould mean explicitly optional at all times and trying to require it should error.Conditionally doing it based on the truthiness of the default value concerns me because there is no guarantee an intended default value will always be a truthy JS value, namely
falseor an empty string.It could maybe be made so that the errors thrown provide sufficient parseable information so that if desired userland code can wrap the parseArgs call in a try catch and implement additional custom behavior for specific errors such as checking environment variable, database or cached data (etc) availability, since this really is one thing that just has too wide range of possibilities to make a good unclogged API for in my opinion, we would probably be talking about stuff like adding callbacks into this to support that in the parseArgs API itself.
tniessen commentedon Oct 20, 2022
It's not really a contradiction; complexity can be orthogonal to usefulness, and apparently, enough people saw the usefulness in default values (even though I personally might have preferred not to add default values to
parseArgs). This proposal, on the other hand, appears much more complex and, according to #45062 (comment), commander did not have this feature for an entire decade, so it might not be as desired.(Also, nobody said that this feature would be "too complex" to do in userland.)
jhmaster2000 commentedon Oct 20, 2022
In my opinion if people continue importing commander or other third party CLI libraries for these kinds of features that
parseArgsmight not have, it kind of takes away the value of havingparseArgsbuilt-in, which would have been to make argument handling easy without the need of third party libraries. Sure some basic CLI programs may have everything they need fromparseArgsalone, but a standard library should try to account for all (or a majority of) usecases.bakkot commentedon Dec 2, 2022
I disagree. It's much more important that the standard library be easy to use and reliable, which is directly in tension with having a bunch of options. It's fine if less-common use cases require you to implement a little logic yourself, even if that means some people will reach for a third-party library instead.
SRHerzog commentedon Feb 14, 2023
Here's a draft PR to help move the discussion forward. I don't see these options adding any instability or inconvenience for users who don't need them, other than an extra paragraph to skip over in the docs.
github-actions commentedon Aug 14, 2023
There has been no activity on this feature request for 5 months and it is unlikely to be implemented. It will be closed 6 months after the last non-automated comment.
For more information on how the project manages feature requests, please consult the feature request management document.
github-actions commentedon Sep 13, 2023
There has been no activity on this feature request and it is being closed. If you feel closing this issue is not the right thing to do, please leave a comment.
For more information on how the project manages feature requests, please consult the feature request management document.
util: extend parseArgs to validate relationships between options