v.io/x/lib/cmdline: subtle bug fix for accessing parsed flags.
The commandline package has some complex logic for handling
duplicate flags. As part of this it takes a copy of the
flagset specified for the command and possibly manipulates
this copy. However, the copy is currently inaccessible to
the application and hence calls to Visit/Narg etc are
unpredictable depending on whether the original flag set
is used or not. A new member variable, ParsedFlags is added
which is guaranteed to be set to the FlagSet that is actually
parsed.
Change-Id: I676b01575a71d7e8f9049fd649647f9a3491c87e
diff --git a/cmdline/.api b/cmdline/.api
index 0c11b43..a5db6d2 100644
--- a/cmdline/.api
+++ b/cmdline/.api
@@ -20,6 +20,7 @@
pkg cmdline, type Command struct, Long string
pkg cmdline, type Command struct, LookPath bool
pkg cmdline, type Command struct, Name string
+pkg cmdline, type Command struct, ParsedFlags *flag.FlagSet
pkg cmdline, type Command struct, Runner Runner
pkg cmdline, type Command struct, Short string
pkg cmdline, type Command struct, Topics []Topic
diff --git a/cmdline/cmdline.go b/cmdline/cmdline.go
index e548b78..678731d 100644
--- a/cmdline/cmdline.go
+++ b/cmdline/cmdline.go
@@ -69,8 +69,17 @@
// Flags defined for this command. When a flag F is defined on a command C,
// we allow F to be specified on the command line immediately after C, or
- // after any descendant of C.
+ // after any descendant of C. This FlagSet is only used to specify the
+ // flags and their associated value variables, it is never parsed and hence
+ // methods on FlagSet that are generally used after parsing cannot be
+ // used on Flags. ParsedFlags should be used instead.
Flags flag.FlagSet
+ // ParsedFlags contains the FlagSet created by the Command
+ // implementation and that has had its Parse method called. It
+ // should be used instead of the Flags field for handling methods
+ // that assume Parse has been called (e.g. Parsed, Visit,
+ // NArgs etc).
+ ParsedFlags *flag.FlagSet
// Children of the command.
Children []*Command
@@ -436,6 +445,7 @@
if err := flags.Parse(args); err != nil {
return nil, nil, err
}
+ cmd.ParsedFlags = flags
return flags.Args(), extractSetFlags(flags), nil
}
diff --git a/cmdline/cmdline_test.go b/cmdline/cmdline_test.go
index 77d6997..990ec1e 100644
--- a/cmdline/cmdline_test.go
+++ b/cmdline/cmdline_test.go
@@ -2971,3 +2971,62 @@
}
runTestCases(t, cmd, tests)
}
+
+func TestParsedFlags(t *testing.T) {
+ root := &Command{
+ Name: "root",
+ Short: "short",
+ Long: "long.",
+ Runner: RunnerFunc(runHello),
+ }
+ var v1, v2 bool
+ env := EnvFromOS()
+ root.Flags.BoolVar(&v1, "a", false, "bool")
+ root.Flags.BoolVar(&v2, "b", false, "bool")
+
+ // ParsedFlags should be nil if Parse fails.
+ _, _, err := Parse(root, env, []string{"-xx"})
+ if err == nil {
+ t.Errorf("expected an error")
+ }
+ var nilFlagSet *flag.FlagSet
+ if got, want := root.ParsedFlags, nilFlagSet; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+
+ // ParsedFlags should be set and Parsed returns true if
+ // the command line is successfully parsed.
+ _, _, err = Parse(root, env, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := root.Flags.Parsed(), false; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := root.ParsedFlags.Parsed(), true; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := v1, false; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := v2, false; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+
+ _, _, err = Parse(root, env, []string{"-a"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := root.Flags.Parsed(), false; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := root.ParsedFlags.Parsed(), true; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := v1, true; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := v2, false; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+}