wrapN returned the whole remaining string whenever the first word did not fit the description column, which ended the wrapping loop in wrap and left every later word on that one line. Break after the over-wide word instead, so only that word overflows and wrapping resumes on the next one. Searching for the break point from i covers both cases the old guard caught: no whitespace before i at all, and whitespace only at index 0 where splitting would emit an empty line. Fixes #501
133 lines
4.9 KiB
Go
133 lines
4.9 KiB
Go
package pflag
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const expectedOutput = ` --long-form[=true|false] Some description
|
|
--long-form2[=true|false] Some description
|
|
with multiline
|
|
-s, --long-name[=true|false] Some description
|
|
-t, --long-name2[=true|false] Some description with
|
|
multiline
|
|
`
|
|
|
|
func setUpPFlagSet(buf io.Writer) *FlagSet {
|
|
f := NewFlagSet("test", ExitOnError)
|
|
f.Bool("long-form", false, "Some description")
|
|
f.Bool("long-form2", false, "Some description\n with multiline")
|
|
f.BoolP("long-name", "s", false, "Some description")
|
|
f.BoolP("long-name2", "t", false, "Some description with\n multiline")
|
|
f.SetOutput(buf)
|
|
return f
|
|
}
|
|
|
|
func TestPrintUsage(t *testing.T) {
|
|
buf := bytes.Buffer{}
|
|
f := setUpPFlagSet(&buf)
|
|
f.PrintDefaults()
|
|
res := buf.String()
|
|
if res != expectedOutput {
|
|
t.Errorf("Expected \n%s \nActual \n%s", expectedOutput, res)
|
|
}
|
|
}
|
|
|
|
func setUpPFlagSet2(buf io.Writer) *FlagSet {
|
|
f := NewFlagSet("test", ExitOnError)
|
|
f.Bool("long-form", false, "Some description")
|
|
f.Bool("long-form2", false, "Some description\n with multiline")
|
|
f.BoolP("long-name", "s", false, "Some description")
|
|
f.BoolP("long-name2", "t", false, "Some description with\n multiline")
|
|
f.StringP("some-very-long-arg", "l", "test", "Some very long description having break the limit")
|
|
f.StringP("other-very-long-arg", "o", "long-default-value", "Some very long description having break the limit")
|
|
f.String("some-very-long-arg2", "very long default value", "Some very long description\nwith line break\nmultiple")
|
|
f.SetOutput(buf)
|
|
return f
|
|
}
|
|
|
|
const expectedOutput2 = ` --long-form[=true|false] Some description
|
|
--long-form2[=true|false] Some description
|
|
with multiline
|
|
-s, --long-name[=true|false] Some description
|
|
-t, --long-name2[=true|false] Some description with
|
|
multiline
|
|
-o, --other-very-long-arg string Some very long description having
|
|
break the limit (default
|
|
"long-default-value")
|
|
-l, --some-very-long-arg string Some very long description having
|
|
break the limit (default "test")
|
|
--some-very-long-arg2 string Some very long description
|
|
with line break
|
|
multiple (default "very long default
|
|
value")
|
|
`
|
|
|
|
func TestPrintUsage_2(t *testing.T) {
|
|
buf := bytes.Buffer{}
|
|
f := setUpPFlagSet2(&buf)
|
|
res := f.FlagUsagesWrapped(80)
|
|
if res != expectedOutput2 {
|
|
t.Errorf("Expected \n%s \nActual \n%s", expectedOutput2, res)
|
|
}
|
|
}
|
|
|
|
const expectedOutput3 = ` --mount string a mount specification e.g.
|
|
'type=bind,source=/opt,destination=/hostopt'.
|
|
The rest of this description is
|
|
wrapped.
|
|
`
|
|
|
|
// A word wider than the description column has to overflow, but only that
|
|
// word: wrapping resumes on the next one.
|
|
func TestPrintUsage_WrapsAfterUnbreakableWord(t *testing.T) {
|
|
f := NewFlagSet("test", ExitOnError)
|
|
f.String("mount", "", "a mount specification e.g. 'type=bind,source=/opt,destination=/hostopt'. The rest of this description is wrapped.")
|
|
|
|
res := f.FlagUsagesWrapped(60)
|
|
if res != expectedOutput3 {
|
|
t.Errorf("Expected \n%s \nActual \n%s", expectedOutput3, res)
|
|
}
|
|
}
|
|
|
|
// No line may be longer than the requested width unless it is a single word
|
|
// that cannot be broken at all.
|
|
func TestPrintUsage_OnlyUnbreakableWordsOverflow(t *testing.T) {
|
|
usages := []string{
|
|
"a mount specification e.g. 'type=bind,source=/opt,destination=/hostopt'. The rest of this description is wrapped.",
|
|
"https://example.com/a/very/long/url/that/will/not/fit/in/the/column and some words after it to wrap",
|
|
"averyveryverylongwordrightatthestart followed by several shorter words",
|
|
"short words then averyveryverylongwordinthemiddle and more words after that one",
|
|
"trailing text ending in anextremelylongunbreakabletokenattheveryend",
|
|
}
|
|
|
|
for _, width := range []int{40, 60, 80} {
|
|
for _, usage := range usages {
|
|
f := NewFlagSet("test", ExitOnError)
|
|
f.String("mount", "", usage)
|
|
|
|
lines := strings.Split(strings.TrimRight(f.FlagUsagesWrapped(width), "\n"), "\n")
|
|
if len(lines) < 2 {
|
|
t.Errorf("width %d, usage %q: expected the description to wrap", width, usage)
|
|
continue
|
|
}
|
|
|
|
// Continuation lines are padded to the description column, so
|
|
// their indent says where the description starts on every line.
|
|
indent := len(lines[1]) - len(strings.TrimLeft(lines[1], " "))
|
|
|
|
for _, line := range lines {
|
|
if len(line) <= width || indent >= len(line) {
|
|
continue
|
|
}
|
|
if strings.Contains(strings.TrimSpace(line[indent:]), " ") {
|
|
t.Errorf("width %d, usage %q: line is %d wide and still has a break point:\n%s",
|
|
width, usage, len(line), line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|