Skip to content

Commit

Permalink
Merge pull request kubernetes#11085 from lavalamp/munger
Browse files Browse the repository at this point in the history
Munger
  • Loading branch information
thockin committed Jul 14, 2015
2 parents a41f508 + 0dd130e commit bdbcbe2
Show file tree
Hide file tree
Showing 236 changed files with 1,676 additions and 202 deletions.
59 changes: 59 additions & 0 deletions cmd/mungedocs/analytics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
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"
"os"
"regexp"
)

var (
beginMungeExp = regexp.QuoteMeta(beginMungeTag("GENERATED_ANALYTICS"))
endMungeExp = regexp.QuoteMeta(endMungeTag("GENERATED_ANALYTICS"))
analyticsExp = regexp.QuoteMeta("[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/") +
"[^?]*" +
regexp.QuoteMeta("?pixel)]()")

// Matches the analytics blurb, with or without the munge headers.
analyticsRE = regexp.MustCompile(`[\n]*` + analyticsExp + `[\n]?` +
`|` + `[\n]*` + beginMungeExp + `[^<]*` + endMungeExp + `[\n]*`)
)

// This adds the analytics link to every .md file.
func checkAnalytics(fileName string, fileBytes []byte) (output []byte, err error) {
fileName = makeRepoRelative(fileName)
desired := fmt.Sprintf(`
`+beginMungeTag("GENERATED_ANALYTICS")+`
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/%s?pixel)]()
`+endMungeTag("GENERATED_ANALYTICS")+`
`, fileName)
if !analyticsRE.MatchString(desired) {
fmt.Printf("%q does not match %q", analyticsRE.String(), desired)
os.Exit(1)
}
//output = replaceNonPreformattedRegexp(fileBytes, analyticsRE, func(in []byte) []byte {
output = analyticsRE.ReplaceAllFunc(fileBytes, func(in []byte) []byte {
return []byte{}
})
output = bytes.TrimRight(output, "\n")
output = append(output, []byte(desired)...)
return output, nil
}
85 changes: 85 additions & 0 deletions cmd/mungedocs/analytics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
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 TestAnalytics(t *testing.T) {
var cases = []struct {
in string
out string
}{
{`aoeu`, `aoeu
` + beginMungeTag("GENERATED_ANALYTICS") + `
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/path/to/file-name.md?pixel)]()
` + endMungeTag("GENERATED_ANALYTICS") + `
`},
{`aoeu
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/path/to/file-name.md?pixel)]()
`, `aoeu
` + beginMungeTag("GENERATED_ANALYTICS") + `
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/path/to/file-name.md?pixel)]()
` + endMungeTag("GENERATED_ANALYTICS") + `
`},
{`aoeu
` + beginMungeTag("GENERATED_ANALYTICS") + `
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/path/to/file-name.md?pixel)]()
` + endMungeTag("GENERATED_ANALYTICS") + `
`, `aoeu
` + beginMungeTag("GENERATED_ANALYTICS") + `
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/path/to/file-name.md?pixel)]()
` + endMungeTag("GENERATED_ANALYTICS") + `
`},
{`aoeu
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/path/to/file-name.md?pixel)]()
` + beginMungeTag("GENERATED_ANALYTICS") + `
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/path/to/file-name.md?pixel)]()
` + endMungeTag("GENERATED_ANALYTICS") + `
`, `aoeu
` + beginMungeTag("GENERATED_ANALYTICS") + `
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/path/to/file-name.md?pixel)]()
` + endMungeTag("GENERATED_ANALYTICS") + `
`},
}
for _, c := range cases {
out, err := checkAnalytics("path/to/file-name.md", []byte(c.in))
assert.NoError(t, err)
if string(out) != c.out {
t.Errorf("Expected \n\n%v\n\n but got \n\n%v\n\n", c.out, string(out))
}
}
}
73 changes: 57 additions & 16 deletions cmd/mungedocs/links.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func checkLinks(filePath string, fileBytes []byte) ([]byte, error) {
dir := path.Dir(filePath)
errors := []string{}

output := linkRE.ReplaceAllFunc(fileBytes, func(in []byte) (out []byte) {
output := replaceNonPreformattedRegexp(fileBytes, linkRE, func(in []byte) (out []byte) {
match := linkRE.FindSubmatch(in)
// match[0] is the entire expression; [1] is the visible text and [2] is the link text.
visibleText := string(match[1])
Expand Down Expand Up @@ -108,13 +108,23 @@ func checkLinks(filePath string, fileBytes []byte) ([]byte, error) {
return output, err
}

func makeRepoRelative(path string) string {
parts := strings.Split(path, "github.com/GoogleCloudPlatform/kubernetes/")
if len(parts) > 1 {
// Take out anything that is specific to the local filesystem.
return parts[1]
func makeRepoRelative(filePath string) string {
realRoot := path.Join(*rootDir, *repoRoot) + "/"
return strings.TrimPrefix(filePath, realRoot)
}

// We have to append together before path.Clean will be able to tell that stuff
// like ../docs isn't needed.
func cleanPath(dirPath, linkPath string) string {
clean := path.Clean(path.Join(dirPath, linkPath))
if strings.HasPrefix(clean, dirPath+"/") {
out := strings.TrimPrefix(clean, dirPath+"/")
if out != linkPath {
fmt.Printf("%s -> %s\n", linkPath, out)
}
return out
}
return path
return linkPath
}

func checkPath(filePath, linkPath string) (newPath string, ok bool) {
Expand All @@ -126,18 +136,49 @@ func checkPath(filePath, linkPath string) (newPath string, ok bool) {
linkPath = linkPath[1:]
}
}
linkPath = cleanPath(dir, linkPath)

// Fast exit if the link is already correct.
if info, err := os.Stat(path.Join(dir, linkPath)); err == nil {
if info.IsDir() {
return linkPath + "/", true
}
return linkPath, true
}

for strings.HasPrefix(linkPath, "../") {
linkPath = strings.TrimPrefix(linkPath, "../")
}

// Fix - vs _ automatically
nameMungers := []func(string) string{
func(s string) string { return s },
func(s string) string { return strings.Replace(s, "-", "_", -1) },
func(s string) string { return strings.Replace(s, "_", "-", -1) },
}
// Fix being moved into/out of admin (replace "admin" with directory
// you're doing mass movements to/from).
pathMungers := []func(string) string{
func(s string) string { return s },
func(s string) string { return path.Join("admin", s) },
func(s string) string { return strings.TrimPrefix(s, "admin/") },
}

newPath = linkPath
for i := 0; i < 5; i++ {
// The file must exist.
target := path.Join(dir, newPath)
if info, err := os.Stat(target); err == nil {
if info.IsDir() {
return newPath + "/", true
for _, namer := range nameMungers {
for _, pather := range pathMungers {
newPath = pather(namer(linkPath))
for i := 0; i < 7; i++ {
// The file must exist.
target := path.Join(dir, newPath)
if info, err := os.Stat(target); err == nil {
if info.IsDir() {
return newPath + "/", true
}
return newPath, true
}
newPath = path.Join("..", newPath)
}
return newPath, true
}
newPath = path.Join("..", newPath)
}
return linkPath, false
}
45 changes: 42 additions & 3 deletions cmd/mungedocs/mungedocs.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,15 @@ import (
)

var (
verify = flag.Bool("verify", false, "Exit with status 1 if files would have needed changes but do not change.")
rootDir = flag.String("root-dir", "", "Root directory containing documents to be processed.")
verify = flag.Bool("verify", false, "Exit with status 1 if files would have needed changes but do not change.")
rootDir = flag.String("root-dir", "", "Root directory containing documents to be processed.")
repoRoot = flag.String("repo-root", "..", `Appended to --root-dir to get the repository root.
It's done this way so that generally you just have to set --root-dir.
Examples:
* --root-dir=docs/ --repo-root=.. means the repository root is ./
* --root-dir=/usr/local/long/path/repo/docs/ --repo-root=.. means the repository root is /usr/local/long/path/repo/
* --root-dir=/usr/local/long/path/repo/docs/admin --repo-root=../.. means the repository root is /usr/local/long/path/repo/`)
skipMunges = flag.String("skip-munges", "", "Comma-separated list of munges to *not* run. Available munges are: "+availableMungeList)

ErrChangesNeeded = errors.New("mungedocs: changes required")

Expand All @@ -41,7 +48,15 @@ var (
{"table-of-contents", updateTOC},
{"check-links", checkLinks},
{"unversioned-warning", updateUnversionedWarning},
{"analytics", checkAnalytics},
}
availableMungeList = func() string {
names := []string{}
for _, m := range allMunges {
names = append(names, m.name)
}
return strings.Join(names, ",")
}()
)

// a munge processes a document, returning an updated document xor an error.
Expand Down Expand Up @@ -121,6 +136,30 @@ func newWalkFunc(fp *fileProcessor, changesNeeded *bool) filepath.WalkFunc {
}
}

func wantedMunges() (filtered []munge) {
skipList := strings.Split(*skipMunges, ",")
skipped := map[string]bool{}
for _, m := range skipList {
if len(m) > 0 {
skipped[m] = true
}
}
for _, m := range allMunges {
if !skipped[m.name] {
filtered = append(filtered, m)
} else {
// Remove from the map so we can verify that everything
// requested was in fact valid.
delete(skipped, m.name)
}
}
if len(skipped) != 0 {
fmt.Fprintf(os.Stderr, "ERROR: requested to skip %v, but these are not valid munges. (valid: %v)\n", skipped, availableMungeList)
os.Exit(1)
}
return filtered
}

func main() {
flag.Parse()

Expand All @@ -139,7 +178,7 @@ func main() {
}

fp := fileProcessor{
munges: allMunges,
munges: wantedMunges(),
verifyOnly: *verify,
}

Expand Down
47 changes: 47 additions & 0 deletions cmd/mungedocs/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ package main
import (
"bytes"
"fmt"
"regexp"
"strings"
)

var (
// Finds all preformatted block start/stops.
preformatRE = regexp.MustCompile("^```.*")
)

// Splits a document up into a slice of lines.
func splitLines(document []byte) []string {
lines := strings.Split(string(document), "\n")
Expand Down Expand Up @@ -110,3 +116,44 @@ func beginMungeTag(desc string) string {
func endMungeTag(desc string) string {
return fmt.Sprintf("<!-- END MUNGE: %s -->", desc)
}

// Calls 'replace' for all sections of the document not in ``` / ``` blocks. So
// that you don't have false positives inside those blocks.
func replaceNonPreformatted(input []byte, replace func([]byte) []byte) []byte {
output := []byte(nil)
cur := []byte(nil)
keepBlock := true
// SplitAfter keeps the newline, so you don't have to worry about
// omitting it on the last line or anything. Also, the documentation
// claims it's unicode safe.
for _, line := range bytes.SplitAfter(input, []byte("\n")) {
if keepBlock {
if preformatRE.Match(line) {
cur = replace(cur)
output = append(output, cur...)
cur = []byte{}
keepBlock = false
}
cur = append(cur, line...)
} else {
cur = append(cur, line...)
if preformatRE.Match(line) {
output = append(output, cur...)
cur = []byte{}
keepBlock = true
}
}
}
if keepBlock {
cur = replace(cur)
}
output = append(output, cur...)
return output
}

// As above, but further uses exp to parse the non-preformatted sections.
func replaceNonPreformattedRegexp(input []byte, exp *regexp.Regexp, replace func([]byte) []byte) []byte {
return replaceNonPreformatted(input, func(in []byte) []byte {
return exp.ReplaceAllFunc(in, replace)
})
}
Loading

0 comments on commit bdbcbe2

Please sign in to comment.