From a22b6400a1492781aedecb79002718e3fbb023dc Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Fri, 5 Jun 2020 00:46:51 -0400 Subject: [PATCH] Improve kubectl explain formatting-preservation --- staging/src/k8s.io/kubectl/pkg/explain/BUILD | 1 + .../k8s.io/kubectl/pkg/explain/formatter.go | 70 ++++++++++++++++--- .../kubectl/pkg/explain/formatter_test.go | 44 +++++++++++- 3 files changed, 106 insertions(+), 9 deletions(-) diff --git a/staging/src/k8s.io/kubectl/pkg/explain/BUILD b/staging/src/k8s.io/kubectl/pkg/explain/BUILD index 91bd8c8b6e9..60f58b2c2d1 100644 --- a/staging/src/k8s.io/kubectl/pkg/explain/BUILD +++ b/staging/src/k8s.io/kubectl/pkg/explain/BUILD @@ -41,6 +41,7 @@ go_test( deps = [ "//staging/src/k8s.io/apimachinery/pkg/api/meta/testrestmapper:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library", "//staging/src/k8s.io/kubectl/pkg/util/openapi/testing:go_default_library", ], diff --git a/staging/src/k8s.io/kubectl/pkg/explain/formatter.go b/staging/src/k8s.io/kubectl/pkg/explain/formatter.go index 04bdb04644b..76ca017e47e 100644 --- a/staging/src/k8s.io/kubectl/pkg/explain/formatter.go +++ b/staging/src/k8s.io/kubectl/pkg/explain/formatter.go @@ -19,6 +19,7 @@ package explain import ( "fmt" "io" + "regexp" "strings" ) @@ -103,22 +104,75 @@ func (l *line) Add(word string) bool { return false } +var bullet = regexp.MustCompile(`^(\d+\.?|-|\*)\s`) + +func shouldStartNewLine(lastWord, str string) bool { + // preserve line breaks ending in : + if strings.HasSuffix(lastWord, ":") { + return true + } + + // preserve code blocks + if strings.HasPrefix(str, " ") { + return true + } + str = strings.TrimSpace(str) + // preserve empty lines + if len(str) == 0 { + return true + } + // preserve lines that look like they're starting lists + if bullet.MatchString(str) == true { + return true + } + // otherwise combine + return false +} + func wrapString(str string, wrap int) []string { - words := strings.Fields(str) wrapped := []string{} l := line{wrap: wrap} - - for _, word := range words { - if !l.Add(word) { + // track the last word added to the current line + lastWord := "" + flush := func() { + if !l.Empty() { + lastWord = "" wrapped = append(wrapped, l.String()) l = line{wrap: wrap} + } + } + + // iterate over the lines in the original description + for _, str := range strings.Split(str, "\n") { + // preserve code blocks and blockquotes as-is + if strings.HasPrefix(str, " ") { + flush() + wrapped = append(wrapped, str) + continue + } + + // preserve empty lines after the first line, since they can separate logical sections + if len(wrapped) > 0 && len(strings.TrimSpace(str)) == 0 { + flush() + wrapped = append(wrapped, "") + continue + } + + // flush if we should start a new line + if shouldStartNewLine(lastWord, str) { + flush() + } + words := strings.Fields(str) + for _, word := range words { + lastWord = word if !l.Add(word) { - panic("Couldn't add to empty line.") + flush() + if !l.Add(word) { + panic("Couldn't add to empty line.") + } } } } - if !l.Empty() { - wrapped = append(wrapped, l.String()) - } + flush() return wrapped } diff --git a/staging/src/k8s.io/kubectl/pkg/explain/formatter_test.go b/staging/src/k8s.io/kubectl/pkg/explain/formatter_test.go index 15aaf0a9873..41db9f55e60 100644 --- a/staging/src/k8s.io/kubectl/pkg/explain/formatter_test.go +++ b/staging/src/k8s.io/kubectl/pkg/explain/formatter_test.go @@ -19,6 +19,8 @@ package explain import ( "bytes" "testing" + + "k8s.io/apimachinery/pkg/util/diff" ) func TestFormatterWrite(t *testing.T) { @@ -54,6 +56,30 @@ func TestFormatterWrappedWrite(t *testing.T) { f.Indent(10).WriteWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi at turpis faucibus, gravida dolor ut, fringilla velit.") // Test long words (especially urls) on their own line. f.Indent(20).WriteWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit. ThisIsAVeryLongWordThatDoesn'tFitOnALineOnItsOwn. Morbi at turpis faucibus, gravida dolor ut, fringilla velit.") + // Test content that includes newlines, bullet points, and blockquotes/code blocks + f.Indent(4).WriteWrapped(` +This is an +introductory paragraph +that should end +up on a continuous line. + +Example: +Example text on its own line + +List: +1. Item with + wrapping text +11. Another + item with wrapping text +* Bullet item + with wrapping text +- Dash item + with wrapping text + +base64( + code goes here + and here +)`) want := `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi at turpis faucibus, gravida dolor ut, @@ -68,10 +94,26 @@ fringilla velit. Morbi at turpis faucibus, gravida dolor ut, fringilla velit. + This is an introductory paragraph that should + end up on a continuous line. + + Example: + Example text on its own line + + List: + 1. Item with wrapping text + 11. Another item with wrapping text + * Bullet item with wrapping text + - Dash item with wrapping text + + base64( + code goes here + and here + ) ` if buf.String() != want { - t.Errorf("Got:\n%v\nWant:\n%v\n", buf.String(), want) + t.Errorf("Diff:\n%s", diff.StringDiff(buf.String(), want)) } }