You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I don't think this is a good idea. We should be keeping as few assumptions out of this implementation as possible & force the user to define when/if values are to be captured & returned or not (which puts them into a better overall experience).
What I think is happening here, & is concerning, is that we've changed the meaning of args (aka. flags) which is causing us to loose information & muddy context along the way.
Looking back (ref. https://github.com/pkgjs/parseargs/blob/3f057c1b158a1bdbe878c64b57460c58e56e465f/README.md), args (aka. flags) was meant to track the existence of an argument no matter what its value, or the provided function options, were. withValue was meant to define whether to return any value back in values (ie. a parsed string from "key=value", a captured positional string or undefined).
Looking at the most recent version of the spec (2022/01/16):
I now need to do two different lookups to find the existence of an argument, & we've created an arbitrary delineation of two "types" of arguments (ie. ones with & without values). Worse yet, afaict, there's no way to determine existence, vs. value, when using withValue.
What I think is happening here, & is concerning, is that we've changed the meaning of args (aka. flags) which is causing us to loose information & muddy context along the way.
I was a driver for that, sorry. The intended usage and potential benefits were not apparent to me (#13).
I am not sure there is more information with the original implementation, but I'll work through your example carefully before commenting further. Thanks for the detail, and I look forward to discussing the issues raised!
What behaviour were you expecting for an unconfigured --key=value?
Is your primary use case a user whipping up a CLI, or using this as a base for a more robust and fuller implementation?
This feels like non-deterministic behavior as in both cases I've set a flag but only changed how I've declared it's value. If I were able to rely on flags always returning true when a flag existed I can write better programs or abstractions.
Example: more fully-baked validation & aliasing...
const{ flags, values }=parseArgs({withValue: ['org','force']})constsettings={org: {alias: 'b',usage: 'run this program with `--org <name>`',required: true,validate: Object.toString},force: {alias: 'f',usage: 'run this program with `--force` to force some action',default: false,validate: (value)=>{if(~['true','false'].indexOf(value.toLowerCase())){returnBoolean(value)}throwError('Type validation error!')}}}Object.entries(settings).map(([flag,option])=>{constisDefined=flags[arg]||flags[option.alias]constvalue=values[flag]||values[option.alias]if(!isDefined&&option.required)throwError('Required arguments are missing',option.usage)if(defined&&option.validate)values[flag]=option.validate(value)})
On "zero config"...
I'm honestly not that opposed to parsing --key=value by default but it does change how I see withValue & it's usefullness.
Changes & explicit rules we might want to consider if we go this route...
drop withValue
introduce acceptsPositionalValues(or some better name, we can bikeshed here)
--key= parses to undefined
--key=value parses to a string value
--key=value1 --key value2 parse things differently with & without acceptsPositionalValues &/or multiples options associated with the key
Examples of this in practice...
Notably, these all include/respect the idea that we'd return the existence of flags again in flags (as referenced above):
"(Early morning here. Hopefully I will have time tonight for some in depth replies.)"
No worries. Take your time. I know I wrote a lot (apologies; I'm playing catch-up for not having time over the last ~3-6months)
"A quick note that I think --foo= should set the value to the empty string, not to undefined.
We might just have to agree to diasgree 🤷🏼♂️ (I noted as much in our other thread). I don't feel super strongly about it though unless that decision effects if/how we throw in strict mode (ie. would we consider --foo= an error if withValue: [ 'foo' ] & strict: true were both defined... even though we would parse that "" otherwise 🤔 something to consider imo).
For me, my expectation is that --foo & --foo= would be the same (ie. undefined). --foo="" is explicitly an empty string.
I know the context is somewhat different but in JS... v not relevant
letfoo=;// is an errorletfoo;// is undefinedletfoo='';// is an empty string
For normal command-line use, there is parsing going on from shell script before node or client code every see the args. I think this makes the distinction between --foo= and --foo="" moot, they look the same by the time they reach parseArgs. Simple example:
I'll pick up this comment first because I think it highlights some key points of confusion or contention. I won't write an essay about every two sentences! And epic exposition in later comment #24 (comment) , thanks @darcyclarke , I'm going to read through that multiple times before commenting on it.
I now need to do two different lookups to find the existence of an argument, & we've created an arbitrary delineation of two "types" of arguments (ie. ones with & without values).
There are different user affordances provided by either approach. To be clear, I am not against your original intentions now you have explained it. From experience with Commander and reading about Yargs and Minimist, I expected a single result object rather than a pair and came up with a different pattern to try and justify the pair: an explicit delineation of two types of arguments, the ones with and without values. I actually thought this might have been part of the original intent, some sort of type safety, one holds booleans and one holds strings in the normal case. I was way wrong about that intent. :-)
Let's assume for a moment we stick with the current suggestion, misguided as it may be. I don't think there is loss of information or difficulty in use.
As the author, I know what I am expecting. If I want to check a boolean flag has been used, I simply check flags[key]. If I want to check an option for which I am expecting a value, if I am ignoring empty strings I can simply check !!values[key], or to include the empty string then values[key] !== undefined.
If I am testing for the existence of an argument with or without a value, I do need to check two values. But this does not seem like a happy-path case.
Worse yet, afaict, there's no way to determine existence, vs. value, when using withValue.
I don't think I understand. If foo is included in withValue, it will have a value in values if it was specified on the command-line. It will not have a value in values if it was not specified on the command-line. The tricky case is --foo without a positional to supply the value (#25) and the suggestion was to store undefined. (I think storing undefined should be be avoided in happy-path cases because it is a pain to test for and a pain to talk about, the property is defined with the value undefined. More reasonable in unhappy path cases like this one.)
I don't think this is a good idea. We should be keeping as few assumptions out of this implementation as possible & force the user to define when/if values are to be captured & returned or not (which puts them into a better overall experience).
My original thought was thatkey would get added to flags & value would be ignored (I'm not opposed to changing this though, see more below)
My concern with that approach when I came across it in the implementation was it has thrown away information. This seems inconsistent with "bring your own validation".
I should say explicitly that I am not heavily invested in the current mutually exclusive flags/values! (I came in expecting a single options result with strings for options with values and true for boolean flags. The XOR flags/values was influenced by that more common pattern.)
I am fine with the seen/any-value concept in theory. However, I am questioning whether it preserves any additional information. If there is no extra information, the pattern has to stand on its other merits.
Part of the point of my refactor in PR #26 was to make it easy to adjust the behaviour, and easy to reuse the behaviour when adding short options.
Because I am using the convention from Darcy's initial spec that one of the results holds whether an option was used on the command line at all, either as a flag or with a value. So in this example the options result records the existence of the option in the input args. (Unfortunately, having multiple conventions in flight is making it hard to talk about anything clearly!) See #38 for longer discussion. I'll add a line clarifying what options is.
The returned object will have just two of those properties, depending on which we implement:
"options" and "values" (aka "existence"), or
"flags" and "values" (xor, option appears in one or the other)
(I'm being very careful as I am not sure I understand your question and not sure if yes or no is the answer! Ask again if my answer is still not useful, we might be at cross-purposes.)
If I configure one flag, one value, and three arguments are passed, I would expect "options" to contain all three, "flags" to contain one or two, and "values" to contain one or two. No?
If there were such an ability, then would my expectations match? ie, { options: { foo: true, bar: true, baz: true }, flags: { foo: true }, values: { foo: undefined, baz: undefined } }?
Side note: I am suggesting we do not store undefined in values for flags, and just leave it undefined. Author still writes code like values[key] !== undefined without needing to wonder about hasOwnProperty.
That also means Object.keys(values) lacks useful information.
Yes indeed, values is not the sole source of truth. But the questions I considered can easily be answered with the information available.
(I started out working through examples testing for explicit undefined and was not happy with the experience. Then I had the realisation that "looks like a flag, store like a flag" reduces and simplifies the possible results. Still championing that concept!)
Leaving aside multiples for now, an option is either used as a flag or with a value. The author know which they intended. The results show what the end-user did.
Have a look at the uses cases I run through in #38 to see if I missed something important.
Activity
[-]Result for zero config --foo=a[/-][+]Behaviour for zero config --foo=a ?[/+]bcoe commentedon Dec 28, 2021
@shadowspawn are you thinking that
--foo-ashould implicitly behave as iffoowas configuredwithValue? This seems like a good assumption to me.darcyclarke commentedon Jan 16, 2022
I don't think this is a good idea. We should be keeping as few assumptions out of this implementation as possible & force the user to define when/if values are to be captured & returned or not (which puts them into a better overall experience).
What I think is happening here, & is concerning, is that we've changed the meaning of
args(aka.flags) which is causing us to loose information & muddy context along the way.Looking back (ref. https://github.com/pkgjs/parseargs/blob/3f057c1b158a1bdbe878c64b57460c58e56e465f/README.md),
args(aka.flags) was meant to track the existence of an argument no matter what its value, or the provided function options, were.withValuewas meant to define whether to return any value back invalues(ie. a parsedstringfrom"key=value", a captured positionalstringorundefined).Looking at the most recent version of the spec (2022/01/16):
I now need to do two different lookups to find the existence of an argument, & we've created an arbitrary delineation of two "types" of arguments (ie. ones with & without values). Worse yet, afaict, there's no way to determine existence, vs. value, when using
withValue.Previously, I could write the following no matter what the options...
Either way, my knee-jerk reaction here is that if we assume parsing of
--key=valuethen...--key=with & withoutwithValue: [ 'key' ]defined (potentially making further assumptions)withValueas a whole (as it seems like it would only be relevant for positional value association/parsing now)shadowspawn commentedon Jan 16, 2022
I was a driver for that, sorry. The intended usage and potential benefits were not apparent to me (#13).
I am not sure there is more information with the original implementation, but I'll work through your example carefully before commenting further. Thanks for the detail, and I look forward to discussing the issues raised!
What behaviour were you expecting for an unconfigured
--key=value?Is your primary use case a user whipping up a CLI, or using this as a base for a more robust and fuller implementation?
darcyclarke commentedon Jan 16, 2022
My original thought was that
keywould get added toflags&valuewould be ignored (I'm not opposed to changing this though, see more below)Both. The ease of use & understanding
parseArgs().flags.fooreturning the existence of--foocaptures both audiences.Take these two examples:
This feels like non-deterministic behavior as in both cases I've set a flag but only changed how I've declared it's value. If I were able to rely on
flagsalways returningtruewhen a flag existed I can write better programs or abstractions.Example: quick & dirty...
Example: more fully-baked validation & aliasing...
On "zero config"...
I'm honestly not that opposed to parsing
--key=valueby default but it does change how I seewithValue& it's usefullness.Changes & explicit rules we might want to consider if we go this route...
withValueacceptsPositionalValues(or some better name, we can bikeshed here)--key=parses toundefined--key=valueparses to astringvalue--key=value1 --key value2parse things differently with & withoutacceptsPositionalValues&/ormultiplesoptions associated with thekeyExamples of this in practice...
Notably, these all include/respect the idea that we'd return the existence of flags again in
flags(as referenced above):shadowspawn commentedon Jan 16, 2022
A quick note that I think
--foo=should set the value to the empty string, not toundefined.(Early morning here. Hopefully I will have time tonight for some in depth replies.)
darcyclarke commentedon Jan 16, 2022
No worries. Take your time. I know I wrote a lot (apologies; I'm playing catch-up for not having time over the last ~3-6months)
We might just have to agree to diasgree 🤷🏼♂️ (I noted as much in our other thread). I don't feel super strongly about it though unless that decision effects if/how we
throwinstrictmode (ie. would we consider--foo=an error ifwithValue: [ 'foo' ]&strict: truewere both defined... even though we would parse that""otherwise 🤔 something to consider imo).For me, my expectation is that
--foo&--foo=would be the same (ie.undefined).--foo=""is explicitly an empty string.I know the context is somewhat different but in JS...v not relevantshadowspawn commentedon Jan 16, 2022
For normal command-line use, there is parsing going on from shell script before node or client code every see the args. I think this makes the distinction between
--foo=and--foo=""moot, they look the same by the time they reach parseArgs. Simple example:darcyclarke commentedon Jan 16, 2022
With that in mind, I'd think that
--fooshould probably be treated the same way... so--foo="",--foo=&--fooall setvalues.foo === ""; thoughts?ljharb commentedon Jan 17, 2022
--key=should parse to the empty string, just like in a URL query string.--keyshould parse to eitherundefinedortrue.shadowspawn commentedon Jan 17, 2022
I'll pick up this comment first because I think it highlights some key points of confusion or contention. I won't write an essay about every two sentences! And epic exposition in later comment #24 (comment) , thanks @darcyclarke , I'm going to read through that multiple times before commenting on it.
There are different user affordances provided by either approach. To be clear, I am not against your original intentions now you have explained it. From experience with Commander and reading about Yargs and Minimist, I expected a single result object rather than a pair and came up with a different pattern to try and justify the pair: an explicit delineation of two types of arguments, the ones with and without values. I actually thought this might have been part of the original intent, some sort of type safety, one holds booleans and one holds strings in the normal case. I was way wrong about that intent. :-)
Let's assume for a moment we stick with the current suggestion, misguided as it may be. I don't think there is loss of information or difficulty in use.
As the author, I know what I am expecting. If I want to check a boolean flag has been used, I simply check
flags[key]. If I want to check an option for which I am expecting a value, if I am ignoring empty strings I can simply check!!values[key], or to include the empty string thenvalues[key] !== undefined.If I am testing for the existence of an argument with or without a value, I do need to check two values. But this does not seem like a happy-path case.
I don't think I understand. If
foois included inwithValue, it will have a value invaluesif it was specified on the command-line. It will not have a value invaluesif it was not specified on the command-line. The tricky case is--foowithout a positional to supply the value (#25) and the suggestion was to storeundefined. (I think storing undefined should be be avoided in happy-path cases because it is a pain to test for and a pain to talk about, the property is defined with the valueundefined. More reasonable in unhappy path cases like this one.)shadowspawn commentedon Jan 17, 2022
(Treating
--foo=alike implicitwithValue.)My concern with that approach when I came across it in the implementation was it has thrown away information. This seems inconsistent with "bring your own validation".
shadowspawn commentedon Jan 17, 2022
I should say explicitly that I am not heavily invested in the current mutually exclusive
flags/values! (I came in expecting a single options result with strings for options with values and true for boolean flags. The XORflags/valueswas influenced by that more common pattern.)I am fine with the
seen/any-valueconcept in theory. However, I am questioning whether it preserves any additional information. If there is no extra information, the pattern has to stand on its other merits.Part of the point of my refactor in PR #26 was to make it easy to adjust the behaviour, and easy to reuse the behaviour when adding short options.
14 remaining items
ljharb commentedon Jan 27, 2022
I’m confused; with zero-config, why would foo ever give true if there’s an equals sign present?
shadowspawn commentedon Jan 27, 2022
Because I am using the convention from Darcy's initial spec that one of the results holds whether an option was used on the command line at all, either as a flag or with a value. So in this example the
optionsresult records the existence of the option in the input args. (Unfortunately, having multiple conventions in flight is making it hard to talk about anything clearly!) See #38 for longer discussion. I'll add a line clarifying whatoptionsis.ljharb commentedon Jan 27, 2022
That makes sense, thanks - so we have "options", "values", and "flags"?
shadowspawn commentedon Jan 27, 2022
Those are the terms I am using, yes. (Or are you asking if
parseArgsreturns all three?)ljharb commentedon Jan 27, 2022
I'm asking if those are the three potential properties of that object.
shadowspawn commentedon Jan 27, 2022
The returned object will have just two of those properties, depending on which we implement:
(I'm being very careful as I am not sure I understand your question and not sure if yes or no is the answer! Ask again if my answer is still not useful, we might be at cross-purposes.)
ljharb commentedon Jan 27, 2022
If I configure one flag, one value, and three arguments are passed, I would expect "options" to contain all three, "flags" to contain one or two, and "values" to contain one or two. No?
shadowspawn commentedon Jan 27, 2022
Short version: no, or at least it depends on the arguments.
(If you give me an example call, I'll lay out the two result flavours I have in mind.)
ljharb commentedon Jan 27, 2022
node index.js --foo --bar --baz, where foo is defined as a flag and bar as withValue?shadowspawn commentedon Jan 27, 2022
First clarification, there is currently no ability to define flags. (Which is a blocker for
strict!)Second, I'll reply with a worked example, but have run out of time to make sure I get it right. Will answer later.
ljharb commentedon Jan 27, 2022
If there were such an ability, then would my expectations match? ie,
{ options: { foo: true, bar: true, baz: true }, flags: { foo: true }, values: { foo: undefined, baz: undefined } }?shadowspawn commentedon Jan 28, 2022
Long answer.
In this comment:
optionsrecords whether an option appears in the argsflagsrecords whether an option has been used as a flag in the argsfoo: configured as a flag and used as a flag
bar: configured as withValue but used as a flag
baz: zero-config flag and used as a flag
"options" and "values" (aka "existence")
"flags" and "values" (xor, option appears in one or the other)
Side note: I am suggesting we do not store
undefinedin values for flags, and just leave it undefined. Author still writes code likevalues[key] !== undefinedwithout needing to wonder abouthasOwnProperty.ljharb commentedon Jan 28, 2022
That also means
Object.keys(values)lacks useful information."needing to wonder about
hasOwnProperty" is also "is able to differentiate between absent and undefined", which is a pretty important ability.shadowspawn commentedon Jan 28, 2022
Yes indeed,
valuesis not the sole source of truth. But the questions I considered can easily be answered with the information available.(I started out working through examples testing for explicit
undefinedand was not happy with the experience. Then I had the realisation that "looks like a flag, store like a flag" reduces and simplifies the possible results. Still championing that concept!)Leaving aside
multiplesfor now, an option is either used as a flag or with a value. The author know which they intended. The results show what the end-user did.Have a look at the uses cases I run through in #38 to see if I missed something important.