Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,33 @@ func main() {
}
```

### Using pflag with go test
`pflag` does not parse the shorthand versions of go test's built-in flags (i.e., those starting with `-test.`).
For more context, see issues [#63](https://github.com/spf13/pflag/issues/63) and [#238](https://github.com/spf13/pflag/issues/238) for more details.

For example, if you use pflag in your `TestMain` function and call `pflag.Parse()` after defining your custom flags, running a test like this:
```bash
go test /your/tests -run ^YourTest -v --your-test-pflags
```
will result in the `-v` flag being ignored. This happens because of the way pflag handles flag parsing, skipping over go test's built-in shorthand flags.
To work around this, you can use the `ParseSkippedFlags` function, which ensures that go test's flags are parsed separately using the standard flag package.

**Example**: You want to parse go test flags that are otherwise ignore by `pflag.Parse()`
```go
import (
goflag "flag"
flag "github.com/spf13/pflag"
)

var ip *int = flag.Int("flagname", 1234, "help message for flagname")

func main() {
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
flag.ParseSkippedFlags(os.Args[1:], goflag.CommandLine)
flag.Parse()
}
```

## More info

You can see the full reference documentation of the pflag package
Expand Down
21 changes: 17 additions & 4 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,32 @@ unaffected.
Define flags using flag.String(), Bool(), Int(), etc.

This declares an integer flag, -flagname, stored in the pointer ip, with type *int.

var ip = flag.Int("flagname", 1234, "help message for flagname")

If you like, you can bind the flag to a variable using the Var() functions.

var flagvar int
func init() {
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
}

Or you can create custom flags that satisfy the Value interface (with
pointer receivers) and couple them to flag parsing by

flag.Var(&flagVal, "name", "help message for flagname")

For such flags, the default value is just the initial value of the variable.

After all flags are defined, call

flag.Parse()

to parse the command line into the defined flags.

Flags may then be used directly. If you're using the flags themselves,
they are all pointers; if you bind to variables, they're values.

fmt.Println("ip has value ", *ip)
fmt.Println("flagvar has value ", flagvar)

Expand All @@ -54,22 +63,26 @@ The arguments are indexed from 0 through flag.NArg()-1.
The pflag package also defines some new functions that are not in flag,
that give one-letter shorthands for flags. You can use these by appending
'P' to the name of any function that defines a flag.

var ip = flag.IntP("flagname", "f", 1234, "help message")
var flagvar bool
func init() {
flag.BoolVarP(&flagvar, "boolname", "b", true, "help message")
}
flag.VarP(&flagval, "varname", "v", "help message")

Shorthand letters can be used with single dashes on the command line.
Boolean shorthand flags can be combined with other shorthand flags.

Command line flag syntax:

--flag // boolean flags only
--flag=x

Unlike the flag package, a single dash before an option means something
different than a double dash. Single dashes signify a series of shorthand
letters for flags. All but the last shorthand letter must be boolean flags.

// boolean flags
-f
-abc
Expand Down Expand Up @@ -934,9 +947,9 @@ func (f *FlagSet) usage() {
}
}

//--unknown (args will be empty)
//--unknown --next-flag ... (args will be --next-flag ...)
//--unknown arg ... (args will be arg ...)
// --unknown (args will be empty)
// --unknown --next-flag ... (args will be --next-flag ...)
// --unknown arg ... (args will be arg ...)
func stripUnknownFlagValue(args []string) []string {
if len(args) == 0 {
//--unknown
Expand Down Expand Up @@ -1014,7 +1027,7 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parseFunc) (outShorts string, outArgs []string, err error) {
outArgs = args

if strings.HasPrefix(shorthands, "test.") {
if isGotestShorthandFlag(shorthands) {
return
}

Expand Down
22 changes: 22 additions & 0 deletions golangflag.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ import (
"strings"
)

// go test flags prefixes
func isGotestFlag(flag string) bool {
return strings.HasPrefix(flag, "-test.")
}

func isGotestShorthandFlag(flag string) bool {
return strings.HasPrefix(flag, "test.")
}

// flagValueWrapper implements pflag.Value around a flag.Value. The main
// difference here is the addition of the Type method that returns a string
// name of the type. As this is generally unknown, we approximate that with
Expand Down Expand Up @@ -103,3 +112,16 @@ func (f *FlagSet) AddGoFlagSet(newSet *goflag.FlagSet) {
}
f.addedGoFlagSets = append(f.addedGoFlagSets, newSet)
}

// ParseSkippedFlags explicitly Parses go test flags (i.e. the one starting with '-test.') with goflag.Parse(),
// since by default those are skipped by pflag.Parse().
// Typical usage example: `ParseGoTestFlags(os.Args[1:], goflag.CommandLine)`
func ParseSkippedFlags(osArgs []string, goFlagSet *goflag.FlagSet) error {
var skippedFlags []string
for _, f := range osArgs {
if isGotestFlag(f) {
skippedFlags = append(skippedFlags, f)
}
}
return goFlagSet.Parse(skippedFlags)
}
16 changes: 15 additions & 1 deletion golangflag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import (
func TestGoflags(t *testing.T) {
goflag.String("stringFlag", "stringFlag", "stringFlag")
goflag.Bool("boolFlag", false, "boolFlag")
var testxxxValue string
goflag.StringVar(&testxxxValue, "test.xxx", "test.xxx", "it is a test flag")

f := NewFlagSet("test", ContinueOnError)

f.AddGoFlagSet(goflag.CommandLine)
err := f.Parse([]string{"--stringFlag=bob", "--boolFlag"})
args := []string{"--stringFlag=bob", "--boolFlag", "-test.xxx=testvalue"}
err := f.Parse(args)
if err != nil {
t.Fatal("expected no error; get", err)
}
Expand All @@ -40,6 +43,17 @@ func TestGoflags(t *testing.T) {
t.Fatal("f.Parsed() return false after f.Parse() called")
}

if testxxxValue != "test.xxx" {
t.Fatalf("expected testxxxValue to be test.xxx but got %v", testxxxValue)
}
err = ParseSkippedFlags(args, goflag.CommandLine)
if err != nil {
t.Fatal("expected no error; ParseSkippedFlags", err)
}
if testxxxValue != "testvalue" {
t.Fatalf("expected testxxxValue to be testvalue but got %v", testxxxValue)
}

// in fact it is useless. because `go test` called flag.Parse()
if !goflag.CommandLine.Parsed() {
t.Fatal("goflag.CommandLine.Parsed() return false after f.Parse() called")
Expand Down
Loading