Merge pull request #9 from eparis/deprecated-flag

Ability to mark flags as deprecated
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!")
+	}
+}