mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-03 11:45:18 +00:00
Update release instructions for documentation. (#7846)
* Add linkpatch tool to rewrite documentation URLs. * Update instructions for creating backport branches. Updates #7675.
This commit is contained in:
205
scripts/linkpatch/linkpatch.go
Normal file
205
scripts/linkpatch/linkpatch.go
Normal file
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user