aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Campbell <[email protected]>2017-01-30 21:42:45 +0000
committerEric Paris <[email protected]>2017-01-30 16:42:45 -0500
commit9ff6c6923cfffbcd502984b8e0c80539a94968b7 (patch)
treec4a238b6f2150d68b981aa21bb451dc634c459ef
parenta9a634f3de0a7529baded7ad6b0c7467d5c6eca7 (diff)
Add FlagSet.FlagUsagesWrapped(cols) which wraps to the given column (#105)
This will allow applications to provide better help text without feeling constrained by how it will wrap even on 80 column displays etc. This has been a factor in tickets such as https://github.com/docker/docker/issues/18797. The basic wrapping algorithm is rather simplistic, it will look for the last whitespace (space or tab) before the given column and wrap there, indenting the continuation lines to match the usage text (i.e. aligned after the flag names themselves), e.g. when applied to `docker ps` wrapping at 70 columns (fairly narrow): Options: [...] -f, --filter filter Filter output based on conditions provided [...] -n, --last int Show n last created containers (includes all states) (default -1) -l, --latest Show the latest created container (includes all states) There are two main tweaks to this basic algorithm, first is to actually try and wrap to a soft limit 5 columns less than requested but allow the line to take up the full hard width if that prevents pushing a short word at the end of the string to the next line which looks odd, particuarly for usage which ends with "(default [])" and wraps the "[])" to the last line. Second if the display is too narrow, meaning after indentation for the flag names there is less than 24 columns allowed for the help text (24 chosen just by my eye and what I thought looked odd) then it will start the help text on the next line indented to the 16th column (16 chosen so as not to align with the first character of either the short of long flag name as 8 would, since that looked strange to me), e.g. wrapping the above example to a rather narrow 45 columns: Options: [...] -f, --filter filter Filter output based on conditions provided [...] -n, --last int Show n last created containers (includes all states) (default -1) -l, --latest Show the latest created container (includes all states) If even with starting the help on the next line there is still less than 24 characters of space available for the help text (implying columns < 24 + 16 == 40) just give up and return the unwrapped version (same as before this change). Signed-off-by: Ian Campbell <[email protected]>,
-rw-r--r--flag.go82
1 files changed, 78 insertions, 4 deletions
diff --git a/flag.go b/flag.go
index 3f5fedd..746af63 100644
--- a/flag.go
+++ b/flag.go
@@ -487,9 +487,76 @@ func UnquoteUsage(flag *Flag) (name string, usage string) {
return
}
-// FlagUsages Returns a string containing the usage information for all flags in
-// the FlagSet
-func (f *FlagSet) FlagUsages() string {
+// Splits the string `s` on whitespace into an initial substring up to
+// `i` runes in length and the remainder. Will go `slop` over `i` if
+// that encompasses the entire string (which allows the caller to
+// avoid short orphan words on the final line).
+func wrapN(i, slop int, s string) (string, string) {
+ if i+slop > len(s) {
+ return s, ""
+ }
+
+ w := strings.LastIndexAny(s[:i], " \t")
+ if w <= 0 {
+ return s, ""
+ }
+
+ return s[:w], s[w+1:]
+}
+
+// Wraps the string `s` to a maximum width `w` with leading indent
+// `i`. The first line is not indented (this is assumed to be done by
+// caller). Pass `w` == 0 to do no wrapping
+func wrap(i, w int, s string) string {
+ if w == 0 {
+ return s
+ }
+
+ // space between indent i and end of line width w into which
+ // we should wrap the text.
+ wrap := w - i
+
+ var r, l string
+
+ // Not enough space for sensible wrapping. Wrap as a block on
+ // the next line instead.
+ if wrap < 24 {
+ i = 16
+ wrap = w - i
+ r += "\n" + strings.Repeat(" ", i)
+ }
+ // If still not enough space then don't even try to wrap.
+ if wrap < 24 {
+ return s
+ }
+
+ // Try to avoid short orphan words on the final line, by
+ // allowing wrapN to go a bit over if that would fit in the
+ // remainder of the line.
+ slop := 5
+ wrap = wrap - slop
+
+ // Handle first line, which is indented by the caller (or the
+ // special case above)
+ l, s = wrapN(wrap, slop, s)
+ r = r + l
+
+ // Now wrap the rest
+ for s != "" {
+ var t string
+
+ t, s = wrapN(wrap, slop, s)
+ r = r + "\n" + strings.Repeat(" ", i) + t
+ }
+
+ return r
+
+}
+
+// FlagUsagesWrapped returns a string containing the usage information
+// for all flags in the FlagSet. Wrapped to `cols` columns (0 for no
+// wrapping)
+func (f *FlagSet) FlagUsagesWrapped(cols int) string {
x := new(bytes.Buffer)
lines := make([]string, 0, len(f.formal))
@@ -546,12 +613,19 @@ func (f *FlagSet) FlagUsages() string {
for _, line := range lines {
sidx := strings.Index(line, "\x00")
spacing := strings.Repeat(" ", maxlen-sidx)
- fmt.Fprintln(x, line[:sidx], spacing, line[sidx+1:])
+ // maxlen + 2 comes from + 1 for the \x00 and + 1 for the (deliberate) off-by-one in maxlen-sidx
+ fmt.Fprintln(x, line[:sidx], spacing, wrap(maxlen+2, cols, line[sidx+1:]))
}
return x.String()
}
+// FlagUsages returns a string containing the usage information for all flags in
+// the FlagSet
+func (f *FlagSet) FlagUsages() string {
+ return f.FlagUsagesWrapped(0)
+}
+
// PrintDefaults prints to standard error the default values of all defined command-line flags.
func PrintDefaults() {
CommandLine.PrintDefaults()