cmdline: options for flag propagation control.
Change-Id: Ibaf6dc6f1b1fddc95b73a316a9407267b9fc0eca
MultiPart: 1/2
diff --git a/cmdline/.api b/cmdline/.api
index 3f5d6fb..73ed475 100644
--- a/cmdline/.api
+++ b/cmdline/.api
@@ -16,6 +16,8 @@
pkg cmdline, type Command struct, ArgsLong string
pkg cmdline, type Command struct, ArgsName string
pkg cmdline, type Command struct, Children []*Command
+pkg cmdline, type Command struct, DontInheritFlags bool
+pkg cmdline, type Command struct, DontPropagateFlags bool
pkg cmdline, type Command struct, Flags flag.FlagSet
pkg cmdline, type Command struct, Long string
pkg cmdline, type Command struct, LookPath bool
diff --git a/cmdline/cmdline.go b/cmdline/cmdline.go
index 1ebac61..022e0c7 100644
--- a/cmdline/cmdline.go
+++ b/cmdline/cmdline.go
@@ -80,6 +80,14 @@
// that assume Parse has been called (e.g. Parsed, Visit,
// NArgs etc).
ParsedFlags *flag.FlagSet
+ // DontPropagateFlags indicates whether to prevent the flags defined on this
+ // command and the ancestor commands from being propagated to the descendant
+ // commands.
+ DontPropagateFlags bool
+ // DontInheritFlags indicates whether to stop inheriting the flags from the
+ // ancestor commands. The flags for the ancestor commands will not be
+ // propagated to the child commands as well.
+ DontInheritFlags bool
// Children of the command.
Children []*Command
@@ -471,11 +479,17 @@
func pathFlags(path []*Command) *flag.FlagSet {
cmd := path[len(path)-1]
flags := copyFlags(&cmd.Flags)
- if cmd.Name != helpName {
+ if cmd.Name != helpName && !cmd.DontInheritFlags {
// Walk backwards to merge flags up to the root command. If this takes too
// long, we could consider memoizing previous results.
for p := len(path) - 2; p >= 0; p-- {
+ if path[p].DontPropagateFlags {
+ break
+ }
mergeFlags(flags, &path[p].Flags)
+ if path[p].DontInheritFlags {
+ break
+ }
}
}
return flags
diff --git a/cmdline/cmdline_test.go b/cmdline/cmdline_test.go
index 990ec1e..85e15bd 100644
--- a/cmdline/cmdline_test.go
+++ b/cmdline/cmdline_test.go
@@ -13,7 +13,9 @@
"os"
"os/exec"
"path/filepath"
+ "reflect"
"regexp"
+ "strconv"
"strings"
"testing"
@@ -3030,3 +3032,105 @@
t.Errorf("got %v, want %v", got, want)
}
}
+
+type fc struct {
+ DontPropagateFlags bool
+ DontInheritFlags bool
+}
+
+func TestFlagPropagation(t *testing.T) {
+ var err error
+ env := EnvFromOS()
+
+ tests := []struct {
+ flagConfigs []fc
+ args []string
+ want []string
+ }{
+ {
+ []fc{fc{false, false}, fc{false, false}, fc{false, false}},
+ []string{"cmd1", "cmd2"},
+ []string{"flag0", "flag1", "flag2"},
+ },
+ {
+ []fc{fc{true, false}, fc{false, false}, fc{false, false}},
+ []string{"cmd1", "cmd2"},
+ []string{"flag1", "flag2"},
+ },
+ {
+ []fc{fc{false, false}, fc{true, false}, fc{false, false}},
+ []string{"cmd1", "cmd2"},
+ []string{"flag2"},
+ },
+ {
+ []fc{fc{false, false}, fc{false, true}, fc{false, false}},
+ []string{"cmd1", "cmd2"},
+ []string{"flag1", "flag2"},
+ },
+ {
+ []fc{fc{false, false}, fc{true, true}, fc{false, false}},
+ []string{"cmd1", "cmd2"},
+ []string{"flag2"},
+ },
+ {
+ []fc{fc{false, false}, fc{false, false}, fc{true, false}},
+ []string{"cmd1", "cmd2"},
+ []string{"flag0", "flag1", "flag2"},
+ },
+ {
+ []fc{fc{false, false}, fc{false, false}, fc{false, true}},
+ []string{"cmd1", "cmd2"},
+ []string{"flag2"},
+ },
+ {
+ []fc{fc{false, false}, fc{false, false}, fc{true, true}},
+ []string{"cmd1", "cmd2"},
+ []string{"flag2"},
+ },
+ }
+
+ for _, test := range tests {
+ commands := createCommandTree(test.flagConfigs)
+ root := commands[0]
+ leaf := commands[len(commands)-1]
+
+ _, _, err = Parse(root, env, test.args)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ want := map[string]bool{}
+ globalFlags.VisitAll(func(f *flag.Flag) { want[f.Name] = true })
+ for _, flagName := range test.want {
+ want[flagName] = true
+ }
+
+ got := map[string]bool{}
+ leaf.ParsedFlags.VisitAll(func(f *flag.Flag) { got[f.Name] = true })
+
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+ }
+}
+
+func createCommandTree(flagConfigs []fc) []*Command {
+ size := len(flagConfigs)
+ result := make([]*Command, size)
+
+ result[size-1] = &Command{Runner: RunnerFunc(runHello)}
+ for i := size - 2; i >= 0; i-- {
+ result[i] = &Command{Children: []*Command{result[i+1]}}
+ }
+
+ for i, cmd := range result {
+ cmd.Name = "cmd" + strconv.Itoa(i)
+ cmd.Short = "short"
+ cmd.Long = "long."
+ cmd.Flags.Bool("flag"+strconv.Itoa(i), false, "bool")
+ cmd.DontPropagateFlags = flagConfigs[i].DontPropagateFlags
+ cmd.DontInheritFlags = flagConfigs[i].DontInheritFlags
+ }
+
+ return result
+}