-
Notifications
You must be signed in to change notification settings - Fork 10
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
Reporting indexes #84
Comments
i am very interested in enforcing this! |
Playing with this idea (and #70 (comment)):
|
I was thinking about how to implement and use this, and a couple of complications:
|
It's either the author-provided thing or
Hm, yeah, the author would need to write slightly more complicated logic to handle these cases. It's especially annoying with the awful If we want to make this case easy, in the case of short options there could be an additional (I'm not sure if the |
🔨 Moving towards MVP Milestone 1 (#87) I propose post-MVP. |
Rather than returning a structure indexed by parsed options, I am thinking about returning an array for each thing the parser discovers. e.g. option, option-terminator, positional. The option elements might have information like whether the option was specified with a short or long name, whether the option values was embedded or in a separate argument, whether the option was auto-discovered or a known option. @bcoe and @bakkot have previously mentioned AST (Abstract Syntax Tree) as concept. I don't have experience with AST so coming at it from first principles and CLI experience. @darcyclarke is trying something similar in minargs: darcyclarke/minargs@f7f4bd4 |
I think it'll be hard to evaluate the difference between that (index -> option) and the originally proposed inverse of that (option -> indices) without writing them out in a little more detail and seeing what different use cases look like with each style. |
What got me thinking in the array direction was disliking needing two indices to tell the order of short options from a short option group. I am imagining a short option group would generate multiple (flat) elements in the array, and not a tree. I can imagine the current But only in my head. Not written any details. :-) |
Playing with a prototype. I'm not trying to make decisions, so feel free to ignore until after the upstream PR lands!
|
Some thoughts on that prototype:
And here's what the use cases in the OP would look like, given those tweaks:
let { values, positionals, elements } = parseArgs(config);
let getLastIndex = name => elements.findLastIndex(x => x.type === 'option' && x.optionName === name);
if (values.foo && values['no-foo']) {
values.foo = getLastIndex('foo') > getLastIndex('no-foo');
}
let { values, elements } = parseArgs(config);
let dupes = Object.keys(value)
.map(name => elements.filter(e => e.type === 'option' && e.optionName === name))
.filter(e => e.length > 1);
if (dupes.length > 0) {
throw new Error(`duplicate flag${dupes.length > 1 ? 's' : ''} ${dupes.join(', ')}`);
}
let { values, elements } = parseArgs(config);
for (let element of elements) {
if (element.type === 'option' && !element.short && element.valueIndex != null && element.valueIndex > element.argIndex) {
throw new Error(`argument ${element.optionName} must specify its argument as \`--foo=bar\`, not \`--foo bar\``);
}
}
let { values, elements } = parseArgs(config);
let getFirstIndex = name => elements.findIndex(x => x.type === 'option' && x.optionName === name);
let getLastIndex = name => elements.findLastIndex(x => x.type === 'option' && x.optionName === name);
if (values.first && value.second && getFirstIndex('first') > getLastIndex('second')) {
throw new Error(`"first" must be specified before "second"`);
} These are all ok, I guess? I notice a lot of Also I'm a bit worried that people are going to conflate "index in this array" with "index in the original array", which works for |
Yes. It is the thing to use for lookups into the supplied config and returned values.
I was thinking about supplying enough information to display an error message without looking up option in config. But a bit subtle to use, can look it up as you say, and I think a boolean might be clearer.
I actually started with that but tried the index, and it wasn't compelling. I agree,
Yes, single.
I avoided "type" in first instance because we use that in input configuration. Would "elementType" be ok? (Per next suggestion.)
Yes. (I was over-reaching with AST! 😊 ) |
Yeah, I don't think "you don't have to look at the config" should be a goal. It's necessarily on hand, so it's not going to be hard to get to.
Ehhhhh that's gonna be painful to write out every time. |
Updated prototype per suggestions: % node index.js -ab --foo=bar -- --pos
{
values: [Object: null prototype] { a: true, b: true, foo: 'bar' },
positionals: [ '--pos' ],
originalArgs: [ '-ab', '--foo=bar', '--', '--pos' ],
parseElements: [
{
kind: 'option',
optionName: 'a',
short: true,
argIndex: 0,
value: undefined,
inlineValue: undefined
},
{
kind: 'option',
optionName: 'b',
short: true,
argIndex: 0,
value: undefined,
inlineValue: undefined
},
{
kind: 'option',
optionName: 'foo',
short: false,
argIndex: 1,
value: 'bar',
inlineValue: true
},
{ kind: 'option-terminator', argIndex: 2 },
{ kind: 'positional', value: '--pos', argIndex: 3 }
]
} |
@shadowspawn Now that |
I'm still a little worried about the potential for people assuming that Assuming config is
then running
Note that this preserves And just for example, here's what "resolve conflicts between let { values, positionals, elements } = parseArgs(config);
let optElements = elements.flatMap(e => e.options ?? []);
if (values.foo && values['no-foo']) {
values.foo = optElements.lastIndexOf('foo') > optElements.lastIndexOf('no-foo');
} ... ok actually that's not as bad as I thought it was going to be. |
I still prefer
Hmm, I'm not expecting this to be a problem. I'm hopeful either confusion won't occur, or it won't come up in use as working with just elements and not referencing back to args.
I agree the 1:1 is a bit harder to use. (Thanks for working an example.) Edit: albeit |
A minor musing on name of returned
|
I don't think the extra context is useful, personally. There's only one thing it could really be. And odds are you're going to find out about it by reading existing code or documentation in any case, which should make it very clear. Still, I don't feel super strongly. It just feels redundant to me.
Mostly I mention it because I made exactly that mistake when writing up this snippet despite having already known they were different. It's just very easy to think "i want the index" and reach for
Yeah, if we're going to return |
Oh, your experience trumps my speculation! Something to be aware of with naming and documentation.
All fair points. I am not aware of any activity on |
@shadowspawn Having thought about it some more, I think the usability benefits of having at most one option per element probably outweighs the benefits of having the indices line up, so probably we should stick with your original design. People will definitely make the same mistake I did, but I don't see a way to avoid that without taking too large of a hit to usability. Do you want to make a PR? I can put one together at some point if not. |
I would like to make a PR. I can open a draft now if anyone would like code to run against for experimenting. Functional, but some distance from final form (Refactor, primordials, et al.) |
This came up here, but I wanted to split it out to track it explicitly.
I think that reporting the indexes of arguments within
args
would be a helpful addition, because it lets you build a bunch of more complicated logic on top ofparseArgs
without needing to do a full re-parse.For example, this would make it straightforward to:
--foo
and--no-foo
as last-wins--foo=bar
rather than--foo bar
(check each to see ifargs[index].includes('=')
)--enable-experimental-options
before--some-unstable-option
, e.g.)The text was updated successfully, but these errors were encountered: