-
Notifications
You must be signed in to change notification settings - Fork 118
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
feature: add configuration option "collect-unknown-options" #181
Conversation
@bcoe I will need some days for my code review. On my first glance the documentation is missing.
I'm not sure wether it is a good idea to use property |
@juergba I can work on documentation, but the whole point of this feature is to treat unknown options like regular arguments. If it doesn't do that, then the whole change is pointless for my needs. Perhaps the name should be updated? The idea of this mode is to treat unknown options like regular arguments, so I'm pretty sure If you look at the issue I filed that this PR fixes, yargs/yargs#1243, you'll see that my intention is to allow unknown options to appear in a varargs of a command, so unless |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's risky to test only with number
s. array
s are luckily not affected by this PR, since hasAnyFlag()
should be truthy. But boolean
s can be very annoying ...
Did you check side-effects with Yargs#strict()
and configurations like populate--
, short-option-groups
and halt-at-non-option
?
Travis error TypeError: Object.values is not a function
: maybe support of Node 6 should be dropped, otherwise search for "// XXX Switch to [].concat(...Object.values(aliases)) once node.js 6 is dropped" within index.js.
index.js
Outdated
@@ -26,7 +26,8 @@ function parse (args, opts) { | |||
'set-placeholder-key': false, | |||
'halt-at-non-option': false, | |||
'strip-aliased': false, | |||
'strip-dashed': false | |||
'strip-dashed': false, | |||
'ignore-unknown-options': false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parse-unknown-options: true
instead of ignore-unknown-options': false
could be an alternative naming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm definitely open to naming suggestions. If you think parse-unknown-options
is clearer, then I'd be happy to go with that. If you have other suggestions, I'm open to those too.
index.js
Outdated
setArg(m[1], m[2]) | ||
} else { | ||
argv._.push(maybeCoerceNumber('_', arg)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an unknown flag is parsed as positional argument, so why processing its value?
Why processing only number
s, not boolean
s?
I think the value should not be processed at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I'll remove the number processing.
index.js
Outdated
} | ||
} | ||
if (unmatched.length > 0) { | ||
argv._.push(maybeCoerceNumber('_', ['-', ...unmatched].join(''))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the values are already processed, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point. I'll remove the number parsing here too.
index.js
Outdated
setArg(key, value) | ||
} else { | ||
unmatched.push(key) | ||
unmatched.push('=') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why with "="?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise the =
is lost, that's why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this a bit odd too because -f a b c
is a valid parse. tldr; you end up with an =
that didn't exist.
Node 6 support fixed (didn't realize I'll plan to expand tests over the weekend if I get to it. |
@henderea @juergba perhaps we can take a step back here, why do you want this behavior? Tools like nyc and mocha have similar needs, in which they want to be able to run a command where some options are intended for the application, and some are intended for the program being instrumented, e.g.,
We've introduced the configuration [halt-on-non-option(https://github.com/yargs/yargs-parser#halt-at-non-option) to support this use-case, which I think is pretty close to what you're trying to do? |
@henderea just bumping up this conversation, was wondering if The behavior of stopping parsing once you hit the first non |
@bcoe: sorry, haven't gotten around to expanding the test suite yet As for Does |
Also, related to The tool I want this for is something I'm converting from ruby, and the ruby library I use for command and option handling by default behaves like my I'll try to get some additional tests written, but I think my needs will only be fully handled by something similar to this pull request. |
For example, I want to have the following definitions:
I want to be able to run
but have
As a note, the |
@bcoe: If possible, I would like for this feature or something else that will treat unknown options as regular arguments to be added to yargs. It looks like the When I write a script, I will define EVERY option that I want to handle, and anything I don't define shouldn't be treated as an option. The ruby library I use for argument parsing (thor) does this by default, and I personally feel that it is rather odd to set options that haven't been defined. The way I see it, my script should only support options that have been defined, and should fail on undefined options, but only if they don't fit into an argument or varargs. |
@henderea 👋 thanks for filling me in regarding your use case, I'll try to get back to you with a response soonish 😄 when I can give this my full attention. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommendations
Simplifying Algorithm
I think you could simplify the logic quite a bit, and make a few less code changes, if you moved the check to the very top of the parser, here:
Line 147 in 83de6f7
if (arg.match(/^--.+=/) || ( |
Do something like:
// just below the argv definition.
if (configuration['collect-unknown-options']) {
argv.unknownOptions = []
}
// ...
if (configuration['collect-unknown-options'] && hasUnknownOption(arg, args[i + 1])) {
argv.unknownOptions.push(arg)
} else if (arg.match(/^--.+=/) || (
!configuration['short-option-groups'] && arg.match(/^-.+=/)
)) {
// do things with argument.
} else {
// etc.
}
☝️ hasUnknownOption
would just need to drop handle:
-xyz
: ensuring thatx
,y
, andz
are all configured.--foo=bar
: extracting just thefoo
and checking that it's configured.--foo
: extracting just thefoo
and making sure it's configured.
Naming things
I agree with @juergba, I would introduce a new key on argv
called unknownOptions
, which for your use case could just be combined with _
in your application.
I also like the name collect-unknown-options
, as the config setting.
Apologies!
Sorry that it took me so long to get back to you with review; my OSS time has been spread thin these days unfortunately, and I wanted to take the time to actually delve into this feature.
I'm feeling like, if we can keep the footprint as small as the new algorithm I described in this PR, I'm happy to land this feature (@juergba how does the algorithm I described sound to you?).
index.js
Outdated
setArg(key, value) | ||
} else { | ||
unmatched.push(key) | ||
unmatched.push('=') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this a bit odd too because -f a b c
is a valid parse. tldr; you end up with an =
that didn't exist.
Meanwhile I have changed my mind on this:
We could end up with spreading our unknown arguments over two different properties.
This list is not complete, the extraction of the plain key out of the raw argument is more complex than this. E.g. boolean negation, dot anotation are missing. We would have to copy the extraction logic into |
@juergba I'd like to avoid adding conditional logic throughout the parser, to handle the _.push(key)
_.push('=') we could just continue to enumerate some of these other cases:
☝️ it's a finite list. |
Well, I don't necessarily have an issue with simplifying the logic if it still does the same thing, but putting the unknown options into a separate property and merging them could alter the order of arguments, which would not be desirable. If there is a way to rewrite the logic to get the same effect with less-invasive changes, I'm all for that. I just want to be sure it still works the same way, or at least that I can get the same result through some method. Altering the order of arguments because they were split between 2 different properties is not desireable. |
@henderea I'm open to putting the options in |
@henderea just following up 👍 to summarize:
|
@bcoe Sure, I can make the changes. The thing is, I'm not 100% sure of the form those changes would take. How would I detect that a key should be skipped before parsing the actual key? If you could give me a bit more direction on that, I could make the changes. |
@henderea I can try to pseudo code something, but my thinking was you only have a few forms of keys:
☝️ so have a little helper function that runs before the rest of our checks and extracts the |
@bcoe I've made the requested changes. I ended up creating a function that takes |
I realized after my first implementation of the updated code that it wasn't checking short flags individually, so I updated my code to handle that, and also added in the check for negative numbers that I saw in the short flags handling, along with renaming |
@henderea thanks for taking this on 👍 will provide review soon. |
Fixes yargs/yargs#1243
All new code is covered in tests. It shouldn't make any difference, but I used the Wallaby.js VSCode extension for test running and coverage, instead of the npm scripts.
I tried to match the existing code style as well as possible, but please feel free to fix any issues I created.