diff --git a/cmd/mungedocs/toc.go b/cmd/mungedocs/toc.go index 0636c8ba76a..9011e117db9 100644 --- a/cmd/mungedocs/toc.go +++ b/cmd/mungedocs/toc.go @@ -35,57 +35,14 @@ func updateTOC(filePath string, markdown []byte) ([]byte, error) { if err != nil { return nil, err } - updatedMarkdown, err := updateMacroBlock(markdown, "", "", string(toc)) + lines := splitLines(markdown) + updatedMarkdown, err := updateMacroBlock(lines, "", "", string(toc)) if err != nil { return nil, err } return updatedMarkdown, nil } -// Replaces the text between matching "beginMark" and "endMark" within "document" with "insertThis". -// -// Delimiters should occupy own line. -// Returns copy of document with modifications. -func updateMacroBlock(document []byte, beginMark, endMark, insertThis string) ([]byte, error) { - var buffer bytes.Buffer - lines := strings.Split(string(document), "\n") - // Skip trailing empty string from Split-ing - if len(lines) > 0 && lines[len(lines)-1] == "" { - lines = lines[:len(lines)-1] - } - betweenBeginAndEnd := false - for _, line := range lines { - trimmedLine := strings.Trim(line, " \n") - if trimmedLine == beginMark { - if betweenBeginAndEnd { - return nil, fmt.Errorf("found second begin mark while updating macro blocks") - } - betweenBeginAndEnd = true - buffer.WriteString(line) - buffer.WriteString("\n") - } else if trimmedLine == endMark { - if !betweenBeginAndEnd { - return nil, fmt.Errorf("found end mark without being mark while updating macro blocks") - } - buffer.WriteString(insertThis) - // Extra newline avoids github markdown bug where comment ends up on same line as last bullet. - buffer.WriteString("\n") - buffer.WriteString(line) - buffer.WriteString("\n") - betweenBeginAndEnd = false - } else { - if !betweenBeginAndEnd { - buffer.WriteString(line) - buffer.WriteString("\n") - } - } - } - if betweenBeginAndEnd { - return nil, fmt.Errorf("never found closing end mark while updating macro blocks") - } - return buffer.Bytes(), nil -} - // builds table of contents for markdown file // // First scans for all section headers (lines that begin with "#" but not within code quotes) diff --git a/cmd/mungedocs/toc_test.go b/cmd/mungedocs/toc_test.go index f52df24df75..5175d8089f1 100644 --- a/cmd/mungedocs/toc_test.go +++ b/cmd/mungedocs/toc_test.go @@ -22,45 +22,6 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_updateMacroBlock(t *testing.T) { - var cases = []struct { - in string - out string - }{ - {"", ""}, - {"Lorem ipsum\ndolor sit amet\n", - "Lorem ipsum\ndolor sit amet\n"}, - {"Lorem ipsum \n BEGIN\ndolor\nEND\nsit amet\n", - "Lorem ipsum \n BEGIN\nfoo\n\nEND\nsit amet\n"}, - } - for _, c := range cases { - actual, err := updateMacroBlock([]byte(c.in), "BEGIN", "END", "foo\n") - assert.NoError(t, err) - if c.out != string(actual) { - t.Errorf("Expected '%v' but got '%v'", c.out, string(actual)) - } - } -} - -func Test_updateMacroBlock_errors(t *testing.T) { - var cases = []struct { - in string - }{ - {"BEGIN\n"}, - {"blah\nBEGIN\nblah"}, - {"END\n"}, - {"blah\nEND\nblah\n"}, - {"END\nBEGIN"}, - {"BEGIN\nEND\nEND"}, - {"BEGIN\nBEGIN\nEND"}, - {"BEGIN\nBEGIN\nEND\nEND"}, - } - for _, c := range cases { - _, err := updateMacroBlock([]byte(c.in), "BEGIN", "END", "foo") - assert.Error(t, err) - } -} - func Test_buildTOC(t *testing.T) { var cases = []struct { in string diff --git a/cmd/mungedocs/util.go b/cmd/mungedocs/util.go new file mode 100644 index 00000000000..8581f5152b9 --- /dev/null +++ b/cmd/mungedocs/util.go @@ -0,0 +1,100 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "fmt" + "strings" +) + +// Splits a document up into a slice of lines. +func splitLines(document []byte) []string { + lines := strings.Split(string(document), "\n") + // Skip trailing empty string from Split-ing + if len(lines) > 0 && lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + return lines +} + +// Replaces the text between matching "beginMark" and "endMark" within the +// document represented by "lines" with "insertThis". +// +// Delimiters should occupy own line. +// Returns copy of document with modifications. +func updateMacroBlock(lines []string, beginMark, endMark, insertThis string) ([]byte, error) { + var buffer bytes.Buffer + betweenBeginAndEnd := false + for _, line := range lines { + trimmedLine := strings.Trim(line, " \n") + if trimmedLine == beginMark { + if betweenBeginAndEnd { + return nil, fmt.Errorf("found second begin mark while updating macro blocks") + } + betweenBeginAndEnd = true + buffer.WriteString(line) + buffer.WriteString("\n") + } else if trimmedLine == endMark { + if !betweenBeginAndEnd { + return nil, fmt.Errorf("found end mark without being mark while updating macro blocks") + } + buffer.WriteString(insertThis) + // Extra newline avoids github markdown bug where comment ends up on same line as last bullet. + buffer.WriteString("\n") + buffer.WriteString(line) + buffer.WriteString("\n") + betweenBeginAndEnd = false + } else { + if !betweenBeginAndEnd { + buffer.WriteString(line) + buffer.WriteString("\n") + } + } + } + if betweenBeginAndEnd { + return nil, fmt.Errorf("never found closing end mark while updating macro blocks") + } + return buffer.Bytes(), nil +} + +// Tests that a document, represented as a slice of lines, has a line. Ignores +// leading and trailing space. +func hasLine(lines []string, needle string) bool { + for _, line := range lines { + trimmedLine := strings.Trim(line, " \n") + if trimmedLine == needle { + return true + } + } + return false +} + +// Tests that a document, represented as a slice of lines, has a macro block. +func hasMacroBlock(lines []string, begin string, end string) bool { + foundBegin := false + for _, line := range lines { + trimmedLine := strings.Trim(line, " \n") + switch { + case !foundBegin && trimmedLine == begin: + foundBegin = true + case foundBegin && trimmedLine == end: + return true + } + } + return false +} diff --git a/cmd/mungedocs/util_test.go b/cmd/mungedocs/util_test.go new file mode 100644 index 00000000000..30d85d5a2b4 --- /dev/null +++ b/cmd/mungedocs/util_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_updateMacroBlock(t *testing.T) { + var cases = []struct { + in string + out string + }{ + {"", ""}, + {"Lorem ipsum\ndolor sit amet\n", + "Lorem ipsum\ndolor sit amet\n"}, + {"Lorem ipsum \n BEGIN\ndolor\nEND\nsit amet\n", + "Lorem ipsum \n BEGIN\nfoo\n\nEND\nsit amet\n"}, + } + for _, c := range cases { + actual, err := updateMacroBlock(splitLines([]byte(c.in)), "BEGIN", "END", "foo\n") + assert.NoError(t, err) + if c.out != string(actual) { + t.Errorf("Expected '%v' but got '%v'", c.out, string(actual)) + } + } +} + +func Test_updateMacroBlock_errors(t *testing.T) { + var cases = []struct { + in string + }{ + {"BEGIN\n"}, + {"blah\nBEGIN\nblah"}, + {"END\n"}, + {"blah\nEND\nblah\n"}, + {"END\nBEGIN"}, + {"BEGIN\nEND\nEND"}, + {"BEGIN\nBEGIN\nEND"}, + {"BEGIN\nBEGIN\nEND\nEND"}, + } + for _, c := range cases { + _, err := updateMacroBlock(splitLines([]byte(c.in)), "BEGIN", "END", "foo") + assert.Error(t, err) + } +} + +func TestHasLine(t *testing.T) { + cases := []struct { + lines []string + needle string + expected bool + }{ + {[]string{"abc", "def", "ghi"}, "abc", true}, + {[]string{" abc", "def", "ghi"}, "abc", true}, + {[]string{"abc ", "def", "ghi"}, "abc", true}, + {[]string{"\n abc", "def", "ghi"}, "abc", true}, + {[]string{"abc \n", "def", "ghi"}, "abc", true}, + {[]string{"abc", "def", "ghi"}, "def", true}, + {[]string{"abc", "def", "ghi"}, "ghi", true}, + {[]string{"abc", "def", "ghi"}, "xyz", false}, + } + + for i, c := range cases { + if hasLine(c.lines, c.needle) != c.expected { + t.Errorf("case[%d]: %q, expected %t, got %t", i, c.needle, c.expected, !c.expected) + } + } +} + +func TestHasMacroBlock(t *testing.T) { + cases := []struct { + lines []string + begin string + end string + expected bool + }{ + {[]string{"<<<", ">>>"}, "<<<", ">>>", true}, + {[]string{"<<<", "abc", ">>>"}, "<<<", ">>>", true}, + {[]string{"<<<", "<<<", "abc", ">>>"}, "<<<", ">>>", true}, + {[]string{"<<<", "abc", ">>>", ">>>"}, "<<<", ">>>", true}, + {[]string{"<<<", ">>>", "<<<", ">>>"}, "<<<", ">>>", true}, + {[]string{"<<<"}, "<<<", ">>>", false}, + {[]string{">>>"}, "<<<", ">>>", false}, + {[]string{"<<<", "abc"}, "<<<", ">>>", false}, + {[]string{"abc", ">>>"}, "<<<", ">>>", false}, + } + + for i, c := range cases { + if hasMacroBlock(c.lines, c.begin, c.end) != c.expected { + t.Errorf("case[%d]: %q,%q, expected %t, got %t", i, c.begin, c.end, c.expected, !c.expected) + } + } +}