Extend UnknownFlags to UnknownFlagsHandling and introduce PassUnknownFlagToArgs (#337)#440
Conversation
| if f.Parsed() { | ||
| t.Fatal("f.Parse() = true before Parse") | ||
| } | ||
| f.ParseErrorsAllowlist.UnknownFlagsHandling = UnknownFlagsHandlingPassUnknownToArgs |
There was a problem hiding this comment.
This test code is same to https://github.com/spf13/pflag/pull/338/files#diff-7a7385a10188cdaa9ac18effc375607e65672f55084b8049b6d3f44a6ab70c51 except this line.
| // UnknownFlagsHandlingErrorOnUnknown will return an error if an unknown flag is found | ||
| UnknownFlagsHandlingErrorOnUnknown UnknownFlagsHandling = iota | ||
| // UnknownFlagsHandlingIgnoreUnknown will ignore unknown flags and continue parsing rest of the flags | ||
| UnknownFlagsHandlingIgnoreUnknown | ||
| // UnknownFlagsHandlingPassUnknownToArgs will treat unknown flags as non-flag arguments. | ||
| // Combined shorthand flags mixed with known ones and unknown ones results | ||
| // combined flags only with unknown ones. | ||
| // E.g. -fghi results -gh if only `f` and `i` are known. | ||
| UnknownFlagsHandlingPassUnknownToArgs |
There was a problem hiding this comment.
Reeeally long names...
I worry names like ErrorOnUnknown would cause name conflict.
There was a problem hiding this comment.
Maybe ErrorOnUnknownFlag? Along with IgnoreUnknownFlag and PassUnknownFlagToArgs. I don't think they can be made shorter than that without losing meaning, but I don't think we need to be overly cautious about naming conflicts here.
If you really want to guard against it, another variant is to namespace these somehow - either with a struct value where the enum values are fields on the struct, or by just moving them to an appropriately named subpackage.
tomasaschan
left a comment
There was a problem hiding this comment.
I like this version a lot better than #338.
(The passing-error-as-control-flow thing smells a little, but the parseLongArg method was a mess before this change, so that's not your fault... Something to look out for in 2.0, I guess.)
Needs a rebase (I haven't looked into why) and probably shouldn't be merged until we've decided we don't have anything else we want to get into 1.0 before cutting 1.1.
| // UnknownFlagsHandlingErrorOnUnknown will return an error if an unknown flag is found | ||
| UnknownFlagsHandlingErrorOnUnknown UnknownFlagsHandling = iota | ||
| // UnknownFlagsHandlingIgnoreUnknown will ignore unknown flags and continue parsing rest of the flags | ||
| UnknownFlagsHandlingIgnoreUnknown | ||
| // UnknownFlagsHandlingPassUnknownToArgs will treat unknown flags as non-flag arguments. | ||
| // Combined shorthand flags mixed with known ones and unknown ones results | ||
| // combined flags only with unknown ones. | ||
| // E.g. -fghi results -gh if only `f` and `i` are known. | ||
| UnknownFlagsHandlingPassUnknownToArgs |
There was a problem hiding this comment.
Maybe ErrorOnUnknownFlag? Along with IgnoreUnknownFlag and PassUnknownFlagToArgs. I don't think they can be made shorter than that without losing meaning, but I don't think we need to be overly cautious about naming conflicts here.
If you really want to guard against it, another variant is to namespace these somehow - either with a struct value where the enum values are fields on the struct, or by just moving them to an appropriately named subpackage.
| func (a *ParseErrorsAllowlist) getUnknownFlagsHandling() UnknownFlagsHandling { | ||
| // if UnknownFlagsHandling is set, use it | ||
| if a.UnknownFlagsHandling != UnknownFlagsHandlingErrorOnUnknown { | ||
| return a.UnknownFlagsHandling | ||
| } | ||
|
|
||
| if a.UnknownFlags { | ||
| return UnknownFlagsHandlingIgnoreUnknown | ||
| } | ||
| return UnknownFlagsHandlingErrorOnUnknown |
f16d4d7 to
d1d7aee
Compare
* `UnknownFlagsHandlingErrorOnUnknown` to `ErrorOnUnknownFlag` and so on
UnknownFlags to UnknownFlagsHandling and introduce UnknownFlagsHandlingPassUnknownToArgs (#337)UnknownFlags to UnknownFlagsHandling and introduce PassUnknownFlagToArgs (#337)
tomasaschan
left a comment
There was a problem hiding this comment.
I'm sorry this took so long to get to reviewing. I have a couple of questions, but given this behavior is opt-in I think it would be OK to merge this to make it easier to test in real applications, and roll forward when more edge cases pop up.
Let me know if you prefer a merge now or to address those questions first.
| return stripUnknownFlagValue(a), nil | ||
| case unknownFlagsHandling == PassUnknownFlagToArgs: | ||
| return a, &unknownFlagError{ |
There was a problem hiding this comment.
I don't understand why the previous case calls stripUnknownFlagValue and this is one does not.
There was a problem hiding this comment.
We can consider the case of --unknown somevalue.
--unknown is an unknown flag, and we cannot reliably tell whether somevalue is a flag value or a positional argument.
With PassUnknownFlagToArgs, we do not need to classify somevalue; we simply leave the argument stream intact and let the caller receive everything as positional args. So we don't need to call stripUnknownFlagValue().
With IgnoreUnknownFlag, the existing behavior treats the next token as a flag value (when it does not look like a flag) and calls stripUnknownFlagValue() to strip it. This behavior is inherited from the existing IgnoreUnknownFlag implementation.
| if len(shorthands) > 2 && shorthands[1] == '=' { | ||
| outShorts = "" | ||
| err = &unknownFlagError{ | ||
| UnknownFlags: shorthands, | ||
| } | ||
| return | ||
| } | ||
| // '-fgh': pass only the first switch | ||
| err = &unknownFlagError{ | ||
| UnknownFlags: shorthands[0:1], | ||
| } | ||
| return |
There was a problem hiding this comment.
What about -fg=g were both f and g are shorthands, but only g accept a value? Or -fgh (same)?
There was a problem hiding this comment.
Same here, differs in the next call.
For an unknown flag, parseShortArg only need to choose between:
- strip only the first letter, or
- strip all the argument
Both -fg=g and -fgh are the former case: only strips the first letter (f) as an unknown flag, and pass the rest (g=g or gh).
The rest will be handled in the next parseSingleShortArg() call.
In the next parseSingleShortArg(), g=g results the latter case, and gh results the former case.
Close #337
Alternative to #338.
FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling=PassUnknownFlagToArgsbehaves like this:--unknown-flagare unknownThis request deprecates
FlagSet.ParseErrorsAllowlist.UnknownFlagsand extends it toFlagSet.ParseErrorsAllowlist.UnknownFlagsHandling.FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling = ErrorOnUnknownFlag(Default)FlagSet.ParseErrorsAllowlist.UnknownFlags = falseFlagSet.ParseErrorsAllowlist.UnknownFlagsHandling = IgnoreUnknownFlagFlagSet.ParseErrorsAllowlist.UnknownFlags = trueFlagSet.ParseErrorsAllowlist.UnknownFlagsHandling=PassUnknownFlagToArgs:Args()(described above)Backward compatibilities:
FlagSet.ParseErrorsAllowlist.UnknownFlagsHandlingis not set (ErrorOnUnknownFlag) andFlagSet.ParseErrorsAllowlist.UnknownFlags = true:FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling = ErrorOnUnknownFlagParseErrorsWhitelist