Merge pull request #52 from JanetKuo/shorthand-deprecator
Add shorthand deprecator
diff --git a/README.md b/README.md
index deee931..247ceca 100644
--- a/README.md
+++ b/README.md
@@ -216,6 +216,25 @@
myFlagSet.SetNormalizeFunc(aliasNormalizeFunc)
```
+## Deprecating a flag or its shorthand
+It is possible to deprecate a flag, or just its shorthand. Deprecating a flag/shorthand hides it from help text and prints a usage message when the deprecated flag/shorthand is used.
+
+**Example #1**: You want to deprecate a flag named "badflag" as well as inform the users what flag they should use instead.
+```go
+// deprecate a flag by specifying its name and a usage message
+flags.MarkDeprecated("badflag", "please use --good-flag instead")
+```
+This hides "badflag" from help text, and prints `Flag --badflag has been deprecated, please use --good-flag instead` when "badflag" is used.
+
+**Example #2**: You want to keep a flag name "noshorthandflag" but deprecate its shortname "n".
+```go
+// deprecate a flag shorthand by specifying its flag name and a usage message
+flags.MarkShorthandDeprecated("noshorthandflag", "please use --noshorthandflag only")
+```
+This hides the shortname "n" from help text, and prints `Flag shorthand -n has been deprecated, please use --noshorthandflag only` when the shorthand "n" is used.
+
+Note that usage message is essential here, and it should not be empty.
+
## More info
You can see the full reference documentation of the pflag package
diff --git a/flag.go b/flag.go
index 603c81a..73f202f 100644
--- a/flag.go
+++ b/flag.go
@@ -149,15 +149,16 @@
// A Flag represents the state of a flag.
type Flag struct {
- Name string // name as it appears on command line
- Shorthand string // one-letter abbreviated flag
- Usage string // help message
- 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)
- NoOptDefVal string //default value (as text); if the flag is on the command line without any options
- 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
+ Name string // name as it appears on command line
+ Shorthand string // one-letter abbreviated flag
+ Usage string // help message
+ 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)
+ NoOptDefVal string //default value (as text); if the flag is on the command line without any options
+ Deprecated string // If this flag is deprecated, this string is the new or now thing to use
+ ShorthandDeprecated string // If the shorthand of 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
}
// Value is the interface to the dynamic value stored in a flag.
@@ -298,10 +299,28 @@
if flag == nil {
return fmt.Errorf("flag %q does not exist", name)
}
+ if len(usageMessage) == 0 {
+ return fmt.Errorf("deprecated message for flag %q must be set", name)
+ }
flag.Deprecated = usageMessage
return nil
}
+// Mark the shorthand of a flag deprecated in your program. It will
+// continue to function but will not show up in help or usage messages. Using
+// this flag will also print the given usageMessage.
+func (f *FlagSet) MarkShorthandDeprecated(name string, usageMessage string) error {
+ flag := f.Lookup(name)
+ if flag == nil {
+ return fmt.Errorf("flag %q does not exist", name)
+ }
+ if len(usageMessage) == 0 {
+ return fmt.Errorf("deprecated message for flag %q must be set", name)
+ }
+ flag.ShorthandDeprecated = usageMessage
+ return nil
+}
+
// Lookup returns the Flag structure of the named command-line flag,
// returning nil if none exists.
func Lookup(name string) *Flag {
@@ -379,7 +398,7 @@
return
}
format := ""
- if len(flag.Shorthand) > 0 {
+ if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
format = " -%s, --%s"
} else {
format = " %s --%s"
@@ -397,7 +416,11 @@
format = format + "]"
}
format = format + ": %s\n"
- fmt.Fprintf(x, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage)
+ shorthand := flag.Shorthand
+ if len(flag.ShorthandDeprecated) > 0 {
+ shorthand = ""
+ }
+ fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage)
})
return x.String()
@@ -586,9 +609,21 @@
if len(flag.Deprecated) > 0 {
fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
}
+ if len(flag.ShorthandDeprecated) > 0 && containsShorthand(origArg, flag.Shorthand) {
+ fmt.Fprintf(os.Stderr, "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
+ }
return nil
}
+func containsShorthand(arg, shorthand string) bool {
+ // filter out flags --<flag_name>
+ if strings.HasPrefix(arg, "-") {
+ return false
+ }
+ arg = strings.SplitN(arg, "=", 2)[0]
+ return strings.Contains(arg, shorthand)
+}
+
func (f *FlagSet) parseLongArg(s string, args []string) (a []string, err error) {
a = args
name := s[2:]
diff --git a/flag_test.go b/flag_test.go
index 5114f28..165f689 100644
--- a/flag_test.go
+++ b/flag_test.go
@@ -729,6 +729,21 @@
}
}
+func TestDeprecatedFlagShorthandInDocs(t *testing.T) {
+ f := NewFlagSet("bob", ContinueOnError)
+ name := "noshorthandflag"
+ f.BoolP(name, "n", true, "always true")
+ f.MarkShorthandDeprecated("noshorthandflag", fmt.Sprintf("use --%s instead", name))
+
+ out := new(bytes.Buffer)
+ f.SetOutput(out)
+ f.PrintDefaults()
+
+ if strings.Contains(out.String(), "-n,") {
+ t.Errorf("found deprecated flag shorthand in usage!")
+ }
+}
+
func parseReturnStderr(t *testing.T, f *FlagSet, args []string) (string, error) {
oldStderr := os.Stderr
r, w, _ := os.Pipe()
@@ -768,6 +783,24 @@
}
}
+func TestDeprecatedFlagShorthandUsage(t *testing.T) {
+ f := NewFlagSet("bob", ContinueOnError)
+ name := "noshorthandflag"
+ f.BoolP(name, "n", true, "always true")
+ usageMsg := fmt.Sprintf("use --%s instead", name)
+ f.MarkShorthandDeprecated(name, usageMsg)
+
+ args := []string{"-n"}
+ 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")