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

Allow use of alternative FlagSet implementations #56

Open
AdamSLevy opened this issue Mar 3, 2020 · 16 comments
Open

Allow use of alternative FlagSet implementations #56

AdamSLevy opened this issue Mar 3, 2020 · 16 comments

Comments

@AdamSLevy
Copy link

Alternative FlagSet implementations such as https://github.com/spf13/pflag allow for different styles of flags.

If ff and ffcli used a minimal interface with only the functions they call, in place of flag.FlagSet, this would allow users to use the FlagSet implementation of their choosing.

@AdamSLevy
Copy link
Author

This is a non-goal according to the README.

@peterbourgon
Copy link
Owner

No promises, but I would be interested to see what a PR looked like.

@peterbourgon peterbourgon reopened this Mar 3, 2020
@AdamSLevy
Copy link
Author

AdamSLevy commented Mar 3, 2020

I played with this a bit but found that the use of calls to flag.Flag is limiting. This is tricky to work around and I have a feeling you wouldn't want it in your repo.

The cleanest way to do this in Go is to use an adapter type that implements the same functions with an interface{} instead of *flag.Flag.

Something like

type flagSetAdapter {
         std *flag.FlagSet
         psx *pflag.FlagSet
}

func newFlagSetAdapter(flg interface{}) (flagSetAdapter, error) {
        switch flg := flg.(type) {
        case *flag.FlagSet:
                return flagSetAdapter{flg,nil}
// etc...
        }
}

This gets tricky when it comes to implementing an adapter method for Visit(fn func(*flag.Flag) and the like. In this case ff needs to implement two identical versions of any fn passed to Visit: one using flag.Flag and one using pflag.Flag. That also limits this to only being compatible with the specific flag libraries that it has fn implementations for. So we're basically up against the wall w.r.t. Go's typing system.

@ebati
Copy link

ebati commented May 3, 2020

I think a minimal set of used FlagSet methods are as follows:

type FlagSet interface {
	Parse([]string) error
	Visit(fn func(name string))
	VisitAll(fn func(name string))
	Set(name string, value string) error
	Lookup(name string) (value string, ok bool)
}

Those can be easily implemented for both flag and pflag. For example:

type stdFS struct {
	base *flag.FlagSet
}

func (fs stdFS) Parse(arguments []string) error {
	return fs.base.Parse(arguments)
}

func (fs stdFS) Set(name, value string) error {
	return fs.base.Set(name, value)
}

func (fs stdFS) Visit(fn func(name string)) {
	fs.base.Visit(func(f *stdflag.Flag) {
		fn(f.Name)
	})
}
func (fs stdFS) VisitAll(fn func(name string)) {
	fs.base.VisitAll(func(f *stdflag.Flag) {
		fn(f.Name)
	})
}

func (fs stdFS) Lookup(s string) (value string, ok bool) {
	f := fs.base.Lookup(s)
	if f != nil {
		return f.Value.String(), true
	}
	return "", false
}

If this seems reasonable, I can work on a PR.

@peterbourgon
Copy link
Owner

peterbourgon commented May 3, 2020

I haven't looked into it deeply, but I also think it ought to be feasible. If you want to do a preliminary PR I'd be happy to give it a review. It would be important that the existing API, using the stdlib flag.FlagSet, not change. That is, if you want to change

func Parse(fs *flag.FlagSet, ...)

to

func Parse(fs SomeInterface, ...)

it would be important that the flag.FlagSet satisfy that interface without an adapter.

@ebati
Copy link

ebati commented May 4, 2020

I have two different approaches and I am not sure which one is better so I sent two PRs.

One(#63) with a FlagSet interface that does not use flag.Flag, so we need to define adapter for both flag.FlagSet and pflag.FlagSet.

Another one(#62) defines FlagSet with the same function signatures as the flag.Flag so only pflag.FlagSet needs an adapter.

@peterbourgon
Copy link
Owner

This is cool. I prefer #62, can you run with that?

@ebati
Copy link

ebati commented May 5, 2020

Ok, I will work on #62. I need to ask one more question.

Since Lookup and Visit* functions provide *flag.Flag but we copy values from pFlag.Flag, so modifying flag returned from those functions will not have any effect. Is that ok? In the current implementation, we do only read f.Name and f.Value.String().

@peterbourgon
Copy link
Owner

Do whatever you need to get a working prototype, then we can assess next steps if anything needs to change. I noticed pflag has some "FromGoFlag" adapters, maybe we could PR pflag to expose some of that if necessary.

@AdamSLevy
Copy link
Author

Just FYI the "official" upstream pflag repo is notoriously slow at merging PRs or addressing issues.

@peterbourgon peterbourgon self-assigned this Nov 25, 2020
@peterbourgon
Copy link
Owner

peterbourgon commented Nov 25, 2020

Looking at this again, I'm reasonably optimistic that there exists a FlagSet abstraction which would be satisfied by the stdlib flag.FlagSet via an adapter, and also permit other implementations like pflag. Stay tuned...

@collinforsyth
Copy link

Hi @peterbourgon,

I can take a stab at this feature if you'd be okay with that, but was curious what your thoughts were.

I see from prior work #64 was creating a new wrapper around the pflag library, but it was closed with the following comment

I may still do this, but by another approach.

I wanted to align directionally with what you were thinking before I attempted. Any high level direction of what you are looking for?

@peterbourgon
Copy link
Owner

So the main thing is that this module must not depend on pflag — I'll happily look at anything that satisfies that constraint. I had some thoughts about how to do that, abstracting over the standard library and pflag flag sets with generalized interfaces or perhaps specialized types, but they didn't quite pan out.

@peterbourgon peterbourgon removed their assignment Mar 29, 2022
@collinforsyth
Copy link

Out of curiosity, why is the dependency on pflag such a strong objection? Without being able to do that I think that this feature given the current flags API would be prohibitively difficult without abusing some reflection.

@peterbourgon
Copy link
Owner

ff simply shouldn't have a dependency relationship to pflag — or any other third-party package, really. It should ideally remain ignorant of anything outside of the stdlib. The current dependencies on the YAML and TOML packages I consider bugs that I'd like to try to fix in the future.

@peterbourgon
Copy link
Owner

I hope this is addressed by #113.

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

No branches or pull requests

4 participants