flag: nicer usage messages

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>
diff --git a/src/flag/flag.go b/src/flag/flag.go
index 8c0e402..bdbfa9e 100644
--- a/src/flag/flag.go
+++ b/src/flag/flag.go
@@ -373,20 +373,110 @@
 	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
 		}
-		fmt.Fprintf(f.out(), format, flag.Name, flag.DefValue, flag.Usage)
+		// 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.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 @@
 // 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()
diff --git a/src/flag/flag_test.go b/src/flag/flag_test.go
index 8c88c8c..d5425a8 100644
--- a/src/flag/flag_test.go
+++ b/src/flag/flag_test.go
@@ -377,3 +377,41 @@
 		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)
+	}
+}