diff --git a/README.md b/README.md index 7a4302b..796ab3f 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,7 @@ topdata: deploy_manifest: .wiki_deploy_manifest.json deploy_edit_summary: Auto-generated from native builder title_prefix_min_length: 3 + status_listing_scope: all autogen: cache: @@ -304,6 +305,11 @@ setting remains as a module-only compatibility alias when `consume_archives` is not set. Stale cleanup is scoped to roots actually touched by extracted resources, so the default module-only extraction does not prune assets. +`topdata.wiki.status_listing_scope` controls the generated wiki-status detail +pages. The default `all` writes both per-namespace listings and aggregate +`meta:wikistatus:` listings. Use `namespace` when aggregate status +lists would be too large for the target NodeBB post limit. + GFF JSON extraction can merge selected fields and lists from the existing source file instead of replacing the whole extracted document. Rules are matched by `target` relative to `paths.source`; files without a matching rule keep the diff --git a/internal/project/effective.go b/internal/project/effective.go index e2a2e70..de368c2 100644 --- a/internal/project/effective.go +++ b/internal/project/effective.go @@ -47,6 +47,7 @@ const ( DefaultTopDataWikiMarkerFormat = "html_comments" DefaultTopDataWikiPageMarkerPrefix = "sow-topdata-wiki" DefaultTopDataWikiTitlePrefixMinLength = 3 + DefaultTopDataWikiStatusListingScope = "all" DefaultWikiDeployManifest = ".wiki_deploy_manifest.json" DefaultWikiDeployEditSummary = "Auto-generated from native builder" DefaultTopDataPackageHAK = "sow_top.hak" @@ -249,6 +250,7 @@ func (p *Project) EffectiveConfig() EffectiveConfig { TemplatesDir: defaultString(p.Config.TopData.Wiki.TemplatesDir, DefaultTopDataWikiTemplatesDir), ManualSectionsDir: defaultString(p.Config.TopData.Wiki.ManualSectionsDir, DefaultTopDataWikiManualSectionsDir), TitlePrefixMinLength: defaultInt(p.Config.TopData.Wiki.TitlePrefixMinLength, DefaultTopDataWikiTitlePrefixMinLength), + StatusListingScope: defaultString(p.Config.TopData.Wiki.StatusListingScope, DefaultTopDataWikiStatusListingScope), StalePages: TopDataWikiStalePagesConfig{ Default: defaultString(p.Config.TopData.Wiki.StalePages.Default, DefaultTopDataWikiStaleDefault), LiveCleanup: defaultString(p.Config.TopData.Wiki.StalePages.LiveCleanup, DefaultTopDataWikiStaleLiveCleanup), @@ -485,6 +487,7 @@ func markMissingDefaults(provenance ConfigProvenance) { "topdata.wiki.templates_dir": DefaultTopDataWikiTemplatesDir, "topdata.wiki.manual_sections_dir": DefaultTopDataWikiManualSectionsDir, "topdata.wiki.title_prefix_min_length": "3", + "topdata.wiki.status_listing_scope": DefaultTopDataWikiStatusListingScope, "topdata.wiki.stale_pages.default": DefaultTopDataWikiStaleDefault, "topdata.wiki.stale_pages.live_cleanup": DefaultTopDataWikiStaleLiveCleanup, "topdata.wiki.managed_region.marker_format": DefaultTopDataWikiMarkerFormat, diff --git a/internal/project/project.go b/internal/project/project.go index b1c2e94..e25aaa0 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -232,6 +232,7 @@ type TopDataWikiConfig struct { TemplatesDir string `json:"templates_dir" yaml:"templates_dir"` ManualSectionsDir string `json:"manual_sections_dir" yaml:"manual_sections_dir"` TitlePrefixMinLength int `json:"title_prefix_min_length" yaml:"title_prefix_min_length"` + StatusListingScope string `json:"status_listing_scope" yaml:"status_listing_scope"` StalePages TopDataWikiStalePagesConfig `json:"stale_pages" yaml:"stale_pages"` ManagedRegion TopDataWikiManagedRegionConfig `json:"managed_region" yaml:"managed_region"` } @@ -594,6 +595,11 @@ func (p *Project) ValidateLayout() error { if effective.TopData.Wiki.TitlePrefixMinLength < 1 { failures = append(failures, fmt.Errorf("topdata.wiki.title_prefix_min_length must be at least 1")) } + switch effective.TopData.Wiki.StatusListingScope { + case "all", "namespace": + default: + failures = append(failures, fmt.Errorf("topdata.wiki.status_listing_scope %q is not supported", effective.TopData.Wiki.StatusListingScope)) + } for key, policy := range map[string]string{ "topdata.wiki.stale_pages.default": effective.TopData.Wiki.StalePages.Default, "topdata.wiki.stale_pages.live_cleanup": effective.TopData.Wiki.StalePages.LiveCleanup, diff --git a/internal/project/project_test.go b/internal/project/project_test.go index 07d97e9..da16a8e 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -278,6 +278,7 @@ topdata: deploy_manifest: wiki-manifest.json deploy_edit_summary: Custom summary title_prefix_min_length: 4 + status_listing_scope: namespace stale_pages: default: report live_cleanup: archive @@ -361,6 +362,9 @@ autogen: if got, want := effective.TopData.Wiki.TitlePrefixMinLength, 4; got != want { t.Fatalf("expected wiki title prefix minimum length %d, got %d", want, got) } + if got, want := effective.TopData.Wiki.StatusListingScope, "namespace"; got != want { + t.Fatalf("expected wiki status listing scope %q, got %q", want, got) + } if got, want := effective.Autogen.Cache.MaxAge, "30m"; got != want { t.Fatalf("expected autogen cache max age %q, got %q", want, got) } @@ -866,13 +870,14 @@ func TestValidateLayoutRejectsInvalidTopDataWikiConfig(t *testing.T) { TopData: TopDataConfig{ Source: "topdata", Wiki: TopDataWikiConfig{ - Source: "../wiki", - Renderer: "dokuwiki", - LinkStrategy: "rewrite_links", - NamespacesFile: "../namespaces.yaml", - TablesFile: "../tables.yaml", - TemplatesDir: ".", - ManualSectionsDir: "../manual", + Source: "../wiki", + Renderer: "dokuwiki", + LinkStrategy: "rewrite_links", + StatusListingScope: "global", + NamespacesFile: "../namespaces.yaml", + TablesFile: "../tables.yaml", + TemplatesDir: ".", + ManualSectionsDir: "../manual", StalePages: TopDataWikiStalePagesConfig{ Default: "delete", LiveCleanup: "delete", @@ -890,6 +895,7 @@ func TestValidateLayoutRejectsInvalidTopDataWikiConfig(t *testing.T) { "topdata.wiki.source", "topdata.wiki.renderer", "topdata.wiki.link_strategy", + "topdata.wiki.status_listing_scope", "topdata.wiki.namespaces_file", "topdata.wiki.tables_file", "topdata.wiki.templates_dir", diff --git a/internal/topdata/wiki_native.go b/internal/topdata/wiki_native.go index c2f090c..4a3fcfa 100644 --- a/internal/topdata/wiki_native.go +++ b/internal/topdata/wiki_native.go @@ -197,7 +197,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres if err := generateEntityPages(outputDir, ctx, writePage); err != nil { return wikiResult{}, err } - if err := generateStatusPages(outputDir, pageStatuses, pageTitles, p.EffectiveConfig().TopData.Wiki.ManagedNamespaces, manualSections); err != nil { + if err := generateStatusPages(outputDir, pageStatuses, pageTitles, p.EffectiveConfig().TopData.Wiki.ManagedNamespaces, p.EffectiveConfig().TopData.Wiki.StatusListingScope, manualSections); err != nil { return wikiResult{}, err } if err := indexUnregisteredWikiPages(rootDir, outputDir, p.EffectiveConfig().TopData.Wiki.StalePages.Default, &pageIndex); err != nil { @@ -1427,7 +1427,7 @@ func wikiStatusNoun(category string) string { } } -func generateStatusPages(outputDir string, pageStatuses map[string]string, pageTitles map[string]string, namespaces []string, manualSections []wikiManualSection) error { +func generateStatusPages(outputDir string, pageStatuses map[string]string, pageTitles map[string]string, namespaces []string, listingScope string, manualSections []wikiManualSection) error { summaries := buildWikiNamespaceSummaries(pageStatuses, namespaces) if err := writeWikiFile(filepath.Join(outputDir, "meta", "wikistatus.html"), renderWikiStatusReportPage(summaries, namespaces), manualSections); err != nil { return err @@ -1444,10 +1444,15 @@ func generateStatusPages(outputDir string, pageStatuses map[string]string, pageT } } } - for _, status := range []string{wikiStatusNew, wikiStatusModified, wikiStatusVanilla} { - pageIDs := filterWikiPageIDs(pageStatuses, "", status) - if err := writeWikiFile(filepath.Join(outputDir, "meta", "wikistatus", status+".html"), renderWikiStatusListingPage(wikiStatusLabels[status]+" Pages", pageIDs, pageTitles), manualSections); err != nil { - return err + if listingScope == "" { + listingScope = project.DefaultTopDataWikiStatusListingScope + } + if listingScope == "all" { + for _, status := range []string{wikiStatusNew, wikiStatusModified, wikiStatusVanilla} { + pageIDs := filterWikiPageIDs(pageStatuses, "", status) + if err := writeWikiFile(filepath.Join(outputDir, "meta", "wikistatus", status+".html"), renderWikiStatusListingPage(wikiStatusLabels[status]+" Pages", pageIDs, pageTitles), manualSections); err != nil { + return err + } } } return nil diff --git a/internal/topdata/wiki_native_test.go b/internal/topdata/wiki_native_test.go index 4d2acbf..7938338 100644 --- a/internal/topdata/wiki_native_test.go +++ b/internal/topdata/wiki_native_test.go @@ -115,6 +115,45 @@ func TestBuildNativeGeneratesAndSkipsWikiPages(t *testing.T) { } } +func TestBuildNativeCanOmitAggregateWikiStatusListings(t *testing.T) { + root := testProjectRoot(t) + mkdirAll(t, filepath.Join(root, "topdata", "data", "skills")) + writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") + writeFile(t, filepath.Join(root, "topdata", "data", "skills", "base.json"), `{ + "output": "skills.2da", + "columns": ["Label", "Name", "Description", "HideFromLevelUp", "Untrained", "KeyAbility", "ArmorCheckPenalty", "Constant"], + "rows": [ + { + "id": 0, + "key": "skills:athletics", + "Label": "Athletics", + "Name": {"tlk": {"key": "skills:athletics.name", "text": "Athletics"}}, + "Description": {"tlk": {"key": "skills:athletics.description", "text": "Ability: Strength."}}, + "HideFromLevelUp": "0", + "Untrained": "1", + "KeyAbility": "STR", + "ArmorCheckPenalty": "1", + "Constant": "SKILL_ATHLETICS" + } + ] +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "skills", "lock.json"), `{"skills:athletics":0}`+"\n") + + proj := testProject(root) + proj.Config.TopData.ReferenceBuilder = "" + proj.Config.TopData.Wiki.StatusListingScope = "namespace" + + if _, err := BuildNative(proj, nil); err != nil { + t.Fatalf("BuildNative failed: %v", err) + } + if _, err := os.Stat(filepath.Join(root, ".cache", "wiki", "pages", "meta", "wikistatus", "skills", "vanilla.html")); err != nil { + t.Fatalf("expected namespace status listing to be generated: %v", err) + } + if _, err := os.Stat(filepath.Join(root, ".cache", "wiki", "pages", "meta", "wikistatus", "vanilla.html")); !os.IsNotExist(err) { + t.Fatalf("expected aggregate status listing to be omitted, got err=%v", err) + } +} + func countGeneratedWikiHTMLFiles(t *testing.T, root string) int { t.Helper() count := 0