Skip to content
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

Add a method to expose unknown flags back to the user #199

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ijc
Copy link

@ijc ijc commented Jan 25, 2019

Currently it is possible to ignore unknown flags (with
FlagSet.ParseErrorsWhitelist.UnknownFlags) but there is no way to find out
what they were after the fact.

Add a method which registers a slice into which all unknown arguments will be
accumulated.

Note that unknown short arguments which appear in a group (e.g. -uuu) will be
exploded (e.g. into -u -u -u) in this slice. Also any following value is
associated with the final option, e.g. ["-uuu", "foo"] is interpreted as
["-u", "-u", "-u", "foo"]. This is consistent with the statement:

Single dashes signify a series of shorthand letters for flags. All but the
last shorthand letter must be boolean flags.

This is the reason why the call to stripUnknownFlagValue in
parseSingleShortArg becomes conditional on this being the last short arg in
the batch.

Add code to the existing TestIgnoreUnknownFlags (which already has
comprehensive set of the varities of possible unknown flags) to check this.

Also fixed a small cut-and-paste-o in the failure message of the test (use of
f.ParseAll).

Signed-off-by: Ian Campbell [email protected]

For my use I'm actually only interested in the first unknown argument but this seemed like a more flexible approach which might be more widely usable. (I could probably get away with a flag "there were unknown args", although that wouldn't be so nice).

Currently it is possible to ignore unknown flags (with
`FlagSet.ParseErrorsWhitelist.UnknownFlags`) but there is no way to find out
what they were after the fact.

Add a method which registers a slice into which all unknown arguments will be
accumulated.

Note that unknown short arguments which appear in a group (e.g. `-uuu`) will be
exploded (e.g. into `-u -u -u`) in this slice. Also any following value is
associated with the final option, e.g. `["-uuu", "foo"]` is interpreted as
`["-u", "-u", "-u", "foo"]`. This is consistent with the statement:

    Single dashes signify a series of shorthand letters for flags. All but the
    last shorthand letter must be boolean flags.

This is the reason why the call to `stripUnknownFlagValue` in
`parseSingleShortArg` becomes conditional on this being the last short arg in
the batch.

Add code to the existing `TestIgnoreUnknownFlags` (which already has
comprehensive set of the varities of possible unknown flags) to check this.

Also fixed a small cut-and-paste-o in the failure message of the test (use of
`f.ParseAll`).

Signed-off-by: Ian Campbell <[email protected]>
@ijc
Copy link
Author

ijc commented Mar 8, 2019

My immediate need for this became obsolete with docker/cli#1722 so feel free to close if there is no interest in this.

@bored-engineer
Copy link

bored-engineer commented Dec 4, 2019

@ijc I have some interest in this still, since the request was approved by @seemethere already can we merge this?

@ijc
Copy link
Author

ijc commented Dec 4, 2019

I've moved onto other employment but I have no objections. I don't have merge privileges myself.

@bored-engineer
Copy link

@spf13 bump, can we get this merged or some review on what changes are needed?

@bored-engineer
Copy link

@spf13 Bump, it seems like this is ready to merge

@davidovich
Copy link

This is a really simple, and to my understanding, a missing piece to the unknown flags mechanism. This would easily fix spf13/cobra#739.

@jpmcb
Copy link

jpmcb commented Aug 21, 2020

+1 - I think this would help the cobra community alot!
@eparis @therealmitchconnors @bep any chance we could get this looked at? Thanks much!

