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
+}