]> Cypherpunks.ru repositories - gostls13.git/commitdiff
flag: nicer usage messages
authorRob Pike <r@golang.org>
Tue, 10 Mar 2015 22:08:55 +0000 (15:08 -0700)
committerRob Pike <r@golang.org>
Thu, 12 Mar 2015 18:20:35 +0000 (18:20 +0000)
Make PrintDefaults print an easier-to-read format, and allow the user
to control it a bit by putting a hint into the usage string.

Here is the new doc comment for PrintDefaults, which does the work:

    PrintDefaults prints, to standard error unless configured otherwise, a
    usage message showing the default settings of all defined command-line
    flags. For an integer valued flag x, the default output has the form

-x int
usage-message-for-x (default 7)

    The usage message will appear on a separate line except for single-
    letter boolean flags. Boolean flags omit the type, since they can be
    used without an actual value, and the parenthetical default is omitted
    if the default is the zero value for the type. The type, here int, can
    be replaced by a string of the user's choosing by placing in the usage
    string for the flag a back-quoted name; the first such item in the
    message is taken to be a parameter name to show in the message and the
    back quotes are stripped from the message when displayed. For instance,
    given

flag.String("I", "", "search `directory` for include files")

    the output will be

-I directory
search directory for include files.

Given

A = flag.Bool("A", false, "for bootstrapping, allow 'any' type")
B = flag.Bool("Alongflagname", false, "disable bounds checking")
C = flag.Bool("C", true, "a boolean defaulting to true")
D = flag.String("D", "", "set relative `path` for local imports")
F = flag.Float64("F", 2.7, "a non-zero float")
G = flag.Float64("G", 0, "a float that defaults to zero")
N = flag.Int("N", 27, "a non-zero int")
Z = flag.Int("Z", 0, "an int that defaults to zero")
T = flag.Duration("deltaT", 0, "a duration")

the old output was

  -A=false: for bootstrapping, allow 'any' type
  -Alongflagname=false: disable bounds checking
  -C=true: a boolean defaulting to true
  -D="": set relative `path` for local imports
  -F=2.7: a non-zero float
  -G=0: a float that defaults to zero
  -N=27: a non-zero int
  -Z=0: an int that defaults to zero
  -deltaT=0: a duration

and the new output is

  -A for bootstrapping, allow 'any' type
  -Alongflagname
disable bounds checking
  -C a boolean defaulting to true (default true)
  -D path
    set relative path for local imports
  -F float
    a non-zero float (default 2.7)
  -G float
    a float that defaults to zero
  -N int
    a non-zero int (default 27)
  -Z int
    an int that defaults to zero
  -deltaT duration
    a duration

Change-Id: I54ab3cd5610d551422b004d95ab78305e06a395d
Reviewed-on: https://go-review.googlesource.com/7330
Reviewed-by: Andrew Gerrand <adg@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
src/flag/flag.go
src/flag/flag_test.go

index 8c0e402e38ebe11601d115a0c06b74b5d56daed5..bdbfa9edb87a54b63537da3e73272f0423b350c5 100644 (file)
@@ -373,20 +373,110 @@ func Set(name, value string) error {
        return CommandLine.Set(name, value)
 }
 
-// PrintDefaults prints, to standard error unless configured
-// otherwise, the default values of all defined flags in the set.
+// isZeroValue guesses whether the string represents the zero
+// value for a flag. It is not accurate but in practice works OK.
+func isZeroValue(value string) bool {
+       switch value {
+       case "false":
+               return true
+       case "":
+               return true
+       case "0":
+               return true
+       }
+       return false
+}
+
+// UnquoteUsage extracts a back-quoted name from the usage
+// string for a flag and returns it and the un-quoted usage.
+// Given "a `name` to show" it returns ("name", "a name to show").
+// If there are no back quotes, the name is an educated guess of the
+// type of the flag's value, or the empty string if the flag is boolean.
+func UnquoteUsage(flag *Flag) (name string, usage string) {
+       // Look for a back-quoted name, but avoid the strings package.
+       usage = flag.Usage
+       for i := 0; i < len(usage); i++ {
+               if usage[i] == '`' {
+                       for j := i + 1; j < len(usage); j++ {
+                               if usage[j] == '`' {
+                                       name = usage[i+1 : j]
+                                       usage = usage[:i] + name + usage[j+1:]
+                                       return name, usage
+                               }
+                       }
+                       break // Only one back quote; use type name.
+               }
+       }
+       // No explicit name, so use type if we can find one.
+       name = "value"
+       switch flag.Value.(type) {
+       case boolFlag:
+               name = ""
+       case *durationValue:
+               name = "duration"
+       case *float64Value:
+               name = "float"
+       case *intValue, *int64Value:
+               name = "int"
+       case *stringValue:
+               name = "string"
+       case *uintValue, *uint64Value:
+               name = "uint"
+       }
+       return
+}
+
+// PrintDefaults prints to standard error the default values of all
+// defined command-line flags in the set. See the documentation for
+// the global function PrintDefaults for more information.
 func (f *FlagSet) PrintDefaults() {
        f.VisitAll(func(flag *Flag) {
-               format := "  -%s=%s: %s\n"
-               if _, ok := flag.Value.(*stringValue); ok {
-                       // put quotes on the value
-                       format = "  -%s=%q: %s\n"
+               s := fmt.Sprintf("  -%s", flag.Name) // Two spaces before -; see next two comments.
+               name, usage := UnquoteUsage(flag)
+               if len(name) > 0 {
+                       s += " " + name
+               }
+               // Boolean flags of one ASCII letter are so common we
+               // treat them specially, putting their usage on the same line.
+               if len(s) <= 4 { // space, space, '-', 'x'.
+                       s += "\t"
+               } else {
+                       // Three spaces before the tab triggers good alignment
+                       // for both 4- and 8-space tab stops.
+                       s += "\n   \t"
+               }
+               s += usage
+               if !isZeroValue(flag.DefValue) {
+                       if _, ok := flag.Value.(*stringValue); ok {
+                               // put quotes on the value
+                               s += fmt.Sprintf(" (default %q)", flag.DefValue)
+                       } else {
+                               s += fmt.Sprintf(" (default %v)", flag.DefValue)
+                       }
                }
-               fmt.Fprintf(f.out(), format, flag.Name, flag.DefValue, flag.Usage)
+               fmt.Fprint(f.out(), s, "\n")
        })
 }
 
-// PrintDefaults prints to standard error the default values of all defined command-line flags.
+// PrintDefaults prints, to standard error unless configured otherwise,
+// a usage message showing the default settings of all defined
+// command-line flags.
+// For an integer valued flag x, the default output has the form
+//     -x int
+//             usage-message-for-x (default 7)
+// The usage message will appear on a separate line for anything but
+// a bool flag with a one-byte name. For bool flags, the type is
+// omitted and if the flag name is one byte the usage message appears
+// on the same line. The parenthetical default is omitted if the
+// default is the zero value for the type. The listed type, here int,
+// can be changed by placing a back-quoted name in the flag's usage
+// string; the first such item in the message is taken to be a parameter
+// name to show in the message and the back quotes are stripped from
+// the message when displayed. For instance, given
+//     flag.String("I", "", "search `directory` for include files")
+// the output will be
+//     -I directory
+//             search directory for include files.
 func PrintDefaults() {
        CommandLine.PrintDefaults()
 }
@@ -408,6 +498,8 @@ func defaultUsage(f *FlagSet) {
 // Usage prints to standard error a usage message documenting all defined command-line flags.
 // It is called when an error occurs while parsing flags.
 // The function is a variable that may be changed to point to a custom function.
+// By default it prints a simple header and calls PrintDefaults; for details about the
+// format of the output and how to control it, see the documentation for PrintDefaults.
 var Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
        PrintDefaults()
index 8c88c8c2744371ff7e3a4e87f0ee7c7bb5e91010..d5425a8d79190db28162435ebb3ed9c05ad02bd5 100644 (file)
@@ -377,3 +377,41 @@ func TestHelp(t *testing.T) {
                t.Fatal("help was called; should not have been for defined help flag")
        }
 }
+
+const defaultOutput = `  -A    for bootstrapping, allow 'any' type
+  -Alongflagname
+       disable bounds checking
+  -C   a boolean defaulting to true (default true)
+  -D path
+       set relative path for local imports
+  -F number
+       a non-zero number (default 2.7)
+  -G float
+       a float that defaults to zero
+  -N int
+       a non-zero int (default 27)
+  -Z int
+       an int that defaults to zero
+  -maxT timeout
+       set timeout for dial
+`
+
+func TestPrintDefaults(t *testing.T) {
+       fs := NewFlagSet("print defaults test", ContinueOnError)
+       var buf bytes.Buffer
+       fs.SetOutput(&buf)
+       fs.Bool("A", false, "for bootstrapping, allow 'any' type")
+       fs.Bool("Alongflagname", false, "disable bounds checking")
+       fs.Bool("C", true, "a boolean defaulting to true")
+       fs.String("D", "", "set relative `path` for local imports")
+       fs.Float64("F", 2.7, "a non-zero `number`")
+       fs.Float64("G", 0, "a float that defaults to zero")
+       fs.Int("N", 27, "a non-zero int")
+       fs.Int("Z", 0, "an int that defaults to zero")
+       fs.Duration("maxT", 0, "set `timeout` for dial")
+       fs.PrintDefaults()
+       got := buf.String()
+       if got != defaultOutput {
+               t.Errorf("got %q want %q\n", got, defaultOutput)
+       }
+}