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 granted_on_level: -1
on_menu: 0 on_menu: 0
wiki: wiki:
source: topdata/wiki
output_root: wiki output_root: wiki
pages_dir: pages pages_dir: pages
state_file: state.json state_file: state.json
page_index_file: page-index.json
managed_namespaces: [classes, feat, itemtypes, races, skills, spells, meta] managed_namespaces: [classes, feat, itemtypes, races, skills, spells, meta]
deploy_manifest: .wiki_deploy_manifest.json deploy_manifest: .wiki_deploy_manifest.json
deploy_edit_summary: Auto-generated from native builder 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 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 title_prefix_min_length: 3
status_listing_scope: all status_listing_scope: all
@@ -325,6 +335,15 @@ topdata:
target_pattern: Guides/Alignment/{alignment} 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 `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 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 of source, but they must not resolve to the repository root (`.`). Output and
+35
View File
@@ -41,8 +41,13 @@ const (
DefaultTopDataWikiNamespacesFile = "namespaces.yaml" DefaultTopDataWikiNamespacesFile = "namespaces.yaml"
DefaultTopDataWikiTablesFile = "tables.yaml" DefaultTopDataWikiTablesFile = "tables.yaml"
DefaultTopDataWikiVisibilityFile = "visibility.yaml" DefaultTopDataWikiVisibilityFile = "visibility.yaml"
DefaultTopDataWikiDataFile = "data.yaml"
DefaultTopDataWikiPagePathsFile = "wiki.yaml"
DefaultTopDataWikiTemplatesDir = "templates" DefaultTopDataWikiTemplatesDir = "templates"
DefaultTopDataWikiPageTemplatesDir = "templates/pages"
DefaultTopDataWikiManualSectionsDir = "manual-sections" DefaultTopDataWikiManualSectionsDir = "manual-sections"
DefaultTopDataWikiManualSectionsFile = "manual-sections/default.yaml"
DefaultTopDataWikiPageIndexFile = "page-index.json"
DefaultTopDataWikiStaleDefault = "report" DefaultTopDataWikiStaleDefault = "report"
DefaultTopDataWikiStaleLiveCleanup = "archive" DefaultTopDataWikiStaleLiveCleanup = "archive"
DefaultTopDataWikiMarkerFormat = "html_comments" DefaultTopDataWikiMarkerFormat = "html_comments"
@@ -258,8 +263,13 @@ func (p *Project) EffectiveConfig() EffectiveConfig {
NamespacesFile: defaultString(p.Config.TopData.Wiki.NamespacesFile, DefaultTopDataWikiNamespacesFile), NamespacesFile: defaultString(p.Config.TopData.Wiki.NamespacesFile, DefaultTopDataWikiNamespacesFile),
TablesFile: defaultString(p.Config.TopData.Wiki.TablesFile, DefaultTopDataWikiTablesFile), TablesFile: defaultString(p.Config.TopData.Wiki.TablesFile, DefaultTopDataWikiTablesFile),
VisibilityFile: defaultString(p.Config.TopData.Wiki.VisibilityFile, DefaultTopDataWikiVisibilityFile), 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), TemplatesDir: defaultString(p.Config.TopData.Wiki.TemplatesDir, DefaultTopDataWikiTemplatesDir),
PageTemplatesDir: defaultWikiPageTemplatesDir(p.Config.TopData.Wiki),
ManualSectionsDir: defaultString(p.Config.TopData.Wiki.ManualSectionsDir, DefaultTopDataWikiManualSectionsDir), 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, AlignmentLinks: p.Config.TopData.Wiki.AlignmentLinks,
TitlePrefixMinLength: defaultInt(p.Config.TopData.Wiki.TitlePrefixMinLength, DefaultTopDataWikiTitlePrefixMinLength), TitlePrefixMinLength: defaultInt(p.Config.TopData.Wiki.TitlePrefixMinLength, DefaultTopDataWikiTitlePrefixMinLength),
StatusPages: defaultBoolPointer(p.Config.TopData.Wiki.StatusPages, DefaultTopDataWikiStatusPages), StatusPages: defaultBoolPointer(p.Config.TopData.Wiki.StatusPages, DefaultTopDataWikiStatusPages),
@@ -551,8 +561,13 @@ func markMissingDefaults(provenance ConfigProvenance) {
"topdata.wiki.namespaces_file": DefaultTopDataWikiNamespacesFile, "topdata.wiki.namespaces_file": DefaultTopDataWikiNamespacesFile,
"topdata.wiki.tables_file": DefaultTopDataWikiTablesFile, "topdata.wiki.tables_file": DefaultTopDataWikiTablesFile,
"topdata.wiki.visibility_file": DefaultTopDataWikiVisibilityFile, "topdata.wiki.visibility_file": DefaultTopDataWikiVisibilityFile,
"topdata.wiki.data_file": DefaultTopDataWikiDataFile,
"topdata.wiki.page_paths_file": DefaultTopDataWikiPagePathsFile,
"topdata.wiki.templates_dir": DefaultTopDataWikiTemplatesDir, "topdata.wiki.templates_dir": DefaultTopDataWikiTemplatesDir,
"topdata.wiki.page_templates_dir": DefaultTopDataWikiPageTemplatesDir,
"topdata.wiki.manual_sections_dir": DefaultTopDataWikiManualSectionsDir, "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.title_prefix_min_length": "3",
"topdata.wiki.status_pages": "true", "topdata.wiki.status_pages": "true",
"topdata.wiki.status_listing_scope": DefaultTopDataWikiStatusListingScope, "topdata.wiki.status_listing_scope": DefaultTopDataWikiStatusListingScope,
@@ -618,6 +633,26 @@ func defaultStringSlice(value, fallback []string) []string {
return slices.Clone(value) 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 { func defaultRequiredFields(configured map[string][]string) map[string][]string {
if len(configured) > 0 { if len(configured) > 0 {
return cloneStringSliceMap(normalizeRequiredFields(configured)) return cloneStringSliceMap(normalizeRequiredFields(configured))
+42
View File
@@ -277,8 +277,13 @@ type TopDataWikiConfig struct {
NamespacesFile string `json:"namespaces_file" yaml:"namespaces_file"` NamespacesFile string `json:"namespaces_file" yaml:"namespaces_file"`
TablesFile string `json:"tables_file" yaml:"tables_file"` TablesFile string `json:"tables_file" yaml:"tables_file"`
VisibilityFile string `json:"visibility_file" yaml:"visibility_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"` 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"` 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"` AlignmentLinks TopDataWikiAlignmentLinks `json:"alignment_links" yaml:"alignment_links"`
TitlePrefixMinLength int `json:"title_prefix_min_length" yaml:"title_prefix_min_length"` TitlePrefixMinLength int `json:"title_prefix_min_length" yaml:"title_prefix_min_length"`
StatusPages *bool `json:"status_pages,omitempty" yaml:"status_pages,omitempty"` 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.state_file": p.Config.TopData.Wiki.StateFile,
"topdata.wiki.deploy_manifest": p.Config.TopData.Wiki.DeployManifest, "topdata.wiki.deploy_manifest": p.Config.TopData.Wiki.DeployManifest,
"topdata.wiki.deploy_edit_summary": p.Config.TopData.Wiki.DeployEditSummary, "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.tables_file": p.Config.TopData.Wiki.TablesFile,
"topdata.wiki.visibility_file": p.Config.TopData.Wiki.VisibilityFile, "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.layout": p.Config.Extract.Layout,
"extract.hak_discovery": p.Config.Extract.HAKDiscovery, "extract.hak_discovery": p.Config.Extract.HAKDiscovery,
"autogen.cache.root": p.Config.Autogen.Cache.Root, "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.namespaces_file", effective.TopData.Wiki.NamespacesFile)...)
failures = append(failures, validateRelativePath("topdata.wiki.tables_file", effective.TopData.Wiki.TablesFile)...) 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.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, 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, 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)...) 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 { if err := validateOutputFileName("outputs.module_archive", filepath.Base(effective.Outputs.ModuleArchive), ".mod"); err != nil {
failures = append(failures, err) 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 { if err := validateOutputFileName("topdata.wiki.deploy_manifest", filepath.Base(effective.TopData.Wiki.DeployManifest), ".json"); err != nil {
failures = append(failures, err) 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 { switch effective.TopData.Wiki.Renderer {
case "nodebb_tiptap_html": case "nodebb_tiptap_html":
default: default:
@@ -1218,6 +1240,26 @@ func (p *Project) TopDataWikiStatePath() string {
return filepath.Join(p.TopDataWikiRootDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.StateFile)) 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 { func (p *Project) TopDataPackageHAKName() string {
return p.EffectiveConfig().TopData.PackageHAK return p.EffectiveConfig().TopData.PackageHAK
} }
+31
View File
@@ -432,6 +432,12 @@ topdata:
output_root: docs output_root: docs
pages_dir: pages pages_dir: pages
state_file: wiki-state.json 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: autogen:
cache: cache:
root: "{paths.cache}/autogen" root: "{paths.cache}/autogen"
@@ -460,6 +466,21 @@ autogen:
if got, want := proj.TopDataWikiStatePath(), filepath.Join(root, "cache", "docs", "wiki-state.json"); got != want { 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) 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 { 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) t.Fatalf("expected autogen cache path %s, got %s", want, got)
} }
@@ -1179,6 +1200,11 @@ func TestValidateLayoutRejectsInvalidTopDataWikiConfig(t *testing.T) {
TablesFile: "../tables.yaml", TablesFile: "../tables.yaml",
VisibilityFile: "../visibility.yaml", VisibilityFile: "../visibility.yaml",
TemplatesDir: ".", TemplatesDir: ".",
DataFile: "../data.yaml",
PagePathsFile: "../wiki.yaml",
PageTemplatesDir: "../templates/pages",
ManualSectionsFile: "../manual/default.yaml",
PageIndexFile: "../page-index.json",
ManualSectionsDir: "../manual", ManualSectionsDir: "../manual",
StalePages: TopDataWikiStalePagesConfig{ StalePages: TopDataWikiStalePagesConfig{
Default: "delete", Default: "delete",
@@ -1202,6 +1228,11 @@ func TestValidateLayoutRejectsInvalidTopDataWikiConfig(t *testing.T) {
"topdata.wiki.tables_file", "topdata.wiki.tables_file",
"topdata.wiki.visibility_file", "topdata.wiki.visibility_file",
"topdata.wiki.templates_dir", "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.manual_sections_dir",
"topdata.wiki.stale_pages.default", "topdata.wiki.stale_pages.default",
"topdata.wiki.stale_pages.live_cleanup", "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 var prebuiltWiki *wikiResult
if opts.BuildWiki { 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) statePath := wikiOutputStatePath(p)
outputDir := wikiOutputPagesDir(p) outputDir := wikiOutputPagesDir(p)
if stateInfo, err := os.Stat(statePath); err == nil && !newestWikiSource.After(stateInfo.ModTime()) { if stateInfo, err := os.Stat(statePath); err == nil && !newestWikiSource.After(stateInfo.ModTime()) {
+18 -6
View File
@@ -34,6 +34,7 @@ const (
type DeployWikiOptions struct { type DeployWikiOptions struct {
SourceDir string SourceDir string
PageIndexPath string
Endpoint string Endpoint string
Token string Token string
Version string Version string
@@ -119,9 +120,17 @@ func DeployWikiWithOptions(p *project.Project, opts DeployWikiOptions, progress
if progress == nil { if progress == nil {
progress = func(string) {} progress = func(string) {}
} }
if opts.SourceDir == "" { sourceDirWasDefaulted := opts.SourceDir == ""
if sourceDirWasDefaulted {
opts.SourceDir = wikiOutputPagesDir(p) 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 { if _, err := os.Stat(opts.SourceDir); err != nil {
return deployResult{}, fmt.Errorf("wiki source directory not found: %w", err) 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)) 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 { if err != nil {
return deployResult{}, err return deployResult{}, err
} }
@@ -377,12 +386,12 @@ func categoryIDsFromNamespaceEnv(declarations []wikiNamespaceDeclaration) (map[s
return categories, nil 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{}{} namespaceSet := map[string]struct{}{}
for _, namespace := range namespaces { for _, namespace := range namespaces {
namespaceSet[namespace] = struct{}{} namespaceSet[namespace] = struct{}{}
} }
pageIndex, err := loadDeployPageIndex(sourceDir) pageIndex, err := loadDeployPageIndex(sourceDir, pageIndexPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -447,8 +456,11 @@ func collectLocalPages(sourceDir string, namespaces []string) (map[string]wikiDe
return pages, nil return pages, nil
} }
func loadDeployPageIndex(sourceDir string) (map[string]wikiPageIndexEntry, error) { func loadDeployPageIndex(sourceDir, pageIndexPath string) (map[string]wikiPageIndexEntry, error) {
path := filepath.Join(filepath.Dir(sourceDir), "page-index.json") path := strings.TrimSpace(pageIndexPath)
if path == "" {
path = filepath.Join(filepath.Dir(sourceDir), "page-index.json")
}
raw, err := os.ReadFile(path) raw, err := os.ReadFile(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
+41 -3
View File
@@ -305,7 +305,7 @@ func TestCollectLocalPagesReadsGeneratedPageIDFromMarkerWhenPathIsTitleBased(t *
t.Fatalf("write source page: %v", err) t.Fatalf("write source page: %v", err)
} }
pages, err := collectLocalPages(sourceDir, []string{"feat"}) pages, err := collectLocalPages(sourceDir, "", []string{"feat"})
if err != nil { if err != nil {
t.Fatalf("collectLocalPages failed: %v", err) t.Fatalf("collectLocalPages failed: %v", err)
} }
@@ -349,7 +349,7 @@ func TestCollectLocalPagesReadsCanonicalPublicPathFromPageIndex(t *testing.T) {
t.Fatalf("write page index: %v", err) t.Fatalf("write page index: %v", err)
} }
pages, err := collectLocalPages(sourceDir, []string{"spells"}) pages, err := collectLocalPages(sourceDir, "", []string{"spells"})
if err != nil { if err != nil {
t.Fatalf("collectLocalPages failed: %v", err) 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) { func TestCollectLocalPagesReadsTitleFromPageIndexForHeadinglessHTML(t *testing.T) {
root := t.TempDir() root := t.TempDir()
sourceDir := filepath.Join(root, "pages") sourceDir := filepath.Join(root, "pages")
@@ -387,7 +425,7 @@ func TestCollectLocalPagesReadsTitleFromPageIndexForHeadinglessHTML(t *testing.T
t.Fatalf("write page index: %v", err) t.Fatalf("write page index: %v", err)
} }
pages, err := collectLocalPages(sourceDir, []string{"spells"}) pages, err := collectLocalPages(sourceDir, "", []string{"spells"})
if err != nil { if err != nil {
t.Fatalf("collectLocalPages failed: %v", err) 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) outputDir := wikiOutputPagesDir(p)
statePath := wikiOutputStatePath(p) statePath := wikiOutputStatePath(p)
digest, err := computeWikiSourceDigest(p.TopDataSourceDir()) digest, err := computeWikiSourceDigest(p)
if err != nil { if err != nil {
return wikiResult{}, err return wikiResult{}, err
} }
@@ -153,7 +153,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
} }
manualSections := loadWikiManualSections(p) manualSections := loadWikiManualSections(p)
ctx.manualSections = manualSections 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.alignmentLinkPattern = p.EffectiveConfig().TopData.Wiki.AlignmentLinks.TargetPattern
ctx.wikiTablesPath = filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.TablesFile)) ctx.wikiTablesPath = filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.TablesFile))
tableDefinitions, err := loadWikiTableDefinitions(ctx.wikiTablesPath) tableDefinitions, err := loadWikiTableDefinitions(ctx.wikiTablesPath)
@@ -161,7 +161,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
return wikiResult{}, err return wikiResult{}, err
} }
ctx.wikiTableDefinitions = tableDefinitions ctx.wikiTableDefinitions = tableDefinitions
ctx.wikiDataPath = filepath.Join(p.TopDataWikiSourceDir(), "data.yaml") ctx.wikiDataPath = p.TopDataWikiDataPath()
dataProviders, err := loadWikiDataProviders(ctx.wikiDataPath) dataProviders, err := loadWikiDataProviders(ctx.wikiDataPath)
if err != nil { if err != nil {
return wikiResult{}, err return wikiResult{}, err
@@ -171,7 +171,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
if err := ctx.loadWikiVisibility(visibilityPath); err != nil { if err := ctx.loadWikiVisibility(visibilityPath); err != nil {
return wikiResult{}, err return wikiResult{}, err
} }
pageSlugOverrides, err := loadWikiPagePathDeclarations(filepath.Join(p.TopDataWikiSourceDir(), "wiki.yaml")) pageSlugOverrides, err := loadWikiPagePathDeclarations(p.TopDataWikiPagePathsPath())
if err != nil { if err != nil {
return wikiResult{}, err 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 { if err := indexUnregisteredWikiPages(rootDir, outputDir, p.EffectiveConfig().TopData.Wiki.StalePages.Default, ctx, &pageIndex); err != nil {
return wikiResult{}, err 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 return wikiResult{}, err
} }
@@ -289,8 +289,7 @@ func wikiCachedPagesAreCurrent(outputDir string, state wikiStateDocument) bool {
func loadWikiManualSections(p *project.Project) []wikiManualSection { func loadWikiManualSections(p *project.Project) []wikiManualSection {
defaults := defaultWikiManualSections() defaults := defaultWikiManualSections()
cfg := p.EffectiveConfig().TopData.Wiki path := p.TopDataWikiManualSectionsPath()
path := filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(cfg.ManualSectionsDir), "default.yaml")
raw, err := os.ReadFile(path) raw, err := os.ReadFile(path)
if err != nil { if err != nil {
return defaults return defaults
@@ -451,9 +450,11 @@ func saveWikiState(path string, state wikiStateDocument) error {
return nil return nil
} }
func computeWikiSourceDigest(sourceDir string) (string, error) { func computeWikiSourceDigest(p *project.Project) (string, error) {
hasher := sha256.New() hasher := sha256.New()
_, _ = hasher.Write([]byte(wikiGeneratorVersion)) _, _ = hasher.Write([]byte(wikiGeneratorVersion))
sourceDir := p.TopDataSourceDir()
wikiSourceDir := p.TopDataWikiSourceDir()
files := []string{} files := []string{}
relevantSourceDirs := []string{ relevantSourceDirs := []string{
filepath.Join(sourceDir, "data", "feat"), filepath.Join(sourceDir, "data", "feat"),
@@ -462,7 +463,7 @@ func computeWikiSourceDigest(sourceDir string) (string, error) {
filepath.Join(sourceDir, "data", "baseitems"), filepath.Join(sourceDir, "data", "baseitems"),
filepath.Join(sourceDir, "data", "classes"), filepath.Join(sourceDir, "data", "classes"),
filepath.Join(sourceDir, "data", "racialtypes"), filepath.Join(sourceDir, "data", "racialtypes"),
filepath.Join(sourceDir, "wiki"), wikiSourceDir,
} }
for _, dir := range relevantSourceDirs { for _, dir := range relevantSourceDirs {
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { _ = 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 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{} newest := time.Time{}
newestPath := "" newestPath := ""
sourceDir := p.TopDataSourceDir()
relevantSourceDirs := []string{ relevantSourceDirs := []string{
filepath.Join(sourceDir, "data", "feat"), filepath.Join(sourceDir, "data", "feat"),
filepath.Join(sourceDir, "data", "skills"), 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", "baseitems"),
filepath.Join(sourceDir, "data", "classes"), filepath.Join(sourceDir, "data", "classes"),
filepath.Join(sourceDir, "data", "racialtypes"), filepath.Join(sourceDir, "data", "racialtypes"),
p.TopDataWikiSourceDir(),
} }
for _, dir := range relevantSourceDirs { for _, dir := range relevantSourceDirs {
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+86 -3
View File
@@ -7,6 +7,8 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
) )
func TestBuildNativeGeneratesAndSkipsWikiPages(t *testing.T) { func TestBuildNativeGeneratesAndSkipsWikiPages(t *testing.T) {
@@ -98,7 +100,7 @@ func TestBuildNativeGeneratesAndSkipsWikiPages(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("read state before second build: %v", err) t.Fatalf("read state before second build: %v", err)
} }
digestBefore, err := computeWikiSourceDigest(filepath.Join(root, "topdata")) digestBefore, err := computeWikiSourceDigest(proj)
if err != nil { if err != nil {
t.Fatalf("compute digest before second build: %v", err) 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") writeFile(t, filepath.Join(root, "data", "skills", "base.json"), `{"rows":[]}`+"\n")
templatePath := filepath.Join(root, "wiki", "templates", "pages", "skills.html") templatePath := filepath.Join(root, "wiki", "templates", "pages", "skills.html")
writeFile(t, templatePath, `<h1>{{title}}</h1>`+"\n") 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 { if err != nil {
t.Fatalf("compute initial digest: %v", err) t.Fatalf("compute initial digest: %v", err)
} }
writeFile(t, templatePath, `<h1>{{title}}</h1><p>Changed</p>`+"\n") writeFile(t, templatePath, `<h1>{{title}}</h1><p>Changed</p>`+"\n")
after, err := computeWikiSourceDigest(root) after, err := computeWikiSourceDigest(proj)
if err != nil { if err != nil {
t.Fatalf("compute changed digest: %v", err) 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) { func TestWikiPageIDForKeyNormalizesSlashSeparatorsOnly(t *testing.T) {
got := wikiPageIDForKey("feat:special/attacks_bull_rush") got := wikiPageIDForKey("feat:special/attacks_bull_rush")
if want := "feat:special_attacks_bull_rush"; got != want { if want := "feat:special_attacks_bull_rush"; got != want {