-
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
Error on unknown options and arguments if specified #51
Error on unknown options and arguments if specified #51
Conversation
@elliot-nelson Wow, thanks for submitting this! I really appreciate you diving into the codebase to figure this out and for the detailed explanation - it really makes my job easier. Offhand, this looks great, but I'd like to devote a little bit of time to a proper review, and I won't be able to do that until later this evening or perhaps tomorrow. In the meantime, if you want to take a stab at some tests, that would be fantastic. I have every intention of getting this merged and released. If you don't hear back with a review by Thursday morning (3/19/2020), please ping me here and I'll do my best to follow up. Since this stuff is fresh on my mind though, I'm gonna try to get you a review sooner rather than later. Thanks again! |
Well, in fact, I have done more testing and discovered a flaw -- I didn't notice at first that positional arguments don't end up "claimed" in the slurped args. So, any command that takes positional args incorrectly triggers the error. That might be some kind of internal bug, but rather than hunt it down, I'll instead suggest my original idea -- which might be simpler. Before I came up with this approach, I had actually just made a tiny tweak to
This actually hews closer to the "if you can see it on usage, it's allowed" direction, and it correctly counts all positional arguments described in DSL strings (like (If you go this direction, the only thing that has to change is my little slurp detection, which can become The bummer about the above is that you don't have the raw string, and for display purposes it would be preferable to print what the user actually typed (e.g. |
Good point. I like the approach of making this a part of the For the raw value, each object in |
I think so - I pushed up an extra commit to show a working example with tests.
|
Cool! There are sywac internals, but nothing is really "hidden" in terms of the API, so I'm good with exposing some view of slurped objects. FYI I'm linting the codebase via |
Just a comment, if I remember correctly from my initial thrashing around 😆, it's hard to do this "inline" because at each level, options for the next level are considered unknown. (That is, options configured in the Running into that repeatedly is how I ended up with this strategy of waiting until we're about to |
That's true, it does make it more difficult, but the risk of doing it once at the top level after all
|
7829357
to
13c821a
Compare
I've pushed up a new version of this PR that I think is even closer to what I'd like to see. It uses the same basic premise, which is that it detects the difference between known/unknown arguments while inside parseFromContext, and then it reports the errors in the furthest nested parse (for commands) or immediately once it returns to the root api (if there are no commands). Improvements here:
My testing so far shows behavior like what I'd expect from yargs, which is a bonus. As implemented, command |
Oh and one extra note - I know there is already a concept of "strict" in the types. Although it's always tricky to overload a word I think they are separate enough not to be confused (strictness of a type's behavior vs strict defined at Api level). |
@elliot-nelson Awesome, thanks! Just want to let you know that I have not forgotten about this. I'm planning on reviewing this (and your docs PR) later this evening or tomorrow. Thank you for your patience and helpful contributions! They are very much appreciated! |
@elliot-nelson This is fantastic, nice work! I noticed a slight "conflict of interest" 😁 with a In general, I am ok with this incompatibility, since they seem like two different approaches to the same problem, but I think Fortunately, I have a suggestion for code changes that will allow The resulting behavior is that help content is displayed until a runnable command is specified, at which point strict mode kicks in and errors on any unknown arguments or options at the innermost command that was tried. Hopefully that strikes a good balance for CLIs that are responsive/forgiving for unknown/unspecified commands and yet strict for known/specified commands. I'll post my suggestions in a review soon. |
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.
Overall, your approach is great! I just have some minor tweaks to support a couple more scenarios.
Let me know what you think. Thanks!
Thanks for the detailed feedback!
I also realized after looking at your example that if a user does want "all of my commands behave strictly but then I have a fall-back that is not strict", you can do that: sywac
.strict()
.command(require('command1'))
.command(require('command2'))
.command(require('command3'))
.command({
aliases: '*',
setup: sywac => {
sywac.strict(false)
},
run: argv => {
console.log(`You tried to ${argv._[0]}, here's a bunny instead!`)
}
}) This could be documented in sywac-website, or possibly we could hack the |
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.
Slightly tweaked your suggestion on
newChild
(it now takes an optional options hash to support future options, but has the same effect as your example).
Good call!
possibly we could hack the
types/Command
constructor to forcestrictMode
to false if the alias matches*
. (I say this because I can't imagine any possible scenario where an explicit*
command is useful with strict mode on: it's impossible for it to run. So in that case forcing it to false and documenting that strict mode isn't supported by the default command fallback could make sense.)
I like this idea, but can we experiment with that in a follow-up PR? I'd like to go ahead and merge this as is.
We should also document .strict()
mode in sywac-website on the sync-config page. It should have a <sup>Since 1.3.0</sup>
note since I plan to release this as version 1.3.0 soon.
SUMMARY
Allow program to specify that unknown arguments and options should result in an error.
DETAILS
My approach here is to use (assume?) that any slurped arguments that aren't claimed are unknown arguments. The check is made either (1) right before we call the run handler on the innermost command, or (2) when we're ready to return from
parse
, if no command was run.I copied commander's interface here and used
allowUnknown(true/false)
, although of course the default is true instead of commander's default of false. The boolean could easily be flipped and calledpreventUnknown
orerrorOnUnknown
instead.In my testing on my local copy, this approach seems to give the desired nesting behavior (you can specify
.allowUnknown(false)
in your top-level CLI as you define all your commands and get it everywhere, or you can specify it in thesetup
of just one command, if you don't want the behavior everywhere).TESTS
I didn't add any unit tests yet, but am happy to if this approach looks reasonable and you'd want to merge it!