Ability to mark flags as deprecated They will not show up in usage or help, but they will still work. The usage message will print on os.Stderr any time the flag is set.
diff --git a/bool_test.go b/bool_test.go index fe45b8d..2073590 100644 --- a/bool_test.go +++ b/bool_test.go
@@ -156,7 +156,8 @@ func TestInvalidValue(t *testing.T) { var tristate triStateValue f := setUpFlagSet(&tristate) - err := f.Parse([]string{"--tristate=invalid"}) + args := []string{"--tristate=invalid"} + _, err := parseReturnStderr(t, f, args) if err == nil { t.Fatal("expected an error but did not get any, tristate has value", tristate) }
diff --git a/flag.go b/flag.go index 33db47c..ac14b81 100644 --- a/flag.go +++ b/flag.go
@@ -152,6 +152,7 @@ Value Value // value as set DefValue string // default value (as text); for usage message Changed bool // If the user set the value (or if left to default) + Deprecated string // If this flag is deprecated, this string is the new or now thing to use Annotations map[string][]string // used by cobra.Command bash autocomple code } @@ -243,6 +244,16 @@ return f.formal[name] } +// Mark a flag deprecated in your program +func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error { + flag := f.Lookup(name) + if flag == nil { + return fmt.Errorf("flag %q does not exist", name) + } + flag.Deprecated = usageMessage + return nil +} + // Lookup returns the Flag structure of the named command-line flag, // returning nil if none exists. func Lookup(name string) *Flag { @@ -264,7 +275,10 @@ f.actual = make(map[normalizedName]*Flag) } f.actual[normalName] = flag - f.lookup(normalName).Changed = true + flag.Changed = true + if len(flag.Deprecated) > 0 { + fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated) + } return nil } @@ -277,6 +291,9 @@ // otherwise, the default values of all defined flags in the set. func (f *FlagSet) PrintDefaults() { f.VisitAll(func(flag *Flag) { + if len(flag.Deprecated) > 0 { + return + } format := "--%s=%s: %s\n" if _, ok := flag.Value.(*stringValue); ok { // put quotes on the value @@ -295,6 +312,9 @@ x := new(bytes.Buffer) f.VisitAll(func(flag *Flag) { + if len(flag.Deprecated) > 0 { + return + } format := "--%s=%s: %s\n" if _, ok := flag.Value.(*stringValue); ok { // put quotes on the value @@ -466,6 +486,9 @@ } f.actual[f.normalizeFlagName(flag.Name)] = flag flag.Changed = true + if len(flag.Deprecated) > 0 { + fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated) + } return nil }
diff --git a/flag_test.go b/flag_test.go index c4055ed..a1478e2 100644 --- a/flag_test.go +++ b/flag_test.go
@@ -7,6 +7,7 @@ import ( "bytes" "fmt" + "io" "io/ioutil" "os" "sort" @@ -445,3 +446,74 @@ t.Errorf("expected argument %q got %q", arg2, f.Args()[1]) } } + +func TestDeprecatedFlagInDocs(t *testing.T) { + f := NewFlagSet("bob", ContinueOnError) + f.Bool("badflag", true, "always true") + f.MarkDeprecated("badflag", "use --good-flag instead") + + out := new(bytes.Buffer) + f.SetOutput(out) + f.PrintDefaults() + + if strings.Contains(out.String(), "badflag") { + t.Errorf("found deprecated flag in usage!") + } +} + +func parseReturnStderr(t *testing.T, f *FlagSet, args []string) (string, error) { + oldStderr := os.Stderr + r, w, _ := os.Pipe() + os.Stderr = w + + err := f.Parse(args) + + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + w.Close() + os.Stderr = oldStderr + out := <-outC + + return out, err +} + +func TestDeprecatedFlagUsage(t *testing.T) { + f := NewFlagSet("bob", ContinueOnError) + f.Bool("badflag", true, "always true") + usageMsg := "use --good-flag instead" + f.MarkDeprecated("badflag", usageMsg) + + args := []string{"--badflag"} + out, err := parseReturnStderr(t, f, args) + if err != nil { + t.Fatal("expected no error; got ", err) + } + + if !strings.Contains(out, usageMsg) { + t.Errorf("usageMsg not printed when using a deprecated flag!") + } +} + +func TestDeprecatedFlagUsageNormalized(t *testing.T) { + f := NewFlagSet("bob", ContinueOnError) + f.Bool("bad-double_flag", true, "always true") + f.SetWordSeparators([]string{"-", "_"}) + usageMsg := "use --good-flag instead" + f.MarkDeprecated("bad_double-flag", usageMsg) + + args := []string{"--bad_double_flag"} + out, err := parseReturnStderr(t, f, args) + if err != nil { + t.Fatal("expected no error; got ", err) + } + + if !strings.Contains(out, usageMsg) { + t.Errorf("usageMsg not printed when using a deprecated flag!") + } +}