Add an anchor on the options names.
This commit is contained in:
parent
0b240ca97a
commit
c294b87a45
83 changed files with 2893 additions and 2586 deletions
266
internal/anchors.go
Normal file
266
internal/anchors.go
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// detect any existing <a ...> tag in the cell (case-insensitive).
|
||||
reExistingAnchor = regexp.MustCompile(`(?i)<\s*a\b`)
|
||||
// separator cell like --- or :---: (3+ dashes, optional leading/trailing colon).
|
||||
reSepCell = regexp.MustCompile(`^\s*:?-{3,}:?\s*$`)
|
||||
// markdown link [text](url) → text (used to strip link wrappers in id).
|
||||
reMdLink = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`)
|
||||
// collapse multiple hyphens.
|
||||
reMultiHyphens = regexp.MustCompile(`-+`)
|
||||
)
|
||||
|
||||
// splitTableRow splits a markdown table line on pipes, while keeping escaped pipes.
|
||||
// parts[1] will be the first data cell for lines that start with '|'.
|
||||
func splitTableRow(line string) []string {
|
||||
var parts []string
|
||||
var b strings.Builder
|
||||
escaped := false
|
||||
for _, r := range line {
|
||||
if escaped {
|
||||
b.WriteRune(r)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
if r == '\\' {
|
||||
escaped = true
|
||||
b.WriteRune(r)
|
||||
continue
|
||||
}
|
||||
if r == '|' {
|
||||
parts = append(parts, b.String())
|
||||
b.Reset()
|
||||
continue
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
parts = append(parts, b.String())
|
||||
return parts
|
||||
}
|
||||
|
||||
func isTableRow(line string) bool {
|
||||
s := strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(s, "|") {
|
||||
return false
|
||||
}
|
||||
parts := splitTableRow(line)
|
||||
return len(parts) >= 3
|
||||
}
|
||||
|
||||
func isSeparatorRow(line string) bool {
|
||||
if !isTableRow(line) {
|
||||
return false
|
||||
}
|
||||
parts := splitTableRow(line)
|
||||
// check all middle cells (skip first and last which are outside pipes)
|
||||
for i := 1; i < len(parts)-1; i++ {
|
||||
cell := strings.TrimSpace(parts[i])
|
||||
if cell == "" {
|
||||
continue
|
||||
}
|
||||
if !reSepCell.MatchString(cell) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create ID from cell text, preserving letter case, removing <br /> and markdown decorations.
|
||||
func makeID(text string) string {
|
||||
id := strings.TrimSpace(text)
|
||||
|
||||
// remove BR tags (common in table cells)
|
||||
id = strings.ReplaceAll(id, "<br />", " ")
|
||||
id = strings.ReplaceAll(id, "<br/>", " ")
|
||||
id = strings.ReplaceAll(id, "<br>", " ")
|
||||
|
||||
// remove the dots
|
||||
id = strings.ReplaceAll(id, ".", "-")
|
||||
|
||||
// strip markdown link wrappers [text](url) -> text
|
||||
id = reMdLink.ReplaceAllString(id, "$1")
|
||||
|
||||
// remove inline markdown characters
|
||||
id = strings.ReplaceAll(id, "`", "")
|
||||
id = strings.ReplaceAll(id, "*", "")
|
||||
id = strings.ReplaceAll(id, "~", "")
|
||||
|
||||
// replace spaces/underscores with hyphen
|
||||
id = strings.ReplaceAll(id, " ", "-")
|
||||
id = strings.ReplaceAll(id, "_", "-")
|
||||
|
||||
// keep only letters (both cases), digits and hyphens
|
||||
var clean []rune
|
||||
for _, r := range id {
|
||||
if (r >= 'a' && r <= 'z') ||
|
||||
(r >= 'A' && r <= 'Z') ||
|
||||
(r >= '0' && r <= '9') ||
|
||||
r == '-' || r == '.' {
|
||||
// keep dot as you requested (we won't replace it)
|
||||
clean = append(clean, r)
|
||||
}
|
||||
}
|
||||
id = string(clean)
|
||||
|
||||
// collapse multiple hyphens and trim
|
||||
id = reMultiHyphens.ReplaceAllString(id, "-")
|
||||
id = strings.Trim(id, "-")
|
||||
if id == "" {
|
||||
id = "row"
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Dedupe ID within a file: if id already seen, append -2, -3...
|
||||
func dedupeID(base string, seen map[string]int) string {
|
||||
if base == "" {
|
||||
base = "row"
|
||||
}
|
||||
|
||||
count, ok := seen[base]
|
||||
if !ok {
|
||||
seen[base] = 1
|
||||
return base
|
||||
}
|
||||
|
||||
seen[base] = count + 1
|
||||
return fmt.Sprintf("%s-%d", base, count+1)
|
||||
}
|
||||
|
||||
// Inject clickable link that is also the target (id + href on same element).
|
||||
func injectClickableFirstCell(line string, seen map[string]int) string {
|
||||
parts := splitTableRow(line)
|
||||
// first data cell is index 1
|
||||
firstCellRaw := parts[1]
|
||||
firstTrimmed := strings.TrimSpace(firstCellRaw)
|
||||
|
||||
// if there's already an <a ...> tag inside, skip (avoid nesting)
|
||||
if reExistingAnchor.MatchString(firstTrimmed) {
|
||||
return line
|
||||
}
|
||||
|
||||
id := makeID(firstTrimmed)
|
||||
if id == "" {
|
||||
return line
|
||||
}
|
||||
id = dedupeID(id, seen)
|
||||
|
||||
// wrap the visible cell content in a link that is also the target
|
||||
// keep the cell inner HTML/text (firstCellRaw) as-is inside the <a>
|
||||
parts[1] = fmt.Sprintf(" <a id=\"%s\" href=\"#%s\" title=\"#%s\">%s</a> ", id, id, id, strings.TrimSpace(firstCellRaw))
|
||||
return strings.Join(parts, "|")
|
||||
}
|
||||
|
||||
func processFile(path string) error {
|
||||
// read file
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var lines []string
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
lines = append(lines, sc.Text())
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
inFence := false
|
||||
seen := make(map[string]int)
|
||||
out := make([]string, len(lines))
|
||||
|
||||
for i, line := range lines {
|
||||
trim := strings.TrimSpace(line)
|
||||
|
||||
// toggle code fence (``` or ~~~)
|
||||
if strings.HasPrefix(trim, "```") || strings.HasPrefix(trim, "~~~") {
|
||||
inFence = !inFence
|
||||
out[i] = line
|
||||
continue
|
||||
}
|
||||
if inFence {
|
||||
out[i] = line
|
||||
continue
|
||||
}
|
||||
|
||||
// not a table row -> copy as-is
|
||||
if !isTableRow(line) {
|
||||
out[i] = line
|
||||
continue
|
||||
}
|
||||
|
||||
// separator row -> copy as-is
|
||||
if isSeparatorRow(line) {
|
||||
out[i] = line
|
||||
continue
|
||||
}
|
||||
|
||||
// detect header row (the row immediately before a separator) and skip it
|
||||
isHeader := false
|
||||
for j := i + 1; j < len(lines); j++ {
|
||||
if strings.TrimSpace(lines[j]) == "" {
|
||||
continue
|
||||
}
|
||||
if isSeparatorRow(lines[j]) {
|
||||
isHeader = true
|
||||
}
|
||||
break
|
||||
}
|
||||
if isHeader {
|
||||
out[i] = line
|
||||
continue
|
||||
}
|
||||
|
||||
// otherwise inject clickable link in first cell
|
||||
out[i] = injectClickableFirstCell(line, seen)
|
||||
}
|
||||
|
||||
// overwrite file in place
|
||||
wf, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bw := bufio.NewWriter(wf)
|
||||
for _, l := range out {
|
||||
fmt.Fprintln(bw, l)
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
_ = wf.Close()
|
||||
return err
|
||||
}
|
||||
return wf.Close()
|
||||
}
|
||||
|
||||
func genAnchors() {
|
||||
root := "./docs/content/reference/"
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".md") {
|
||||
if perr := processFile(path); perr != nil {
|
||||
fmt.Printf("⚠️ Error processing %s: %v\n", path, perr)
|
||||
} else {
|
||||
fmt.Printf("✅ Processed %s\n", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("walk error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ var commentGenerated = `## CODE GENERATED AUTOMATICALLY
|
|||
func main() {
|
||||
genRoutingConfDoc()
|
||||
genInstallConfDoc()
|
||||
genAnchors()
|
||||
}
|
||||
|
||||
// Generate the Routing Configuration YAML and TOML files.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue