cqlprotodoc/main.go (154 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 (
"embed"
"fmt"
"html/template"
"os"
"path/filepath"
"strings"
"cqlprotodoc/spec"
)
//go:embed template.gohtml
var templateFS embed.FS
type TOCNode struct {
spec.TOCEntry
Exists bool
Children []*TOCNode
}
func buildTOCTree(entries []spec.TOCEntry, sectionNumbers map[string]struct{}) []*TOCNode {
var root TOCNode
stack := []*TOCNode{&root}
for _, e := range entries {
level := strings.Count(e.Number, ".") + 1
if len(stack) > level {
stack = stack[:level]
}
parent := stack[len(stack)-1]
_, exists := sectionNumbers[e.Number]
node := &TOCNode{
TOCEntry: e,
Children: nil,
Exists: exists,
}
parent.Children = append(parent.Children, node)
stack = append(stack, node)
}
return root.Children
}
type templateData struct {
spec.Document
LicenseHTML template.HTML
TOCTree []*TOCNode
Sections []Section
}
type Section struct {
spec.Section
Level int
BodyHTML template.HTML
}
func link(sb *strings.Builder, href, text string) {
sb.WriteString(`<a href="`)
sb.WriteString(template.HTMLEscapeString(href))
sb.WriteString(`">`)
sb.WriteString(template.HTMLEscapeString(text))
sb.WriteString(`</a>`)
}
func formatBody(text []spec.Text, sectionNumbers map[string]struct{}) template.HTML {
var sb strings.Builder
for _, t := range text {
switch {
case t.SectionRef != "":
if _, ok := sectionNumbers[t.SectionRef]; ok {
link(&sb, "#s"+t.SectionRef, t.Text)
} else {
sb.WriteString(template.HTMLEscapeString(t.Text))
}
case t.Href != "":
link(&sb, t.Href, t.Text)
default:
sb.WriteString(template.HTMLEscapeString(t.Text))
}
}
return template.HTML(sb.String())
}
func buildSections(in []spec.Section, sectionNumbers map[string]struct{}) []Section {
ret := make([]Section, len(in))
for i, s := range in {
ret[i].Section = in[i]
ret[i].Level = strings.Count(s.Number, ".") + 2
ret[i].BodyHTML = formatBody(in[i].Body, sectionNumbers)
}
return ret
}
func checkSectionLinks(d spec.Document, sectionNumbers, tocNumbers map[string]struct{}) {
for _, t := range d.TOC {
if _, ok := sectionNumbers[t.Number]; !ok {
fmt.Fprintf(os.Stderr, "section %q exists in TOC, but not in sections\n", t.Number)
}
}
for _, s := range d.Sections {
if _, ok := tocNumbers[s.Number]; !ok {
fmt.Fprintf(os.Stderr, "section %q exists in sections, but not in TOC\n", s.Number)
}
for _, tt := range s.Body {
if tt.SectionRef != "" {
if _, ok := sectionNumbers[tt.SectionRef]; !ok {
fmt.Fprintf(os.Stderr, "non-existing section %q is referenced from section %q\n",
tt.SectionRef, s.Number)
}
}
}
}
}
var tmpl = template.Must(template.ParseFS(templateFS, "template.gohtml"))
func processDocument(inputPath, outputPath string) (outErr error) {
data, err := os.ReadFile(inputPath)
if err != nil {
return err
}
doc, err := spec.Parse(string(data))
if err != nil {
return err
}
sectionNumbers := make(map[string]struct{})
for _, s := range doc.Sections {
sectionNumbers[s.Number] = struct{}{}
}
tocNumbers := make(map[string]struct{})
for _, t := range doc.TOC {
tocNumbers[t.Number] = struct{}{}
}
checkSectionLinks(doc, sectionNumbers, tocNumbers)
f, err := os.Create(outputPath)
if err != nil {
return err
}
defer func() {
closeErr := f.Close()
if closeErr != nil && outErr == nil {
outErr = closeErr
}
}()
return tmpl.Execute(f, templateData{
Document: doc,
LicenseHTML: formatBody(doc.License, sectionNumbers),
TOCTree: buildTOCTree(doc.TOC, sectionNumbers),
Sections: buildSections(doc.Sections, sectionNumbers),
})
}
func main() {
if len(os.Args) != 3 {
fmt.Fprintln(os.Stderr, "Usage: <cassandra_doc_dir> <output_dir>")
return
}
inputDir := os.Args[1]
outputDir := os.Args[2]
for _, name := range []string{"v5", "v4", "v3"} {
fmt.Fprintln(os.Stderr, name)
err := processDocument(filepath.Join(inputDir, fmt.Sprintf("native_protocol_%s.spec", name)),
filepath.Join(outputDir, fmt.Sprintf("native_protocol_%s.html", name)))
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", name, err)
return
}
}
}