Files
sow-tools/internal/topdata/wiki_page_paths.go
T
archvillainette 8cebf718b7 Canonical Wiki Paths Enforcement (#6)
### Summary
- Change generated topdata wiki links to target public wiki namespace and canonical slug paths.
- Add generated public path metadata to wiki page generation and page-index output.
- Add YAML-driven generated slug overrides and public-target collision validation.

### Details
- Generated relation links now render public targets such as:
  - `[[feat/hardiness-vs-enchantments|Hardiness vs. Enchantments]]`
  - `[[feat/favored-enemy-elves|Favored Enemy: Elves]]`
- Topdata page IDs remain the internal identity for output layout, managed markers, deploy state, and bookkeeping.
- Generated managed markers now include the effective public slug:
  - `<!-- sow-topdata-wiki:page=... wiki_slug=... -->`
- Page-index entries now include:
  - `public_slug`
  - `public_target`
- Added parsing and validation for `page_paths.slug_overrides` from `topdata/wiki/wiki.yaml`.
- Generated public targets are validated for duplicate namespace-local collisions.
- Table link helpers now derive public targets from target page titles when available, so display aliases do not accidentally change link destinations.

### Tests
- Added and updated coverage for:
  - public target rendering
  - marker `wiki_slug` output
  - page-index public metadata
  - duplicate generated public target rejection
  - slug override parsing and validation
  - override-driven target selection
  - target title slugging independent of display labels

### Validation
- `go test ./...`
- `gofmt -w` on changed Go files
- `./build-tool.sh`
- `git diff --check`

Reviewed-on: https://gitea.westgate.pw/ShadowsOverWestgate/sow-tools/pulls/6
Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
2026-05-21 19:21:02 +02:00

75 lines
1.9 KiB
Go

package topdata
import (
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
)
type wikiPagePathDocument struct {
PagePaths wikiPagePaths `json:"page_paths" yaml:"page_paths"`
}
type wikiPagePaths struct {
SlugOverrides []wikiPageSlugOverride `json:"slug_overrides" yaml:"slug_overrides"`
}
type wikiPageSlugOverride struct {
PageID string `json:"page_id" yaml:"page_id"`
Slug string `json:"slug" yaml:"slug"`
}
func loadWikiPagePathDeclarations(path string) (map[string]string, error) {
raw, err := os.ReadFile(path)
if os.IsNotExist(err) {
return map[string]string{}, nil
}
if err != nil {
return nil, err
}
return parseWikiPagePathDeclarations(raw, path)
}
func parseWikiPagePathDeclarations(raw []byte, path string) (map[string]string, error) {
var doc wikiPagePathDocument
if err := yaml.Unmarshal(raw, &doc); err != nil {
return nil, fmt.Errorf("parse wiki page paths %s: %w", path, err)
}
overrides := map[string]string{}
for _, override := range doc.PagePaths.SlugOverrides {
pageID := strings.TrimSpace(override.PageID)
slug := strings.TrimSpace(override.Slug)
if pageID == "" {
return nil, fmt.Errorf("parse wiki page paths %s: slug override has empty page_id", path)
}
if !isCanonicalWikiSlug(slug) {
return nil, fmt.Errorf("parse wiki page paths %s: page_id %q has invalid slug %q", path, pageID, override.Slug)
}
if _, ok := overrides[pageID]; ok {
return nil, fmt.Errorf("parse wiki page paths %s: duplicate page_id %q", path, pageID)
}
overrides[pageID] = slug
}
return overrides, nil
}
func isCanonicalWikiSlug(value string) bool {
if value == "" || value != strings.ToLower(value) {
return false
}
parts := strings.Split(value, "-")
for _, part := range parts {
if part == "" {
return false
}
for _, r := range part {
if !((r >= 'a' && r <= 'z') || (r >= '0' && r <= '9')) {
return false
}
}
}
return true
}