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

Feature:(issue_269) Allow external package flag definitions #1540

Merged
merged 5 commits into from Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions app.go
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"time"
)

Expand Down Expand Up @@ -195,6 +196,14 @@ func (a *App) Setup() {
a.ErrWriter = os.Stderr
}

// add global flags added by other packages
flag.VisitAll(func(f *flag.Flag) {
// skip test flags
if !strings.HasPrefix(f.Name, "test.") {
a.Flags = append(a.Flags, extFlag{f})
Copy link
Member

Choose a reason for hiding this comment

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

I'm good with specifically ignoring flags with "test." prefix in this implementation 👍🏼 I looked briefly and couldn't find any exported code that could be used to get this prefix from go libraries 🤷🏼

What's the reasoning behind using a extFlag{f} instead of &extFlag{f} and having the extFlag methods use a pointer receiver?

Copy link
Contributor Author

@dearchap dearchap Oct 22, 2022

Choose a reason for hiding this comment

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

We need the test prefix check because otherwise when we run unit tests those flags get added !!!!

Also extFlag seemed better since we are not necessarily modifying any aspect of the underlying flag

Copy link
Member

Choose a reason for hiding this comment

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

@dearchap I'm in favor of filtering out `"test." in general 👍🏼 My thinking was more around if we could read this value from somewhere instead of having it hard-coded here 🤷🏼

As for extFlag, I meant that I wondered if you meant to use the struct and method receivers as structs rather than pointers to structs. In this implementation, there's going to be a lot more implicit copying of structs, IIUC.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@meatballhat do you want it to be defined as an App field or as a library var ?

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

@Dokiys Dokiys Oct 26, 2022

Choose a reason for hiding this comment

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

@meatballhat do you want it to be defined as an App field or as a library var ?

@dearchap Maybe he means use pointer receivers(*extFlag) rather than value receivers(extFlag) implement Flag interface to avoid unnecessary data duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No no that is fine. I meant the "test" prefix.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@meatballhat I updated the extFlag to be pointer receiver.

}
})

var newCommands []*Command

for _, c := range a.Commands {
Expand Down
36 changes: 36 additions & 0 deletions app_test.go
Expand Up @@ -643,6 +643,42 @@ func TestApp_RunDefaultCommandWithFlags(t *testing.T) {
}
}

func TestApp_FlagsFromExtPackage(t *testing.T) {

var someint int
flag.IntVar(&someint, "epflag", 2, "ext package flag usage")

// Based on source code we can reset the global flag parsing this way
defer func() {
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
}()

a := &App{
Flags: []Flag{
&StringFlag{
Name: "carly",
Aliases: []string{"c"},
Required: false,
},
&BoolFlag{
Name: "jimbob",
Aliases: []string{"j"},
Required: false,
Value: true,
},
},
}

err := a.Run([]string{"foo", "-c", "cly", "--epflag", "10"})
if err != nil {
t.Error(err)
}

if someint != 10 {
t.Errorf("Expected 10 got %d for someint", someint)
}
}

func TestApp_Setup_defaultsReader(t *testing.T) {
app := &App{}
app.Setup()
Expand Down
48 changes: 48 additions & 0 deletions flag_ext.go
@@ -0,0 +1,48 @@
package cli

import "flag"

type extFlag struct {
f *flag.Flag
}

func (e extFlag) Apply(fs *flag.FlagSet) error {
fs.Var(e.f.Value, e.f.Name, e.f.Usage)
return nil
}

func (e extFlag) Names() []string {
return []string{e.f.Name}
}

func (e extFlag) IsSet() bool {
return false
}

func (e extFlag) String() string {
return FlagStringer(e)
}

func (e extFlag) IsVisible() bool {
return true
}

func (e extFlag) TakesValue() bool {
return false
}

func (e extFlag) GetUsage() string {
return e.f.Usage
}

func (e extFlag) GetValue() string {
return e.f.Value.String()
}

func (e extFlag) GetDefaultText() string {
return e.f.DefValue
}

func (e extFlag) GetEnvVars() []string {
return nil
}