diff --git a/README.md b/README.md index cd4b0bf..169daf5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/internal/project/effective.go b/internal/project/effective.go index b73c7ac..139717c 100644 --- a/internal/project/effective.go +++ b/internal/project/effective.go @@ -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)) diff --git a/internal/project/project.go b/internal/project/project.go index d45ce6b..0227ca5 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -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 } diff --git a/internal/project/project_test.go b/internal/project/project_test.go index a1f2810..4e2e7b4 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -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", diff --git a/internal/topdata/native.go b/internal/topdata/native.go index 054bd75..b1726af 100644 --- a/internal/topdata/native.go +++ b/internal/topdata/native.go @@ -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()) { diff --git a/internal/topdata/wiki_deploy.go b/internal/topdata/wiki_deploy.go index 57e01fa..23b8762 100644 --- a/internal/topdata/wiki_deploy.go +++ b/internal/topdata/wiki_deploy.go @@ -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) { diff --git a/internal/topdata/wiki_deploy_test.go b/internal/topdata/wiki_deploy_test.go index 15b784f..ed85b88 100644 --- a/internal/topdata/wiki_deploy_test.go +++ b/internal/topdata/wiki_deploy_test.go @@ -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 := ` + +

Acid Fog

+ +` + 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) } diff --git a/internal/topdata/wiki_native.go b/internal/topdata/wiki_native.go index a00a9dc..5f2ccaa 100644 --- a/internal/topdata/wiki_native.go +++ b/internal/topdata/wiki_native.go @@ -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 { diff --git a/internal/topdata/wiki_native_test.go b/internal/topdata/wiki_native_test.go index 23524a0..9b473cc 100644 --- a/internal/topdata/wiki_native_test.go +++ b/internal/topdata/wiki_native_test.go @@ -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, `

{{title}}

`+"\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, `

{{title}}

Changed

`+"\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"), `

{{title}}

`+"\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 {