diff --git a/cmd/mungedocs/example_syncer.go b/cmd/mungedocs/example_syncer.go new file mode 100644 index 0000000000..e0a48a249d --- /dev/null +++ b/cmd/mungedocs/example_syncer.go @@ -0,0 +1,115 @@ +/* +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" + "io/ioutil" + "path" + "regexp" + "strings" +) + +const exampleMungeTag = "EXAMPLE" + +// syncExamples updates all examples in markdown file. +// +// Finds the magic macro block tags, find the link to the example +// specified in the tags, and replaces anything between those with +// the content of the example, thereby syncing it. +// +// For example, +// +// +// ```yaml +// foo: +// bar: +// ``` +// +// [Download example](../../examples/guestbook/frontend-controller.yaml) +// +func syncExamples(filePath string, markdown []byte) ([]byte, error) { + // find the example syncer begin tag + header := beginMungeTag(fmt.Sprintf("%s %s", exampleMungeTag, `(([^ ])*.(yaml|json))`)) + exampleLinkRE := regexp.MustCompile(header) + lines := splitLines(markdown) + updatedMarkdown, err := updateExampleMacroBlock(filePath, lines, exampleLinkRE, endMungeTag(exampleMungeTag)) + if err != nil { + return updatedMarkdown, err + } + return updatedMarkdown, nil +} + +// exampleContent retrieves the content of the file at linkPath +func exampleContent(filePath, linkPath, fileType string) (content string, err error) { + realRoot := path.Join(*rootDir, *repoRoot) + "/" + path := path.Join(realRoot, path.Dir(filePath), linkPath) + dat, err := ioutil.ReadFile(path) + if err != nil { + return content, err + } + content = fmt.Sprintf("\n```%s\n%s\n```\n\n[Download example](%s)", fileType, string(dat), linkPath) + return +} + +// updateExampleMacroBlock sync the yaml/json example between begin tag and end tag +func updateExampleMacroBlock(filePath string, lines []string, beginMarkExp *regexp.Regexp, endMark string) ([]byte, error) { + var buffer bytes.Buffer + betweenBeginAndEnd := false + for _, line := range lines { + trimmedLine := strings.Trim(line, " \n") + if beginMarkExp.Match([]byte(trimmedLine)) { + if betweenBeginAndEnd { + return nil, fmt.Errorf("found second begin mark while updating macro blocks") + } + betweenBeginAndEnd = true + buffer.WriteString(line) + buffer.WriteString("\n") + match := beginMarkExp.FindStringSubmatch(line) + if len(match) < 4 { + return nil, fmt.Errorf("failed to parse the link in example header") + } + // match[0] is the entire expression; [1] is the link text and [3] is the file type (yaml or json). + linkText := match[1] + fileType := match[3] + example, err := exampleContent(filePath, linkText, fileType) + if err != nil { + return nil, err + } + buffer.WriteString(example) + } else if trimmedLine == endMark { + if !betweenBeginAndEnd { + return nil, fmt.Errorf("found end mark without being mark while updating macro blocks") + } + // 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 +} diff --git a/cmd/mungedocs/example_syncer_test.go b/cmd/mungedocs/example_syncer_test.go new file mode 100644 index 0000000000..76b9072840 --- /dev/null +++ b/cmd/mungedocs/example_syncer_test.go @@ -0,0 +1,58 @@ +/* +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_syncExamples(t *testing.T) { + var podExample = `apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 +` + var cases = []struct { + in string + out string + }{ + {"", ""}, + { + "\n\n", + "\n\n```yaml\n" + podExample + "```\n\n[Download example](testdata/pod.yaml)\n\n", + }, + { + "\n\n", + "\n\n```yaml\n" + podExample + "```\n\n[Download example](../mungedocs/testdata/pod.yaml)\n\n", + }, + } + for _, c := range cases { + actual, err := syncExamples("mungedocs/filename.md", []byte(c.in)) + assert.NoError(t, err) + if c.out != string(actual) { + t.Errorf("Expected example \n'%v' but got \n'%v'", c.out, string(actual)) + } + } +} diff --git a/cmd/mungedocs/mungedocs.go b/cmd/mungedocs/mungedocs.go index ac644ceb73..d2a0d85061 100644 --- a/cmd/mungedocs/mungedocs.go +++ b/cmd/mungedocs/mungedocs.go @@ -52,6 +52,7 @@ Examples: {"unversioned-warning", updateUnversionedWarning}, {"analytics", checkAnalytics}, {"kubectl-dash-f", checkKubectlFileTargets}, + {"sync-examples", syncExamples}, } availableMungeList = func() string { names := []string{} diff --git a/cmd/mungedocs/testdata/pod.yaml b/cmd/mungedocs/testdata/pod.yaml new file mode 100644 index 0000000000..89920b83a9 --- /dev/null +++ b/cmd/mungedocs/testdata/pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 \ No newline at end of file