//go:build xr
package main
// xr reads source files and rfc files and generates html versions, a code and
// rfc index file, and an overal index file to view code and rfc side by side.
import (
"bytes"
"flag"
"fmt"
htmltemplate "html/template"
"log"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"golang.org/x/exp/maps"
)
var destdir string
func xcheckf(err error, format string, args ...any) {
if err != nil {
log.Fatalf("%s: %s", fmt.Sprintf(format, args...), err)
}
}
func xwritefile(path string, buf []byte) {
p := filepath.Join(destdir, path)
os.MkdirAll(filepath.Dir(p), 0755)
err := os.WriteFile(p, buf, 0644)
xcheckf(err, "writing file %s", p)
}
func main() {
log.SetFlags(0)
var release bool
flag.BoolVar(&release, "release", false, "generate cross-references for a release, highlighting the release version as active page")
flag.Usage = func() {
log.Println("usage: go run xr.go destdir revision date latestrelease ../*.go ../*/*.go")
flag.PrintDefaults()
os.Exit(2)
}
flag.Parse()
args := flag.Args()
if len(args) < 4 {
flag.Usage()
}
destdir = args[0]
revision := args[1]
date := args[2]
latestRelease := args[3]
srcfiles := args[4:]
// Generate code.html index.
srcdirs := map[string][]string{}
for _, arg := range srcfiles {
arg = strings.TrimPrefix(arg, "../")
dir := filepath.Dir(arg)
file := filepath.Base(arg)
srcdirs[dir] = append(srcdirs[dir], file)
}
for _, files := range srcdirs {
sort.Strings(files)
}
dirs := maps.Keys(srcdirs)
sort.Strings(dirs)
var codeBuf bytes.Buffer
err := codeTemplate.Execute(&codeBuf, map[string]any{
"Dirs": srcdirs,
})
xcheckf(err, "generating code.html")
xwritefile("code.html", codeBuf.Bytes())
// Generate code html files.
re := regexp.MustCompile(`(\.\./)?rfc/[0-9]{3,5}(-[^ :]*)?(:[0-9]+)?`)
for dir, files := range srcdirs {
for _, file := range files {
src := filepath.Join("..", dir, file)
dst := filepath.Join(dir, file+".html")
buf, err := os.ReadFile(src)
xcheckf(err, "reading file %s", src)
var b bytes.Buffer
fmt.Fprint(&b, `
`)
for i, line := range strings.Split(string(buf), "\n") {
n := i + 1
_, err := fmt.Fprintf(&b, `%d`, n, n, n)
xcheckf(err, "writing source line")
if line == "" {
b.WriteString("\n")
} else {
for line != "" {
loc := re.FindStringIndex(line)
if loc == nil {
b.WriteString(htmltemplate.HTMLEscapeString(line))
break
}
s, e := loc[0], loc[1]
b.WriteString(htmltemplate.HTMLEscapeString(line[:s]))
match := line[s:e]
line = line[e:]
t := strings.Split(match, ":")
linenumber := 1
if len(t) == 2 {
v, err := strconv.ParseInt(t[1], 10, 31)
xcheckf(err, "parsing linenumber %q", t[1])
linenumber = int(v)
}
fmt.Fprintf(&b, `
%s`, t[0], linenumber, htmltemplate.HTMLEscapeString(match))
}
}
fmt.Fprint(&b, "
\n")
}
fmt.Fprint(&b, `
`)
xwritefile(dst, b.Bytes())
}
}
// Generate rfc index.
rfctext, err := os.ReadFile("index.txt")
xcheckf(err, "reading rfc index.txt")
type rfc struct {
File string
Title string
}
topics := map[string][]rfc{}
var topic string
for _, line := range strings.Split(string(rfctext), "\n") {
if strings.HasPrefix(line, "# ") {
topic = line[2:]
continue
}
t := strings.Split(line, "\t")
if len(t) != 4 {
continue
}
topics[topic] = append(topics[topic], rfc{strings.TrimSpace(t[0]), t[3]})
}
for _, l := range topics {
sort.Slice(l, func(i, j int) bool {
return l[i].File < l[j].File
})
}
var rfcBuf bytes.Buffer
err = rfcTemplate.Execute(&rfcBuf, map[string]any{
"Topics": topics,
})
xcheckf(err, "generating rfc.html")
xwritefile("rfc.html", rfcBuf.Bytes())
// Process each rfc file into html.
for _, rfcs := range topics {
for _, rfc := range rfcs {
dst := filepath.Join("rfc", rfc.File+".html")
buf, err := os.ReadFile(rfc.File)
xcheckf(err, "reading rfc %s", rfc.File)
var b bytes.Buffer
fmt.Fprint(&b, `
`)
isRef := func(s string) bool {
return s[0] >= '0' && s[0] <= '9' || strings.HasPrefix(s, "../")
}
parseRef := func(s string) (string, int, bool) {
t := strings.Split(s, ":")
linenumber := 1
if len(t) == 2 {
v, err := strconv.ParseInt(t[1], 10, 31)
xcheckf(err, "parsing linenumber")
linenumber = int(v)
}
isCode := strings.HasPrefix(t[0], "../")
return t[0], linenumber, isCode
}
for i, line := range strings.Split(string(buf), "\n") {
if line == "" {
line = "\n"
} else if len(line) < 80 || strings.Contains(rfc.File, "-") && i > 0 {
line = htmltemplate.HTMLEscapeString(line)
} else {
t := strings.Split(line[80:], " ")
line = htmltemplate.HTMLEscapeString(line[:80])
for i, s := range t {
if i > 0 {
line += " "
}
if s == "" || !isRef(s) {
line += htmltemplate.HTMLEscapeString(s)
continue
}
file, linenumber, isCode := parseRef(s)
target := ""
if isCode {
target = ` target="code"`
}
line += fmt.Sprintf(` %s:%d`, file, linenumber, target, file, linenumber)
}
}
n := i + 1
_, err := fmt.Fprintf(&b, `%s`, n, n, n, line, "\n")
xcheckf(err, "writing rfc line")
}
fmt.Fprint(&b, `
`)
xwritefile(dst, b.Bytes())
}
}
// Generate overal file.
index := indexHTML
if release {
index = strings.ReplaceAll(index, "RELEASEWEIGHT", "bold")
index = strings.ReplaceAll(index, "REVISIONWEIGHT", "normal")
} else {
index = strings.ReplaceAll(index, "RELEASEWEIGHT", "normal")
index = strings.ReplaceAll(index, "REVISIONWEIGHT", "bold")
}
index = strings.ReplaceAll(index, "REVISION", revision)
index = strings.ReplaceAll(index, "DATE", date)
index = strings.ReplaceAll(index, "RELEASE", latestRelease)
xwritefile("index.html", []byte(index))
}
var indexHTML = `
Cross-referenced code and RFCs - Mox
`
var codeTemplate = htmltemplate.Must(htmltemplate.New("code").Parse(`
code index
Package | Files |
{{- range $dir, $files := .Dirs }}
{{ $dir }}/ |
{{- range $files }}
{{ . }}
{{- end }}
|
{{- end }}
`))
var rfcTemplate = htmltemplate.Must(htmltemplate.New("rfc").Parse(`
Topic | RFC |
{{- range $topic, $rfcs := .Topics }}
{{ $topic }} |
{{- range $rfcs }}
{{ .File }}
{{- end }}
|
{{- end }}
`))