aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--flag.go120
-rw-r--r--flag_test.go39
3 files changed, 142 insertions, 19 deletions
diff --git a/.travis.yml b/.travis.yml
index df435a0..c7d8e05 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,5 +14,5 @@ install:
- go install ./...
script:
- - verify/all.sh
+ - verify/all.sh -v
- go test ./...
diff --git a/flag.go b/flag.go
index fd91440..deac3af 100644
--- a/flag.go
+++ b/flag.go
@@ -408,41 +408,123 @@ func (f *FlagSet) PrintDefaults() {
fmt.Fprintf(f.out(), "%s", usages)
}
+// 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 "<nil>":
+ 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
+}
+
// FlagUsages Returns a string containing the usage information for all flags in
// the FlagSet
func (f *FlagSet) FlagUsages() string {
x := new(bytes.Buffer)
+ lines := make([]string, 0, len(f.formal))
+
+ maxlen := 0
f.VisitAll(func(flag *Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden {
return
}
- format := ""
+
+ line := ""
if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
- format = " -%s, --%s"
+ line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
} else {
- format = " %s --%s"
- }
- if len(flag.NoOptDefVal) > 0 {
- format = format + "["
+ line = fmt.Sprintf(" --%s", flag.Name)
}
- if flag.Value.Type() == "string" {
- // put quotes on the value
- format = format + "=%q"
- } else {
- format = format + "=%s"
+
+ varname, usage := UnquoteUsage(flag)
+ if len(varname) > 0 {
+ line += " " + varname
}
if len(flag.NoOptDefVal) > 0 {
- format = format + "]"
+ switch flag.Value.Type() {
+ case "string":
+ line += fmt.Sprintf("[=%q]", flag.NoOptDefVal)
+ case "bool":
+ if flag.NoOptDefVal != "true" {
+ line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
+ }
+ default:
+ line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
+ }
+ }
+
+ // This special character will be replaced with spacing once the
+ // correct alignment is calculated
+ line += "\x00"
+ if len(line) > maxlen {
+ maxlen = len(line)
}
- format = format + ": %s\n"
- shorthand := flag.Shorthand
- if len(flag.ShorthandDeprecated) > 0 {
- shorthand = ""
+
+ line += usage
+ if !isZeroValue(flag.DefValue) {
+ if flag.Value.Type() == "string" {
+ line += fmt.Sprintf(" (default %q)", flag.DefValue)
+ } else {
+ line += fmt.Sprintf(" (default %s)", flag.DefValue)
+ }
}
- fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage)
+
+ lines = append(lines, line)
})
+ for _, line := range lines {
+ sidx := strings.Index(line, "\x00")
+ spacing := strings.Repeat(" ", maxlen-sidx)
+ fmt.Fprintln(x, line[:sidx], spacing, line[sidx+1:])
+ }
+
return x.String()
}
@@ -463,6 +545,8 @@ func defaultUsage(f *FlagSet) {
// Usage prints to standard error a usage message documenting all defined command-line 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()
@@ -806,7 +890,7 @@ func Parsed() bool {
return CommandLine.Parsed()
}
-// The default set of command-line flags, parsed from os.Args.
+// CommandLine is the default set of command-line flags, parsed from os.Args.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
// NewFlagSet returns a new, empty flag set with the specified name and
diff --git a/flag_test.go b/flag_test.go
index e17b2aa..0ae2e4f 100644
--- a/flag_test.go
+++ b/flag_test.go
@@ -872,3 +872,42 @@ func TestHiddenFlagUsage(t *testing.T) {
t.Errorf("usage message printed when using a hidden flag!")
}
}
+
+const defaultOutput = ` --A for bootstrapping, allow 'any' type
+ --Alongflagname disable bounds checking
+ -C, --CCC 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)
+ --ND1 string[="bar"] a string with NoOptDefVal (default "foo")
+ --ND2 num[=4321] a num with NoOptDefVal (default 1234)
+ --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.BoolP("CCC", "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.String("ND1", "foo", "a string with NoOptDefVal")
+ fs.Lookup("ND1").NoOptDefVal = "bar"
+ fs.Int("ND2", 1234, "a `num` with NoOptDefVal")
+ fs.Lookup("ND2").NoOptDefVal = "4321"
+ fs.PrintDefaults()
+ got := buf.String()
+ if got != defaultOutput {
+ fmt.Println("\n" + got)
+ fmt.Println("\n" + defaultOutput)
+ t.Errorf("got %q want %q\n", got, defaultOutput)
+ }
+}