// SetUnknownFlagsSlice arranges to append any unknown flags to the
// given slice. This requires ParseErrorsWhitelist.UnknownFlags to be
// set so that parsing does not abort on the first unknown flag.
func (f *FlagSet) SetUnknownFlagsSlice(s *[]string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm debating if I like this API or if we should just always store them in th flagset and the user call GetUnknownFlagSlice() to fetch from our internal storage. I feel ike 51/49 towards using GetUnknownFlagSlice(). But why did you go this way?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid I don't recall what I was thinking last week, let alone 20 months ago ;-)

My motivation for needing this ended up being going away due to other changes needed in the consuming code base and I've also since change jobs. I'm happy if someone else wants to carry this change though.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This response sums up my experience trying to contribute to this repo. I've waited two years for review. That's insane.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cobra community would like this (or something like this) so that we can have an api access to unknown flags. Ether way would be fine as long as we can get them!

Copy link

@cornfeedhobo cornfeedhobo Aug 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm finally merging down a few of these PRs into my fork and I'll update the readme to explain how to override the dependency when using cobra.

Edit: I've published my fork and will be working backwards through old PRs in the coming days, then I'll get to this one. It'd be great to get this into cobra or finally get this repo some love.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jpmcb Okay. I've made some changes to the way this was implemented and merged it into my fork. I hope that suits your needs, but feel free to open any issues and I'll jump on them.

@ijc Thanks for putting this together and making it easy!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting a fork together. However, I think it would be an anti-goal of the cobra project to fracture viper, pflags, and cobra by introducing a fork as a core dependency. Cobra is really just a wrapper for pflags and viper. So ideally, I'd like if this could get some love and be merged

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jpmcb Yeah, I'd like that too, but I don't want to wait another 2 years for a response. No one has an obligation to use my fork, but I'm movin' on :)

cornfeedhobo added a commit to cornfeedhobo/pflag that referenced this pull request Aug 29, 2020
cornfeedhobo added a commit to cornfeedhobo/pflag that referenced this pull request Aug 29, 2020
@jpmcb
Copy link

jpmcb commented Oct 1, 2020

Any idea when this could be merged? @eparis @therealmitchconnors @bep

@underrun
Copy link

I'm debating if I like this API or if we should just always store them in th flagset and the user call GetUnknownFlagSlice() to fetch from our internal storage. I feel ike 51/49 towards using GetUnknownFlagSlice(). But why did you go this way?

so a justification for not always storing them is that current behavior is to fail fast on the first unknown flag.

it's true doing this "the other way" would still allow failing if anything wasn't parsed but it would fail after parsing the unknown flags - which is a behavior change that may have unexpected impact.

other arguments for merging this PR are that at 51/49 preference isn't a large preference, this code is here and ready to go, and cobra is waiting on it :-)

any thoughts on if we can get this merged in soon?

"--unknown7=unknown7value",
"--unknown8=unknown8value",
"--unknown6", "",
"-u", "-u", "-u", "-u", "-u", "",
Copy link

@cornfeedhobo cornfeedhobo Mar 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this is brought up in the PR description, but I keep thinking about this line. If u is an unknown shorthand, everything following it could be a value, which matches the support syntax in the readme

// mixed
-abcs "hello"
-absd="hello"
-abcs1234

It doesn't seem right to assume they are concatenated shorthands.

You can see a working gist here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly disagree that concatenating short flags and values should be supported by anything. It is very unexpected to me that this should work where valid short flags are t and o and the expected value for o is foobar

$ go run main.go -tofoobar

This should fail to work both because it's super confusing and terrible design and because it adds a high level of complexity at the flag parsing layer to support both this feature and unknown short flags.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@underrun well, it's a pretty old pattern, so I think you'll have to take that up with history. The fact remains that it's a syntax currently supported, and the preservation of unknown flags should be as naive as possible so that the resulting slice can be manually passed to Parse.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't see how having unknown flags can work while allowing concatenated short flags and values though. it feels like it's reasonable to disallow this if unknown flags are requred. no?

Copy link

@cornfeedhobo cornfeedhobo Apr 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@underrun I'm not sure I fully follow, but you did help me stumble on another bug with this implementation; if the unknown flag has a value, this current implementation will swallow the value and consider it an argument, despite us having a test for this 🤔 .

Works: -Dfoobar
Doesn't work: -D foobar, --stringVal foobar

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@underrun I think I better understand your comment now. Essentially this comes down to an inability to surface values that are separated by a space, because they are indistinguishable from arguments, and there is no way to determine if it's associated to an unknown flag type. A best effort is all that can be done, and that is achieved if the user includes = when assigning a value to an unknown.

@marckhouzam
Copy link

Would still be great for Cobra
/cc @eparis

@cornfeedhobo
Copy link

@underrun Your concern is covered by the whitelisting option

@ijc As pointed out in my review, I think this PR breaks expected behavior

cc; @marckhouzam @jpmcb

@ijc
Copy link
Author

ijc commented Apr 8, 2021

@ijc As pointed out in my review, I think this PR breaks expected behavior

@cornfeedhobo Thanks but please see #199 (comment): I'm not planning to do anything further with this PR at this point (maybe I should just close it?). If it isn't going to be merged as is then someone else will have to take it over and address the review.

@cornfeedhobo
Copy link

@ijc My apologies, I totally forgot about that comment. Thanks for the prompt reply. Cheers!

@underrun
Copy link

underrun commented Apr 8, 2021

@cornfeedhobo my full concern as laid out in the related cobra issue combined with my commend a month ago is not covered by whitelisting - or I don't think it is. Maybe you could explain how?

@ijc - i'm willing to take this over if there are changes that could get it merged

@cornfeedhobo
Copy link

cornfeedhobo commented Apr 8, 2021

@underrun I modified this PR a little before merging it into my fork, so maybe you're right, but I'm pretty sure this is covered in this PR. Either way, you can look at my commit history and pull out the relevant changes if you are holding out hope that this will be reviewed by the repo maintainers. It specifically checks for the whitelist condition.

@eparis
Copy link
Collaborator

eparis commented Apr 8, 2021

The description says that -uuu foo is somehow treated specially. I'd have to look at things more closely. But if the intention of the user was the flag -u and the value uu, with a separate unrelated argument foo, all of that context would be lost by anything trying to look at unparsed flags right. So it would be impossible for the code handling these unknown flags to correctly handle things.

Instead of using the arbitrary example above let's assume what the user typed was -cblue draw. And this was supposed to mean to something handling unknown flags that the color is supposed to be blue and we're supposed to draw that color. Breaking this into a slice with six different entries seems problematic. You have no idea if the user typed -c -b -l -u -e draw, or what was actually typed. Does this ambiguity not seem problematic?

@underrun
Copy link

underrun commented Apr 9, 2021

Instead of using the arbitrary example above let's assume what the user typed was -cblue draw. And this was supposed to mean to something handling unknown flags that the color is supposed to be blue and we're supposed to draw that color. Breaking this into a slice with six different entries seems problematic. You have no idea if the user typed -c -b -l -u -e draw, or what was actually typed. Does this ambiguity not seem problematic?

personally i think it's reasonable that if we are combing unknown argument parsing with allowance for concatenated short flags to disallow concatenating the argument of the last short flag with the list of short flags.

but i think it'd also be possible to stop treating data as unknown short flags after hitting a short flag that is non-boolean?

@cornfeedhobo
Copy link

cornfeedhobo commented Apr 21, 2021

This just got a little more interesting. I decided to write out some test cases, and the first one I wrote seems to uncover a bug, if someone would like to verify:

	testCases := []struct {
		args         []string
		wantFlags    []string
		wantArgs     []string
		wantUnknowns []string
	}{
		// Known flags
		//   StringP("name", "n", ...)
		//   BoolP("enabled", "E", ...)
		// Unknown flags
		//   StringP("group", "g", ...)
		//   BoolP("hidden", "H", ...)
		{
			args:         []string{"--name", "foo", "arg1", "--group", "bar", "arg2", "--enabled", "arg3", "--hidden", "arg4"},
			wantFlags:    []string{"--name", "foo", "--enabled", "true"},
			wantUnknowns: []string{"--group", "bar", "--hidden"},
			wantArgs:     []string{"arg1", "arg2", "arg3", "arg4"},
		},
	}
    flag_test.go:674: Got:  [arg1 arg2 arg3]
    flag_test.go:675: Want: [arg1 arg2 arg3 arg4]

@underrun
Copy link

is that a bug or is that like ... impossible to differentiate with unknown flags where unknown flags can be either boolean or non-boolean?

would this be fixed by disallowing both boolean and non-boolean unknown flags to be handled at the same time and giving users the choice to parse boolean unknown flags or non-boolean unknown flags? I don't like this option but maybe?

another option would be to disallow boolean unknown flags except for the final flag (which mirrors short flag handling).

@cornfeedhobo
Copy link

cornfeedhobo commented Apr 21, 2021

would this be fixed by disallowing both boolean and non-boolean unknown flags to be handled at the same time and giving users the choice to parse boolean unknown flags or non-boolean unknown flags? I don't like this option but maybe?

@underrun Exactly. That is where this breaks down - do we assume they are values when no = is used, or do we assume they are arguments, or do we leave that to the developer to toggle through a setting(s)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants