Commit 51d66601 authored by Rob Pike's avatar Rob Pike

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/7330Reviewed-by: 's avatarAndrew Gerrand <adg@golang.org>
Reviewed-by: 's avatarRuss Cox <rsc@golang.org>
parent 6d0e87af
...@@ -373,20 +373,110 @@ func Set(name, value string) error { ...@@ -373,20 +373,110 @@ func Set(name, value string) error {
return CommandLine.Set(name, value) return CommandLine.Set(name, value)
} }
// PrintDefaults prints, to standard error unless configured // isZeroValue guesses whether the string represents the zero
// otherwise, the default values of all defined flags in the set. // 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() { func (f *FlagSet) PrintDefaults() {
f.VisitAll(func(flag *Flag) { f.VisitAll(func(flag *Flag) {
format := " -%s=%s: %s\n" s := fmt.Sprintf(" -%s", flag.Name) // Two spaces before -; see next two comments.
if _, ok := flag.Value.(*stringValue); ok { name, usage := UnquoteUsage(flag)
// put quotes on the value if len(name) > 0 {
format = " -%s=%q: %s\n" 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() { func PrintDefaults() {
CommandLine.PrintDefaults() CommandLine.PrintDefaults()
} }
...@@ -408,6 +498,8 @@ func defaultUsage(f *FlagSet) { ...@@ -408,6 +498,8 @@ func defaultUsage(f *FlagSet) {
// Usage prints to standard error a usage message documenting all defined command-line flags. // Usage prints to standard error a usage message documenting all defined command-line flags.
// It is called when an error occurs while parsing 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. // 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() { var Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
PrintDefaults() PrintDefaults()
......
...@@ -377,3 +377,41 @@ func TestHelp(t *testing.T) { ...@@ -377,3 +377,41 @@ func TestHelp(t *testing.T) {
t.Fatal("help was called; should not have been for defined help flag") 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)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment