Canonical path adherence (#10)

Reviewed-on: https://gitea.westgate.pw/ShadowsOverWestgate/sow-tools/pulls/10
Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
This commit is contained in:
2026-05-22 12:37:00 +02:00
committed by archvillainette
parent f4d92a9766
commit 835d1153f0
6 changed files with 218 additions and 128 deletions
+47 -36
View File
@@ -56,8 +56,7 @@ type wikiPageIndexEntry struct {
SourceDataset string `json:"source_dataset"`
SourceKey string `json:"source_key"`
Title string `json:"title"`
PublicSlug string `json:"public_slug"`
PublicTarget string `json:"public_target"`
PublicPath string `json:"public_path"`
Hash string `json:"hash"`
OutputPath string `json:"output_path"`
EditPolicy string `json:"edit_policy"`
@@ -119,7 +118,7 @@ type wikiContext struct {
wikiDataProviders map[string]wikiDataProviderDefinition
visibilityPolicy *wikiVisibilityDefinitions
visibleWikiKeys map[string]bool
pageSlugOverrides map[string]string
namespaceTitles map[string]string
}
func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progress func(string)) (wikiResult, error) {
@@ -173,7 +172,12 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
if err != nil {
return wikiResult{}, err
}
ctx.pageSlugOverrides = pageSlugOverrides
_ = pageSlugOverrides
namespaceDeclarations, err := loadWikiNamespaceDeclarations(p)
if err != nil {
return wikiResult{}, err
}
ctx.namespaceTitles = wikiNamespaceTitles(namespaceDeclarations)
if err := os.RemoveAll(outputDir); err != nil {
return wikiResult{}, fmt.Errorf("clean wiki output: %w", err)
@@ -192,8 +196,8 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
return err
}
namespace := strings.SplitN(pageID, ":", 2)[0]
publicSlug := ctx.publicWikiPageSlug(pageID, title)
content = renderNodeBBManagedHTML(pageID, publicSlug, content, manualSections)
publicPath := ctx.publicWikiPath(namespace, title)
content = renderNodeBBManagedHTML(pageID, content, manualSections)
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
return err
}
@@ -209,8 +213,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
SourceDataset: categoryFromWikiNamespace(namespace),
SourceKey: pageID,
Title: title,
PublicSlug: publicSlug,
PublicTarget: wikiPublicTarget(namespace, publicSlug),
PublicPath: publicPath,
Hash: computeManagedHash(content),
OutputPath: filepath.ToSlash(rel),
EditPolicy: wikiEditPolicy(namespace),
@@ -327,7 +330,7 @@ func saveWikiPageIndex(path string, index wikiPageIndex) error {
func validateWikiPageIndex(index wikiPageIndex) error {
pageIDs := map[string]string{}
outputPaths := map[string]string{}
publicTargets := map[string]string{}
publicPaths := map[string]string{}
for _, entry := range index.Pages {
if previous, ok := pageIDs[entry.PageID]; ok {
return fmt.Errorf("duplicate wiki page-index page_id %q for %s and %s", entry.PageID, previous, entry.OutputPath)
@@ -335,11 +338,11 @@ func validateWikiPageIndex(index wikiPageIndex) error {
if previous, ok := outputPaths[entry.OutputPath]; ok {
return fmt.Errorf("duplicate wiki page-index output_path %q for %s and %s", entry.OutputPath, previous, entry.PageID)
}
if entry.PublicTarget != "" {
if previous, ok := publicTargets[entry.PublicTarget]; ok {
return fmt.Errorf("duplicate wiki page-index public_target %q for %s and %s", entry.PublicTarget, previous, entry.PageID)
if entry.PublicPath != "" {
if previous, ok := publicPaths[entry.PublicPath]; ok {
return fmt.Errorf("duplicate wiki page-index public_path %q for %s and %s", entry.PublicPath, previous, entry.PageID)
}
publicTargets[entry.PublicTarget] = entry.PageID
publicPaths[entry.PublicPath] = entry.PageID
}
pageIDs[entry.PageID] = entry.OutputPath
outputPaths[entry.OutputPath] = entry.PageID
@@ -383,8 +386,7 @@ func indexUnregisteredWikiPages(rootDir, outputDir, stalePolicy string, index *w
SourceDataset: categoryFromWikiNamespace(namespace),
SourceKey: pageID,
Title: extractHTMLTitle(string(raw), pageID),
PublicSlug: wikiSlugifyTitle(extractHTMLTitle(string(raw), pageID)),
PublicTarget: wikiPublicTarget(namespace, wikiSlugifyTitle(extractHTMLTitle(string(raw), pageID))),
PublicPath: (&wikiContext{}).publicWikiPath(namespace, extractHTMLTitle(string(raw), pageID)),
Hash: computeManagedHash(string(raw)),
OutputPath: filepath.ToSlash(relToRoot),
EditPolicy: wikiEditPolicy(namespace),
@@ -1532,7 +1534,7 @@ func writeWikiFile(path, content string, manualSections []wikiManualSection) err
return err
}
pageID := wikiRelPathToPageID(path)
content = renderNodeBBManagedHTML(pageID, wikiSlugifyTitle(extractHTMLTitle(content, pageID)), content, manualSections)
content = renderNodeBBManagedHTML(pageID, content, manualSections)
return os.WriteFile(path, []byte(ensureTrailingNewline(content)), 0o644)
}
@@ -1636,6 +1638,18 @@ func filterWikiPageIDs(pageStatuses map[string]string, namespace, status string)
return pageIDs
}
func wikiNamespaceTitles(declarations []wikiNamespaceDeclaration) map[string]string {
titles := map[string]string{}
for _, declaration := range declarations {
id := strings.TrimSpace(declaration.ID)
title := strings.TrimSpace(declaration.Title)
if id != "" && title != "" {
titles[id] = title
}
}
return titles
}
func wikiPageIDForKey(key string) string {
if key == "" {
return ""
@@ -1655,17 +1669,10 @@ func wikiPageIDForKey(key string) string {
if namespace == "" {
return ""
}
value = strings.NewReplacer("/", ":", "\\", ":", "_", ":").Replace(value)
value = strings.NewReplacer("/", ":", "\\", ":").Replace(value)
return namespace + ":" + value
}
func (ctx *wikiContext) publicWikiPageSlug(pageID, title string) string {
if slug := ctx.pageSlugOverrides[pageID]; slug != "" {
return slug
}
return wikiSlugifyTitle(title)
}
func (ctx *wikiContext) publicWikiTargetForKey(key, fallbackTitle string) string {
pageID := wikiPageIDForKey(key)
if pageID == "" {
@@ -1683,19 +1690,21 @@ func (ctx *wikiContext) publicWikiTargetForKey(key, fallbackTitle string) string
title = key
}
namespace := strings.SplitN(pageID, ":", 2)[0]
return wikiPublicTarget(namespace, ctx.publicWikiPageSlug(pageID, title))
return ctx.publicWikiPath(namespace, title)
}
func wikiPublicTarget(namespace, slug string) string {
namespace = strings.TrimSpace(namespace)
slug = strings.TrimSpace(slug)
if namespace == "" {
return slug
func (ctx *wikiContext) publicWikiPath(namespace, title string) string {
namespaceTitle := namespaceTitle(namespace)
if ctx != nil && ctx.namespaceTitles != nil {
if declared := strings.TrimSpace(ctx.namespaceTitles[namespace]); declared != "" {
namespaceTitle = declared
}
}
if slug == "" {
return namespace
segments := []string{namespaceTitle}
for _, part := range strings.Split(title, " :: ") {
segments = append(segments, part)
}
return namespace + "/" + slug
return canonicalWikiPath(segments...)
}
func wikiSlugifyTitle(value string) string {
@@ -1719,7 +1728,9 @@ func wikiRelPathToPageID(path string) string {
func namespaceTitle(namespace string) string {
switch namespace {
case "itemtypes":
return "Itemtypes"
return "Item Types"
case "feat":
return "Feats"
default:
return strings.Title(namespace)
}
@@ -1924,14 +1935,14 @@ func renderNodeBBManagedMarkdown(pageID, dokuText string) string {
return ensureTrailingNewline(strings.Join(parts, "\n"))
}
func renderNodeBBManagedHTML(pageID, publicSlug, sourceText string, manualSections []wikiManualSection) string {
func renderNodeBBManagedHTML(pageID, sourceText string, manualSections []wikiManualSection) string {
body := sourceText
if !strings.Contains(sourceText, "<h1") {
body = dokuWikiToNodeBBHTML(sourceText)
}
hash := computeManagedHash(body)
parts := []string{
"<!-- sow-topdata-wiki:page=" + pageID + " wiki_slug=" + publicSlug + " -->",
"<!-- sow-topdata-wiki:page=" + pageID + " -->",
"<!-- sow-topdata-wiki:managed:start hash=\"sha256:" + hash + "\" -->",
strings.TrimSpace(body),
"<!-- sow-topdata-wiki:managed:end -->",