forked from tendermint/tendermint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update release instructions for documentation. (tendermint#7846)
* Add linkpatch tool to rewrite documentation URLs. * Update instructions for creating backport branches. Updates tendermint#7675.
- Loading branch information
M. J. Fromberger
authored
Feb 21, 2022
1 parent
351adf8
commit bd6fce1
Showing
5 changed files
with
237 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
// Program linkpatch rewrites absolute URLs pointing to targets in GitHub in | ||
// Markdown link tags to target a different branch. | ||
// | ||
// This is used to update documentation links for backport branches. | ||
// See https://github.com/tendermint/tendermint/issues/7675 for context. | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"flag" | ||
"fmt" | ||
"io/fs" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/creachadair/atomicfile" | ||
) | ||
|
||
var ( | ||
repoName = flag.String("repo", "tendermint/tendermint", "Repository name to match") | ||
sourceBranch = flag.String("source", "master", "Source branch name (required)") | ||
targetBranch = flag.String("target", "", "Target branch name (required)") | ||
doRecur = flag.Bool("recur", false, "Recur into subdirectories") | ||
|
||
skipPath stringList | ||
skipMatch regexpFlag | ||
|
||
// Match markdown links pointing to absolute URLs. | ||
// This only works for "inline" links, not referenced links. | ||
// The submetch selects the URL. | ||
linkRE = regexp.MustCompile(`(?m)\[.*?\]\((https?://.*?)\)`) | ||
) | ||
|
||
func init() { | ||
flag.Var(&skipPath, "skip-path", "Skip these paths (comma-separated)") | ||
flag.Var(&skipMatch, "skip-match", "Skip URLs matching this regexp (RE2)") | ||
|
||
flag.Usage = func() { | ||
fmt.Fprintf(os.Stderr, `Usage: %[1]s [options] <file/dir>... | ||
Rewrite absolute Markdown links targeting the specified GitHub repository | ||
and source branch name to point to the target branch instead. Matching | ||
files are updated in-place. | ||
Each path names either a directory to list, or a single file path to | ||
rewrite. By default, only the top level of a directory is scanned; use -recur | ||
to recur into subdirectories. | ||
Options: | ||
`, filepath.Base(os.Args[0])) | ||
flag.PrintDefaults() | ||
} | ||
} | ||
|
||
func main() { | ||
flag.Parse() | ||
switch { | ||
case *repoName == "": | ||
log.Fatal("You must specify a non-empty -repo name (org/repo)") | ||
case *targetBranch == "": | ||
log.Fatal("You must specify a non-empty -target branch") | ||
case *sourceBranch == "": | ||
log.Fatal("You must specify a non-empty -source branch") | ||
case *sourceBranch == *targetBranch: | ||
log.Fatalf("Source and target branch are the same (%q)", *sourceBranch) | ||
case flag.NArg() == 0: | ||
log.Fatal("You must specify at least one file/directory to rewrite") | ||
} | ||
|
||
r, err := regexp.Compile(fmt.Sprintf(`^https?://github.com/%s/(?:blob|tree)/%s`, | ||
*repoName, *sourceBranch)) | ||
if err != nil { | ||
log.Fatalf("Compiling regexp: %v", err) | ||
} | ||
for _, path := range flag.Args() { | ||
if err := processPath(r, path); err != nil { | ||
log.Fatalf("Processing %q failed: %v", path, err) | ||
} | ||
} | ||
} | ||
|
||
func processPath(r *regexp.Regexp, path string) error { | ||
fi, err := os.Lstat(path) | ||
if err != nil { | ||
return err | ||
} | ||
if fi.Mode().IsDir() { | ||
return processDir(r, path) | ||
} else if fi.Mode().IsRegular() { | ||
return processFile(r, path) | ||
} | ||
return nil // nothing to do with links, device files, sockets, etc. | ||
} | ||
|
||
func processDir(r *regexp.Regexp, root string) error { | ||
return filepath.Walk(root, func(path string, fi fs.FileInfo, err error) error { | ||
if err != nil { | ||
return err | ||
} | ||
if fi.IsDir() { | ||
if skipPath.Contains(path) { | ||
log.Printf("Skipping %q (per -skip-path)", path) | ||
return filepath.SkipDir // explicitly skipped | ||
} else if !*doRecur && path != root { | ||
return filepath.SkipDir // skipped because we aren't recurring | ||
} | ||
return nil // nothing else to do for directories | ||
} else if skipPath.Contains(path) { | ||
log.Printf("Skipping %q (per -skip-path)", path) | ||
return nil // explicitly skipped | ||
} else if filepath.Ext(path) != ".md" { | ||
return nil // nothing to do for non-Markdown files | ||
} | ||
|
||
return processFile(r, path) | ||
}) | ||
} | ||
|
||
func processFile(r *regexp.Regexp, path string) error { | ||
log.Printf("Processing file %q", path) | ||
input, err := os.ReadFile(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
pos := 0 | ||
var output bytes.Buffer | ||
for _, m := range linkRE.FindAllSubmatchIndex(input, -1) { | ||
href := string(input[m[2]:m[3]]) | ||
u := r.FindStringIndex(href) | ||
if u == nil || skipMatch.MatchString(href) { | ||
if u != nil { | ||
log.Printf("Skipped URL %q (by -skip-match)", href) | ||
} | ||
output.Write(input[pos:m[1]]) // copy the existing data as-is | ||
pos = m[1] | ||
continue | ||
} | ||
|
||
// Copy everything before the URL as-is, then write the replacement. | ||
output.Write(input[pos:m[2]]) // everything up to the URL | ||
fmt.Fprintf(&output, `https://github.com/%s/blob/%s%s`, *repoName, *targetBranch, href[u[1]:]) | ||
|
||
// Write out the tail of the match, everything after the URL. | ||
output.Write(input[m[3]:m[1]]) | ||
pos = m[1] | ||
} | ||
output.Write(input[pos:]) // the rest of the file | ||
|
||
_, err = atomicfile.WriteAll(path, &output, 0644) | ||
return err | ||
} | ||
|
||
// stringList implements the flag.Value interface for a comma-separated list of strings. | ||
type stringList []string | ||
|
||
func (lst *stringList) Set(s string) error { | ||
if s == "" { | ||
*lst = nil | ||
} else { | ||
*lst = strings.Split(s, ",") | ||
} | ||
return nil | ||
} | ||
|
||
// Contains reports whether lst contains s. | ||
func (lst stringList) Contains(s string) bool { | ||
for _, elt := range lst { | ||
if s == elt { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (lst stringList) String() string { return strings.Join([]string(lst), ",") } | ||
|
||
// regexpFlag implements the flag.Value interface for a regular expression. | ||
type regexpFlag struct{ *regexp.Regexp } | ||
|
||
func (r regexpFlag) MatchString(s string) bool { | ||
if r.Regexp == nil { | ||
return false | ||
} | ||
return r.Regexp.MatchString(s) | ||
} | ||
|
||
func (r *regexpFlag) Set(s string) error { | ||
c, err := regexp.Compile(s) | ||
if err != nil { | ||
return err | ||
} | ||
r.Regexp = c | ||
return nil | ||
} | ||
|
||
func (r regexpFlag) String() string { | ||
if r.Regexp == nil { | ||
return "" | ||
} | ||
return r.Regexp.String() | ||
} |