Unhardcode

This commit is contained in:
2026-05-25 10:23:42 +02:00
parent e6fffdf041
commit 7714c068fe
9 changed files with 286 additions and 23 deletions
+19
View File
@@ -259,13 +259,23 @@ topdata:
granted_on_level: -1
on_menu: 0
wiki:
source: topdata/wiki
output_root: wiki
pages_dir: pages
state_file: state.json
page_index_file: page-index.json
managed_namespaces: [classes, feat, itemtypes, races, skills, spells, meta]
deploy_manifest: .wiki_deploy_manifest.json
deploy_edit_summary: Auto-generated from native builder
namespaces_file: namespaces.yaml
data_file: data.yaml
page_paths_file: wiki.yaml
tables_file: tables.yaml
visibility_file: visibility.yaml
templates_dir: templates
page_templates_dir: templates/pages
manual_sections_dir: manual-sections
manual_sections_file: manual-sections/default.yaml
title_prefix_min_length: 3
status_listing_scope: all
@@ -325,6 +335,15 @@ topdata:
target_pattern: Guides/Alignment/{alignment}
```
Generated wiki input and output paths are YAML-configurable. `source` points to
the wiki authoring root, `data_file`, `page_paths_file`, `tables_file`,
`visibility_file`, `page_templates_dir`, and `manual_sections_file` are resolved
under that source root, and `page_index_file`, `pages_dir`, and `state_file` are
resolved under the configured wiki output root. `templates_dir` and
`manual_sections_dir` remain as compatibility defaults for deriving
`page_templates_dir` and `manual_sections_file` when the newer explicit paths
are omitted.
`paths.source` and `paths.assets` must name real repository subtrees when they
are configured. They may be omitted for repositories that do not own that class
of source, but they must not resolve to the repository root (`.`). Output and
+35
View File
@@ -41,8 +41,13 @@ const (
DefaultTopDataWikiNamespacesFile = "namespaces.yaml"
DefaultTopDataWikiTablesFile = "tables.yaml"
DefaultTopDataWikiVisibilityFile = "visibility.yaml"
DefaultTopDataWikiDataFile = "data.yaml"
DefaultTopDataWikiPagePathsFile = "wiki.yaml"
DefaultTopDataWikiTemplatesDir = "templates"
DefaultTopDataWikiPageTemplatesDir = "templates/pages"
DefaultTopDataWikiManualSectionsDir = "manual-sections"
DefaultTopDataWikiManualSectionsFile = "manual-sections/default.yaml"
DefaultTopDataWikiPageIndexFile = "page-index.json"
DefaultTopDataWikiStaleDefault = "report"
DefaultTopDataWikiStaleLiveCleanup = "archive"
DefaultTopDataWikiMarkerFormat = "html_comments"
@@ -258,8 +263,13 @@ func (p *Project) EffectiveConfig() EffectiveConfig {
NamespacesFile: defaultString(p.Config.TopData.Wiki.NamespacesFile, DefaultTopDataWikiNamespacesFile),
TablesFile: defaultString(p.Config.TopData.Wiki.TablesFile, DefaultTopDataWikiTablesFile),
VisibilityFile: defaultString(p.Config.TopData.Wiki.VisibilityFile, DefaultTopDataWikiVisibilityFile),
DataFile: defaultString(p.Config.TopData.Wiki.DataFile, DefaultTopDataWikiDataFile),
PagePathsFile: defaultString(p.Config.TopData.Wiki.PagePathsFile, DefaultTopDataWikiPagePathsFile),
TemplatesDir: defaultString(p.Config.TopData.Wiki.TemplatesDir, DefaultTopDataWikiTemplatesDir),
PageTemplatesDir: defaultWikiPageTemplatesDir(p.Config.TopData.Wiki),
ManualSectionsDir: defaultString(p.Config.TopData.Wiki.ManualSectionsDir, DefaultTopDataWikiManualSectionsDir),
ManualSectionsFile: defaultWikiManualSectionsFile(p.Config.TopData.Wiki),
PageIndexFile: defaultString(p.Config.TopData.Wiki.PageIndexFile, DefaultTopDataWikiPageIndexFile),
AlignmentLinks: p.Config.TopData.Wiki.AlignmentLinks,
TitlePrefixMinLength: defaultInt(p.Config.TopData.Wiki.TitlePrefixMinLength, DefaultTopDataWikiTitlePrefixMinLength),
StatusPages: defaultBoolPointer(p.Config.TopData.Wiki.StatusPages, DefaultTopDataWikiStatusPages),
@@ -551,8 +561,13 @@ func markMissingDefaults(provenance ConfigProvenance) {
"topdata.wiki.namespaces_file": DefaultTopDataWikiNamespacesFile,
"topdata.wiki.tables_file": DefaultTopDataWikiTablesFile,
"topdata.wiki.visibility_file": DefaultTopDataWikiVisibilityFile,
"topdata.wiki.data_file": DefaultTopDataWikiDataFile,
"topdata.wiki.page_paths_file": DefaultTopDataWikiPagePathsFile,
"topdata.wiki.templates_dir": DefaultTopDataWikiTemplatesDir,
"topdata.wiki.page_templates_dir": DefaultTopDataWikiPageTemplatesDir,
"topdata.wiki.manual_sections_dir": DefaultTopDataWikiManualSectionsDir,
"topdata.wiki.manual_sections_file": DefaultTopDataWikiManualSectionsFile,
"topdata.wiki.page_index_file": DefaultTopDataWikiPageIndexFile,
"topdata.wiki.title_prefix_min_length": "3",
"topdata.wiki.status_pages": "true",
"topdata.wiki.status_listing_scope": DefaultTopDataWikiStatusListingScope,
@@ -618,6 +633,26 @@ func defaultStringSlice(value, fallback []string) []string {
return slices.Clone(value)
}
func defaultWikiPageTemplatesDir(cfg TopDataWikiConfig) string {
if strings.TrimSpace(cfg.PageTemplatesDir) != "" {
return strings.TrimSpace(cfg.PageTemplatesDir)
}
if strings.TrimSpace(cfg.TemplatesDir) != "" {
return filepath.ToSlash(filepath.Join(filepath.FromSlash(strings.TrimSpace(cfg.TemplatesDir)), "pages"))
}
return DefaultTopDataWikiPageTemplatesDir
}
func defaultWikiManualSectionsFile(cfg TopDataWikiConfig) string {
if strings.TrimSpace(cfg.ManualSectionsFile) != "" {
return strings.TrimSpace(cfg.ManualSectionsFile)
}
if strings.TrimSpace(cfg.ManualSectionsDir) != "" {
return filepath.ToSlash(filepath.Join(filepath.FromSlash(strings.TrimSpace(cfg.ManualSectionsDir)), "default.yaml"))
}
return DefaultTopDataWikiManualSectionsFile
}
func defaultRequiredFields(configured map[string][]string) map[string][]string {
if len(configured) > 0 {
return cloneStringSliceMap(normalizeRequiredFields(configured))
+42
View File
@@ -277,8 +277,13 @@ type TopDataWikiConfig struct {
NamespacesFile string `json:"namespaces_file" yaml:"namespaces_file"`
TablesFile string `json:"tables_file" yaml:"tables_file"`
VisibilityFile string `json:"visibility_file" yaml:"visibility_file"`
DataFile string `json:"data_file" yaml:"data_file"`
PagePathsFile string `json:"page_paths_file" yaml:"page_paths_file"`
TemplatesDir string `json:"templates_dir" yaml:"templates_dir"`
PageTemplatesDir string `json:"page_templates_dir" yaml:"page_templates_dir"`
ManualSectionsDir string `json:"manual_sections_dir" yaml:"manual_sections_dir"`
ManualSectionsFile string `json:"manual_sections_file" yaml:"manual_sections_file"`
PageIndexFile string `json:"page_index_file" yaml:"page_index_file"`
AlignmentLinks TopDataWikiAlignmentLinks `json:"alignment_links" yaml:"alignment_links"`
TitlePrefixMinLength int `json:"title_prefix_min_length" yaml:"title_prefix_min_length"`
StatusPages *bool `json:"status_pages,omitempty" yaml:"status_pages,omitempty"`
@@ -587,8 +592,17 @@ func (p *Project) ValidateLayout() error {
"topdata.wiki.state_file": p.Config.TopData.Wiki.StateFile,
"topdata.wiki.deploy_manifest": p.Config.TopData.Wiki.DeployManifest,
"topdata.wiki.deploy_edit_summary": p.Config.TopData.Wiki.DeployEditSummary,
"topdata.wiki.source": p.Config.TopData.Wiki.Source,
"topdata.wiki.namespaces_file": p.Config.TopData.Wiki.NamespacesFile,
"topdata.wiki.tables_file": p.Config.TopData.Wiki.TablesFile,
"topdata.wiki.visibility_file": p.Config.TopData.Wiki.VisibilityFile,
"topdata.wiki.data_file": p.Config.TopData.Wiki.DataFile,
"topdata.wiki.page_paths_file": p.Config.TopData.Wiki.PagePathsFile,
"topdata.wiki.templates_dir": p.Config.TopData.Wiki.TemplatesDir,
"topdata.wiki.page_templates_dir": p.Config.TopData.Wiki.PageTemplatesDir,
"topdata.wiki.manual_sections_dir": p.Config.TopData.Wiki.ManualSectionsDir,
"topdata.wiki.manual_sections_file": p.Config.TopData.Wiki.ManualSectionsFile,
"topdata.wiki.page_index_file": p.Config.TopData.Wiki.PageIndexFile,
"extract.layout": p.Config.Extract.Layout,
"extract.hak_discovery": p.Config.Extract.HAKDiscovery,
"autogen.cache.root": p.Config.Autogen.Cache.Root,
@@ -627,8 +641,13 @@ func (p *Project) ValidateLayout() error {
failures = append(failures, validateRelativePath("topdata.wiki.namespaces_file", effective.TopData.Wiki.NamespacesFile)...)
failures = append(failures, validateRelativePath("topdata.wiki.tables_file", effective.TopData.Wiki.TablesFile)...)
failures = append(failures, validateRelativePath("topdata.wiki.visibility_file", effective.TopData.Wiki.VisibilityFile)...)
failures = append(failures, validateRelativePath("topdata.wiki.data_file", effective.TopData.Wiki.DataFile)...)
failures = append(failures, validateRelativePath("topdata.wiki.page_paths_file", effective.TopData.Wiki.PagePathsFile)...)
failures = append(failures, validateTreeRootPath("topdata.wiki.templates_dir", effective.TopData.Wiki.TemplatesDir)...)
failures = append(failures, validateRelativePath("topdata.wiki.page_templates_dir", effective.TopData.Wiki.PageTemplatesDir)...)
failures = append(failures, validateTreeRootPath("topdata.wiki.manual_sections_dir", effective.TopData.Wiki.ManualSectionsDir)...)
failures = append(failures, validateRelativePath("topdata.wiki.manual_sections_file", effective.TopData.Wiki.ManualSectionsFile)...)
failures = append(failures, validateRelativePath("topdata.wiki.page_index_file", effective.TopData.Wiki.PageIndexFile)...)
failures = append(failures, validateRelativePath("autogen.cache.root", effective.Autogen.Cache.Root)...)
if err := validateOutputFileName("outputs.module_archive", filepath.Base(effective.Outputs.ModuleArchive), ".mod"); err != nil {
failures = append(failures, err)
@@ -642,6 +661,9 @@ func (p *Project) ValidateLayout() error {
if err := validateOutputFileName("topdata.wiki.deploy_manifest", filepath.Base(effective.TopData.Wiki.DeployManifest), ".json"); err != nil {
failures = append(failures, err)
}
if err := validateOutputFileName("topdata.wiki.page_index_file", filepath.Base(effective.TopData.Wiki.PageIndexFile), ".json"); err != nil {
failures = append(failures, err)
}
switch effective.TopData.Wiki.Renderer {
case "nodebb_tiptap_html":
default:
@@ -1218,6 +1240,26 @@ func (p *Project) TopDataWikiStatePath() string {
return filepath.Join(p.TopDataWikiRootDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.StateFile))
}
func (p *Project) TopDataWikiPageIndexPath() string {
return filepath.Join(p.TopDataWikiRootDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.PageIndexFile))
}
func (p *Project) TopDataWikiDataPath() string {
return filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.DataFile))
}
func (p *Project) TopDataWikiPagePathsPath() string {
return filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.PagePathsFile))
}
func (p *Project) TopDataWikiPageTemplatesDir() string {
return filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.PageTemplatesDir))
}
func (p *Project) TopDataWikiManualSectionsPath() string {
return filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.ManualSectionsFile))
}
func (p *Project) TopDataPackageHAKName() string {
return p.EffectiveConfig().TopData.PackageHAK
}
+31
View File
@@ -432,6 +432,12 @@ topdata:
output_root: docs
pages_dir: pages
state_file: wiki-state.json
page_index_file: manifest/page-index.json
source: docs-src/wiki
data_file: providers/wiki-data.yaml
page_paths_file: paths/wiki-paths.yaml
page_templates_dir: page-fragments
manual_sections_file: sections/defaults.yaml
autogen:
cache:
root: "{paths.cache}/autogen"
@@ -460,6 +466,21 @@ autogen:
if got, want := proj.TopDataWikiStatePath(), filepath.Join(root, "cache", "docs", "wiki-state.json"); got != want {
t.Fatalf("expected wiki state path %s, got %s", want, got)
}
if got, want := proj.TopDataWikiPageIndexPath(), filepath.Join(root, "cache", "docs", "manifest", "page-index.json"); got != want {
t.Fatalf("expected wiki page-index path %s, got %s", want, got)
}
if got, want := proj.TopDataWikiDataPath(), filepath.Join(root, "docs-src", "wiki", "providers", "wiki-data.yaml"); got != want {
t.Fatalf("expected wiki data path %s, got %s", want, got)
}
if got, want := proj.TopDataWikiPagePathsPath(), filepath.Join(root, "docs-src", "wiki", "paths", "wiki-paths.yaml"); got != want {
t.Fatalf("expected wiki page paths path %s, got %s", want, got)
}
if got, want := proj.TopDataWikiPageTemplatesDir(), filepath.Join(root, "docs-src", "wiki", "page-fragments"); got != want {
t.Fatalf("expected wiki page template path %s, got %s", want, got)
}
if got, want := proj.TopDataWikiManualSectionsPath(), filepath.Join(root, "docs-src", "wiki", "sections", "defaults.yaml"); got != want {
t.Fatalf("expected wiki manual sections path %s, got %s", want, got)
}
if got, want := proj.AutogenCachePath("manifest.json"), filepath.Join(root, "cache", "autogen", "manifest.json"); got != want {
t.Fatalf("expected autogen cache path %s, got %s", want, got)
}
@@ -1179,6 +1200,11 @@ func TestValidateLayoutRejectsInvalidTopDataWikiConfig(t *testing.T) {
TablesFile: "../tables.yaml",
VisibilityFile: "../visibility.yaml",
TemplatesDir: ".",
DataFile: "../data.yaml",
PagePathsFile: "../wiki.yaml",
PageTemplatesDir: "../templates/pages",
ManualSectionsFile: "../manual/default.yaml",
PageIndexFile: "../page-index.json",
ManualSectionsDir: "../manual",
StalePages: TopDataWikiStalePagesConfig{
Default: "delete",
@@ -1202,6 +1228,11 @@ func TestValidateLayoutRejectsInvalidTopDataWikiConfig(t *testing.T) {
"topdata.wiki.tables_file",
"topdata.wiki.visibility_file",
"topdata.wiki.templates_dir",
"topdata.wiki.data_file",
"topdata.wiki.page_paths_file",
"topdata.wiki.page_templates_dir",
"topdata.wiki.manual_sections_file",
"topdata.wiki.page_index_file",
"topdata.wiki.manual_sections_dir",
"topdata.wiki.stale_pages.default",
"topdata.wiki.stale_pages.live_cleanup",
+1 -1
View File
@@ -210,7 +210,7 @@ func BuildNativeWithOptions(p *project.Project, opts NativeBuildOptions, progres
}
var prebuiltWiki *wikiResult
if opts.BuildWiki {
if newestWikiSource, _, err := newestRelevantWikiSource(p.TopDataSourceDir()); err == nil && !newestWikiSource.IsZero() {
if newestWikiSource, _, err := newestRelevantWikiSource(p); err == nil && !newestWikiSource.IsZero() {
statePath := wikiOutputStatePath(p)
outputDir := wikiOutputPagesDir(p)
if stateInfo, err := os.Stat(statePath); err == nil && !newestWikiSource.After(stateInfo.ModTime()) {
+18 -6
View File
@@ -34,6 +34,7 @@ const (
type DeployWikiOptions struct {
SourceDir string
PageIndexPath string
Endpoint string
Token string
Version string
@@ -119,9 +120,17 @@ func DeployWikiWithOptions(p *project.Project, opts DeployWikiOptions, progress
if progress == nil {
progress = func(string) {}
}
if opts.SourceDir == "" {
sourceDirWasDefaulted := opts.SourceDir == ""
if sourceDirWasDefaulted {
opts.SourceDir = wikiOutputPagesDir(p)
}
if opts.PageIndexPath == "" {
if sourceDirWasDefaulted {
opts.PageIndexPath = p.TopDataWikiPageIndexPath()
} else {
opts.PageIndexPath = filepath.Join(filepath.Dir(opts.SourceDir), p.EffectiveConfig().TopData.Wiki.PageIndexFile)
}
}
if _, err := os.Stat(opts.SourceDir); err != nil {
return deployResult{}, fmt.Errorf("wiki source directory not found: %w", err)
}
@@ -172,7 +181,7 @@ func DeployWikiWithOptions(p *project.Project, opts DeployWikiOptions, progress
}
progress(fmt.Sprintf("Collecting local wiki pages from %s", opts.SourceDir))
pages, err := collectLocalPages(opts.SourceDir, namespaces)
pages, err := collectLocalPages(opts.SourceDir, opts.PageIndexPath, namespaces)
if err != nil {
return deployResult{}, err
}
@@ -377,12 +386,12 @@ func categoryIDsFromNamespaceEnv(declarations []wikiNamespaceDeclaration) (map[s
return categories, nil
}
func collectLocalPages(sourceDir string, namespaces []string) (map[string]wikiDeployPage, error) {
func collectLocalPages(sourceDir, pageIndexPath string, namespaces []string) (map[string]wikiDeployPage, error) {
namespaceSet := map[string]struct{}{}
for _, namespace := range namespaces {
namespaceSet[namespace] = struct{}{}
}
pageIndex, err := loadDeployPageIndex(sourceDir)
pageIndex, err := loadDeployPageIndex(sourceDir, pageIndexPath)
if err != nil {
return nil, err
}
@@ -447,8 +456,11 @@ func collectLocalPages(sourceDir string, namespaces []string) (map[string]wikiDe
return pages, nil
}
func loadDeployPageIndex(sourceDir string) (map[string]wikiPageIndexEntry, error) {
path := filepath.Join(filepath.Dir(sourceDir), "page-index.json")
func loadDeployPageIndex(sourceDir, pageIndexPath string) (map[string]wikiPageIndexEntry, error) {
path := strings.TrimSpace(pageIndexPath)
if path == "" {
path = filepath.Join(filepath.Dir(sourceDir), "page-index.json")
}
raw, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
+41 -3
View File
@@ -305,7 +305,7 @@ func TestCollectLocalPagesReadsGeneratedPageIDFromMarkerWhenPathIsTitleBased(t *
t.Fatalf("write source page: %v", err)
}
pages, err := collectLocalPages(sourceDir, []string{"feat"})
pages, err := collectLocalPages(sourceDir, "", []string{"feat"})
if err != nil {
t.Fatalf("collectLocalPages failed: %v", err)
}
@@ -349,7 +349,7 @@ func TestCollectLocalPagesReadsCanonicalPublicPathFromPageIndex(t *testing.T) {
t.Fatalf("write page index: %v", err)
}
pages, err := collectLocalPages(sourceDir, []string{"spells"})
pages, err := collectLocalPages(sourceDir, "", []string{"spells"})
if err != nil {
t.Fatalf("collectLocalPages failed: %v", err)
}
@@ -358,6 +358,44 @@ func TestCollectLocalPagesReadsCanonicalPublicPathFromPageIndex(t *testing.T) {
}
}
func TestCollectLocalPagesUsesConfiguredPageIndexPath(t *testing.T) {
root := t.TempDir()
sourceDir := filepath.Join(root, "pages")
if err := os.MkdirAll(filepath.Join(sourceDir, "spells"), 0755); err != nil {
t.Fatalf("create source dir: %v", err)
}
generated := `<!-- sow-topdata-wiki:page=spells:acid_fog -->
<!-- sow-topdata-wiki:managed:start hash="sha256:local" -->
<h1>Acid Fog</h1>
<!-- sow-topdata-wiki:managed:end -->
`
if err := os.WriteFile(filepath.Join(sourceDir, "spells", "Acid_Fog.html"), []byte(generated), 0644); err != nil {
t.Fatalf("write source page: %v", err)
}
indexPath := filepath.Join(root, "manifest", "pages.json")
if err := saveWikiPageIndex(indexPath, wikiPageIndex{
Version: wikiGeneratorVersion,
Pages: []wikiPageIndexEntry{
{
PageID: "spells:acid_fog",
Title: "Acid Fog",
PublicPath: "Magic/Acid_Fog",
OutputPath: "pages/spells/Acid_Fog.html",
},
},
}); err != nil {
t.Fatalf("write page index: %v", err)
}
pages, err := collectLocalPages(sourceDir, indexPath, []string{"spells"})
if err != nil {
t.Fatalf("collectLocalPages failed: %v", err)
}
if got := pages["spells:acid_fog"].PublicPath; got != "Magic/Acid_Fog" {
t.Fatalf("expected configured page-index public path, got %q", got)
}
}
func TestCollectLocalPagesReadsTitleFromPageIndexForHeadinglessHTML(t *testing.T) {
root := t.TempDir()
sourceDir := filepath.Join(root, "pages")
@@ -387,7 +425,7 @@ func TestCollectLocalPagesReadsTitleFromPageIndexForHeadinglessHTML(t *testing.T
t.Fatalf("write page index: %v", err)
}
pages, err := collectLocalPages(sourceDir, []string{"spells"})
pages, err := collectLocalPages(sourceDir, "", []string{"spells"})
if err != nil {
t.Fatalf("collectLocalPages failed: %v", err)
}
+13 -10
View File
@@ -131,7 +131,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
outputDir := wikiOutputPagesDir(p)
statePath := wikiOutputStatePath(p)
digest, err := computeWikiSourceDigest(p.TopDataSourceDir())
digest, err := computeWikiSourceDigest(p)
if err != nil {
return wikiResult{}, err
}
@@ -153,7 +153,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
}
manualSections := loadWikiManualSections(p)
ctx.manualSections = manualSections
ctx.templateDir = filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.TemplatesDir), "pages")
ctx.templateDir = p.TopDataWikiPageTemplatesDir()
ctx.alignmentLinkPattern = p.EffectiveConfig().TopData.Wiki.AlignmentLinks.TargetPattern
ctx.wikiTablesPath = filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.TablesFile))
tableDefinitions, err := loadWikiTableDefinitions(ctx.wikiTablesPath)
@@ -161,7 +161,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
return wikiResult{}, err
}
ctx.wikiTableDefinitions = tableDefinitions
ctx.wikiDataPath = filepath.Join(p.TopDataWikiSourceDir(), "data.yaml")
ctx.wikiDataPath = p.TopDataWikiDataPath()
dataProviders, err := loadWikiDataProviders(ctx.wikiDataPath)
if err != nil {
return wikiResult{}, err
@@ -171,7 +171,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
if err := ctx.loadWikiVisibility(visibilityPath); err != nil {
return wikiResult{}, err
}
pageSlugOverrides, err := loadWikiPagePathDeclarations(filepath.Join(p.TopDataWikiSourceDir(), "wiki.yaml"))
pageSlugOverrides, err := loadWikiPagePathDeclarations(p.TopDataWikiPagePathsPath())
if err != nil {
return wikiResult{}, err
}
@@ -243,7 +243,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
if err := indexUnregisteredWikiPages(rootDir, outputDir, p.EffectiveConfig().TopData.Wiki.StalePages.Default, ctx, &pageIndex); err != nil {
return wikiResult{}, err
}
if err := saveWikiPageIndex(filepath.Join(rootDir, "page-index.json"), pageIndex); err != nil {
if err := saveWikiPageIndex(p.TopDataWikiPageIndexPath(), pageIndex); err != nil {
return wikiResult{}, err
}
@@ -289,8 +289,7 @@ func wikiCachedPagesAreCurrent(outputDir string, state wikiStateDocument) bool {
func loadWikiManualSections(p *project.Project) []wikiManualSection {
defaults := defaultWikiManualSections()
cfg := p.EffectiveConfig().TopData.Wiki
path := filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(cfg.ManualSectionsDir), "default.yaml")
path := p.TopDataWikiManualSectionsPath()
raw, err := os.ReadFile(path)
if err != nil {
return defaults
@@ -451,9 +450,11 @@ func saveWikiState(path string, state wikiStateDocument) error {
return nil
}
func computeWikiSourceDigest(sourceDir string) (string, error) {
func computeWikiSourceDigest(p *project.Project) (string, error) {
hasher := sha256.New()
_, _ = hasher.Write([]byte(wikiGeneratorVersion))
sourceDir := p.TopDataSourceDir()
wikiSourceDir := p.TopDataWikiSourceDir()
files := []string{}
relevantSourceDirs := []string{
filepath.Join(sourceDir, "data", "feat"),
@@ -462,7 +463,7 @@ func computeWikiSourceDigest(sourceDir string) (string, error) {
filepath.Join(sourceDir, "data", "baseitems"),
filepath.Join(sourceDir, "data", "classes"),
filepath.Join(sourceDir, "data", "racialtypes"),
filepath.Join(sourceDir, "wiki"),
wikiSourceDir,
}
for _, dir := range relevantSourceDirs {
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
@@ -503,9 +504,10 @@ func computeWikiSourceDigest(sourceDir string) (string, error) {
return fmt.Sprintf("%x", hasher.Sum(nil)), nil
}
func newestRelevantWikiSource(sourceDir string) (time.Time, string, error) {
func newestRelevantWikiSource(p *project.Project) (time.Time, string, error) {
newest := time.Time{}
newestPath := ""
sourceDir := p.TopDataSourceDir()
relevantSourceDirs := []string{
filepath.Join(sourceDir, "data", "feat"),
filepath.Join(sourceDir, "data", "skills"),
@@ -513,6 +515,7 @@ func newestRelevantWikiSource(sourceDir string) (time.Time, string, error) {
filepath.Join(sourceDir, "data", "baseitems"),
filepath.Join(sourceDir, "data", "classes"),
filepath.Join(sourceDir, "data", "racialtypes"),
p.TopDataWikiSourceDir(),
}
for _, dir := range relevantSourceDirs {
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+86 -3
View File
@@ -7,6 +7,8 @@ import (
"strings"
"testing"
"time"
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
)
func TestBuildNativeGeneratesAndSkipsWikiPages(t *testing.T) {
@@ -98,7 +100,7 @@ func TestBuildNativeGeneratesAndSkipsWikiPages(t *testing.T) {
if err != nil {
t.Fatalf("read state before second build: %v", err)
}
digestBefore, err := computeWikiSourceDigest(filepath.Join(root, "topdata"))
digestBefore, err := computeWikiSourceDigest(proj)
if err != nil {
t.Fatalf("compute digest before second build: %v", err)
}
@@ -1998,13 +2000,20 @@ func TestWikiSourceDigestIncludesTemplates(t *testing.T) {
writeFile(t, filepath.Join(root, "data", "skills", "base.json"), `{"rows":[]}`+"\n")
templatePath := filepath.Join(root, "wiki", "templates", "pages", "skills.html")
writeFile(t, templatePath, `<h1>{{title}}</h1>`+"\n")
proj := &project.Project{
Root: root,
Config: project.Config{
Module: project.ModuleConfig{Name: "Test", ResRef: "test"},
TopData: project.TopDataConfig{Source: ".", Wiki: project.TopDataWikiConfig{Source: "wiki"}},
},
}
before, err := computeWikiSourceDigest(root)
before, err := computeWikiSourceDigest(proj)
if err != nil {
t.Fatalf("compute initial digest: %v", err)
}
writeFile(t, templatePath, `<h1>{{title}}</h1><p>Changed</p>`+"\n")
after, err := computeWikiSourceDigest(root)
after, err := computeWikiSourceDigest(proj)
if err != nil {
t.Fatalf("compute changed digest: %v", err)
}
@@ -2013,6 +2022,80 @@ func TestWikiSourceDigestIncludesTemplates(t *testing.T) {
}
}
func TestBuildWikiHonorsConfiguredWikiPaths(t *testing.T) {
root := t.TempDir()
mkdirAll(t, filepath.Join(root, "topdata", "data", "skills"))
mkdirAll(t, filepath.Join(root, "docs", "wiki", "page-fragments"))
mkdirAll(t, filepath.Join(root, "docs", "wiki", "config"))
mkdirAll(t, filepath.Join(root, "docs", "wiki", "sections"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "skills", "base.json"), `{
"columns": ["Label", "Name"],
"rows": [
{
"id": 0,
"key": "skills:athletics",
"Label": "Athletics",
"Name": {"tlk": {"key": "skills:athletics.name", "text": "Athletics"}}
}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "skills", "lock.json"), `{"skills:athletics":0}`+"\n")
writeFile(t, filepath.Join(root, "docs", "wiki", "page-fragments", "skills.html"), `<h1>{{title}}</h1>`+"\n")
writeFile(t, filepath.Join(root, "docs", "wiki", "config", "providers.yaml"), "providers: {}\n")
writeFile(t, filepath.Join(root, "docs", "wiki", "config", "paths.yaml"), "page_paths: {}\n")
writeFile(t, filepath.Join(root, "docs", "wiki", "config", "tables.yaml"), "tables: {}\n")
writeFile(t, filepath.Join(root, "docs", "wiki", "config", "visibility.yaml"), "datasets: {}\n")
writeFile(t, filepath.Join(root, "docs", "wiki", "sections", "defaults.yaml"), `
manual_sections:
- id: user_top
heading: Custom Top
`)
statusPages := false
proj := &project.Project{
Root: root,
Config: project.Config{
Module: project.ModuleConfig{Name: "Test", ResRef: "test"},
Paths: project.PathConfig{Build: "build", Cache: ".cache"},
TopData: project.TopDataConfig{
Source: "topdata",
Build: ".cache",
Wiki: project.TopDataWikiConfig{
Source: "docs/wiki",
DataFile: "config/providers.yaml",
PagePathsFile: "config/paths.yaml",
TablesFile: "config/tables.yaml",
VisibilityFile: "config/visibility.yaml",
PageTemplatesDir: "page-fragments",
ManualSectionsFile: "sections/defaults.yaml",
PageIndexFile: "manifest/pages.json",
StatusPages: &statusPages,
},
},
},
}
result, err := buildWiki(proj, BuildResult{}, true, nil)
if err != nil {
t.Fatalf("build wiki with configured paths: %v", err)
}
if result.PageCount != 1 {
t.Fatalf("expected generated skill page, got %d", result.PageCount)
}
pagePath := filepath.Join(root, ".cache", "wiki", "pages", "skills", "Athletics.html")
pageRaw, err := os.ReadFile(pagePath)
if err != nil {
t.Fatalf("read configured-path wiki page: %v", err)
}
if !strings.Contains(string(pageRaw), "Custom Top") {
t.Fatalf("expected configured manual section file to be used, got:\n%s", pageRaw)
}
if _, err := os.Stat(filepath.Join(root, ".cache", "wiki", "manifest", "pages.json")); err != nil {
t.Fatalf("expected configured page-index file: %v", err)
}
}
func TestWikiPageIDForKeyNormalizesSlashSeparatorsOnly(t *testing.T) {
got := wikiPageIDForKey("feat:special/attacks_bull_rush")
if want := "feat:special_attacks_bull_rush"; got != want {