diff --git a/README.md b/README.md
index 169daf5..18f2fdc 100644
--- a/README.md
+++ b/README.md
@@ -326,13 +326,17 @@ the toolkit keeps its legacy default injections for compatibility.
`topdata.wiki.alignment_links.target_pattern` controls whether formatted
alignment names become internal wiki links. The pattern may use `{alignment}`
for the title-shaped alignment segment with spaces converted to underscores, and
-`{label}` for the display label:
+`{label}` for the display label. `alignment_links.link_format` defaults to
+`wiki_markup`, which emits `[[target|label]]`. Use `html_anchor` when the
+configured target should be emitted as a root URL anchor instead of being
+resolved as namespace-relative wiki markup:
```yaml
topdata:
wiki:
alignment_links:
- target_pattern: Guides/Alignment/{alignment}
+ target_pattern: /wiki/Guides/Alignment/{alignment}
+ link_format: html_anchor
```
Generated wiki input and output paths are YAML-configurable. `source` points to
diff --git a/internal/project/effective.go b/internal/project/effective.go
index 139717c..98350d3 100644
--- a/internal/project/effective.go
+++ b/internal/project/effective.go
@@ -55,6 +55,7 @@ const (
DefaultTopDataWikiTitlePrefixMinLength = 3
DefaultTopDataWikiStatusPages = true
DefaultTopDataWikiStatusListingScope = "all"
+ DefaultTopDataWikiAlignmentLinkFormat = "wiki_markup"
DefaultWikiDeployManifest = ".wiki_deploy_manifest.json"
DefaultWikiDeployEditSummary = "Auto-generated from native builder"
DefaultTopDataPackageHAK = "sow_top.hak"
@@ -251,26 +252,29 @@ func (p *Project) EffectiveConfig() EffectiveConfig {
RowGeneration: cloneTopDataRowGeneration(p.Config.TopData.RowGeneration),
ClassFeatInjections: cloneTopDataClassFeatInjections(p.Config.TopData.ClassFeatInjections),
Wiki: TopDataWikiConfig{
- OutputRoot: defaultString(p.Config.TopData.Wiki.OutputRoot, DefaultTopDataWikiOutputRoot),
- PagesDir: defaultString(p.Config.TopData.Wiki.PagesDir, DefaultTopDataWikiPagesDir),
- StateFile: defaultString(p.Config.TopData.Wiki.StateFile, DefaultTopDataWikiStateFile),
- ManagedNamespaces: defaultStringSlice(p.Config.TopData.Wiki.ManagedNamespaces, []string{"classes", "feat", "itemtypes", "races", "skills", "spells", "meta"}),
- DeployManifest: defaultString(p.Config.TopData.Wiki.DeployManifest, DefaultWikiDeployManifest),
- DeployEditSummary: defaultString(p.Config.TopData.Wiki.DeployEditSummary, DefaultWikiDeployEditSummary),
- Source: defaultString(p.Config.TopData.Wiki.Source, DefaultTopDataWikiSource),
- Renderer: defaultString(p.Config.TopData.Wiki.Renderer, DefaultTopDataWikiRenderer),
- LinkStrategy: defaultString(p.Config.TopData.Wiki.LinkStrategy, DefaultTopDataWikiLinkStrategy),
- 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,
+ OutputRoot: defaultString(p.Config.TopData.Wiki.OutputRoot, DefaultTopDataWikiOutputRoot),
+ PagesDir: defaultString(p.Config.TopData.Wiki.PagesDir, DefaultTopDataWikiPagesDir),
+ StateFile: defaultString(p.Config.TopData.Wiki.StateFile, DefaultTopDataWikiStateFile),
+ ManagedNamespaces: defaultStringSlice(p.Config.TopData.Wiki.ManagedNamespaces, []string{"classes", "feat", "itemtypes", "races", "skills", "spells", "meta"}),
+ DeployManifest: defaultString(p.Config.TopData.Wiki.DeployManifest, DefaultWikiDeployManifest),
+ DeployEditSummary: defaultString(p.Config.TopData.Wiki.DeployEditSummary, DefaultWikiDeployEditSummary),
+ Source: defaultString(p.Config.TopData.Wiki.Source, DefaultTopDataWikiSource),
+ Renderer: defaultString(p.Config.TopData.Wiki.Renderer, DefaultTopDataWikiRenderer),
+ LinkStrategy: defaultString(p.Config.TopData.Wiki.LinkStrategy, DefaultTopDataWikiLinkStrategy),
+ 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: TopDataWikiAlignmentLinks{
+ TargetPattern: strings.TrimSpace(p.Config.TopData.Wiki.AlignmentLinks.TargetPattern),
+ LinkFormat: defaultString(p.Config.TopData.Wiki.AlignmentLinks.LinkFormat, DefaultTopDataWikiAlignmentLinkFormat),
+ },
TitlePrefixMinLength: defaultInt(p.Config.TopData.Wiki.TitlePrefixMinLength, DefaultTopDataWikiTitlePrefixMinLength),
StatusPages: defaultBoolPointer(p.Config.TopData.Wiki.StatusPages, DefaultTopDataWikiStatusPages),
StatusListingScope: defaultString(p.Config.TopData.Wiki.StatusListingScope, DefaultTopDataWikiStatusListingScope),
@@ -571,6 +575,7 @@ func markMissingDefaults(provenance ConfigProvenance) {
"topdata.wiki.title_prefix_min_length": "3",
"topdata.wiki.status_pages": "true",
"topdata.wiki.status_listing_scope": DefaultTopDataWikiStatusListingScope,
+ "topdata.wiki.alignment_links.link_format": DefaultTopDataWikiAlignmentLinkFormat,
"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 0227ca5..16f01fe 100644
--- a/internal/project/project.go
+++ b/internal/project/project.go
@@ -294,6 +294,7 @@ type TopDataWikiConfig struct {
type TopDataWikiAlignmentLinks struct {
TargetPattern string `json:"target_pattern" yaml:"target_pattern"`
+ LinkFormat string `json:"link_format" yaml:"link_format"`
}
type TopDataWikiStalePagesConfig struct {
@@ -567,50 +568,51 @@ func (p *Project) ValidateLayout() error {
failures = append(failures, errors.New("at least one of paths.source, paths.assets, or topdata.source is required"))
}
for field, value := range map[string]string{
- "paths.source": p.Config.Paths.Source,
- "paths.assets": p.Config.Paths.Assets,
- "paths.build": p.Config.Paths.Build,
- "paths.cache": p.Config.Paths.Cache,
- "paths.tools": p.Config.Paths.Tools,
- "outputs.module_archive": p.Config.Outputs.ModuleArchive,
- "outputs.hak_manifest": p.Config.Outputs.HAKManifest,
- "outputs.hak_archive": p.Config.Outputs.HAKArchive,
- "generated_assets.topdata_2da": "",
- "validation.profile": p.Config.Validation.Profile,
- "scripts.source_dir": p.Config.Scripts.SourceDir,
- "scripts.cache": p.Config.Scripts.Cache,
- "scripts.compiler.path": p.Config.Scripts.Compiler.Path,
- "scripts.compiler.env.path": p.Config.Scripts.Compiler.Env.Path,
- "topdata.source": p.Config.TopData.Source,
- "topdata.build": p.Config.TopData.Build,
- "topdata.reference_builder": p.Config.TopData.ReferenceBuilder,
- "topdata.assets": p.Config.TopData.Assets,
- "topdata.compiled_2da_dir": p.Config.TopData.Compiled2DADir,
- "topdata.compiled_tlk": p.Config.TopData.CompiledTLK,
- "topdata.wiki.output_root": p.Config.TopData.Wiki.OutputRoot,
- "topdata.wiki.pages_dir": p.Config.TopData.Wiki.PagesDir,
- "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,
- "autogen.cache.max_age": p.Config.Autogen.Cache.MaxAge,
- "autogen.cache.refresh_env": p.Config.Autogen.Cache.RefreshEnv,
- "autogen.release_source.provider": p.Config.Autogen.ReleaseSource.Provider,
- "autogen.release_source.server_url_env": p.Config.Autogen.ReleaseSource.ServerURLEnv,
- "autogen.release_source.repo_env": p.Config.Autogen.ReleaseSource.RepoEnv,
+ "paths.source": p.Config.Paths.Source,
+ "paths.assets": p.Config.Paths.Assets,
+ "paths.build": p.Config.Paths.Build,
+ "paths.cache": p.Config.Paths.Cache,
+ "paths.tools": p.Config.Paths.Tools,
+ "outputs.module_archive": p.Config.Outputs.ModuleArchive,
+ "outputs.hak_manifest": p.Config.Outputs.HAKManifest,
+ "outputs.hak_archive": p.Config.Outputs.HAKArchive,
+ "generated_assets.topdata_2da": "",
+ "validation.profile": p.Config.Validation.Profile,
+ "scripts.source_dir": p.Config.Scripts.SourceDir,
+ "scripts.cache": p.Config.Scripts.Cache,
+ "scripts.compiler.path": p.Config.Scripts.Compiler.Path,
+ "scripts.compiler.env.path": p.Config.Scripts.Compiler.Env.Path,
+ "topdata.source": p.Config.TopData.Source,
+ "topdata.build": p.Config.TopData.Build,
+ "topdata.reference_builder": p.Config.TopData.ReferenceBuilder,
+ "topdata.assets": p.Config.TopData.Assets,
+ "topdata.compiled_2da_dir": p.Config.TopData.Compiled2DADir,
+ "topdata.compiled_tlk": p.Config.TopData.CompiledTLK,
+ "topdata.wiki.output_root": p.Config.TopData.Wiki.OutputRoot,
+ "topdata.wiki.pages_dir": p.Config.TopData.Wiki.PagesDir,
+ "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,
+ "topdata.wiki.alignment_links.link_format": p.Config.TopData.Wiki.AlignmentLinks.LinkFormat,
+ "extract.layout": p.Config.Extract.Layout,
+ "extract.hak_discovery": p.Config.Extract.HAKDiscovery,
+ "autogen.cache.root": p.Config.Autogen.Cache.Root,
+ "autogen.cache.max_age": p.Config.Autogen.Cache.MaxAge,
+ "autogen.cache.refresh_env": p.Config.Autogen.Cache.RefreshEnv,
+ "autogen.release_source.provider": p.Config.Autogen.ReleaseSource.Provider,
+ "autogen.release_source.server_url_env": p.Config.Autogen.ReleaseSource.ServerURLEnv,
+ "autogen.release_source.repo_env": p.Config.Autogen.ReleaseSource.RepoEnv,
} {
if strings.Contains(value, "\x00") {
failures = append(failures, fmt.Errorf("%s must not contain NUL bytes", field))
@@ -674,6 +676,11 @@ func (p *Project) ValidateLayout() error {
default:
failures = append(failures, fmt.Errorf("topdata.wiki.link_strategy %q is not supported", effective.TopData.Wiki.LinkStrategy))
}
+ switch effective.TopData.Wiki.AlignmentLinks.LinkFormat {
+ case "wiki_markup", "html_anchor":
+ default:
+ failures = append(failures, fmt.Errorf("topdata.wiki.alignment_links.link_format %q is not supported", effective.TopData.Wiki.AlignmentLinks.LinkFormat))
+ }
if effective.TopData.Wiki.TitlePrefixMinLength < 1 {
failures = append(failures, fmt.Errorf("topdata.wiki.title_prefix_min_length must be at least 1"))
}
diff --git a/internal/project/project_test.go b/internal/project/project_test.go
index 4e2e7b4..649a64f 100644
--- a/internal/project/project_test.go
+++ b/internal/project/project_test.go
@@ -1206,6 +1206,9 @@ func TestValidateLayoutRejectsInvalidTopDataWikiConfig(t *testing.T) {
ManualSectionsFile: "../manual/default.yaml",
PageIndexFile: "../page-index.json",
ManualSectionsDir: "../manual",
+ AlignmentLinks: TopDataWikiAlignmentLinks{
+ LinkFormat: "raw_url",
+ },
StalePages: TopDataWikiStalePagesConfig{
Default: "delete",
LiveCleanup: "delete",
@@ -1234,6 +1237,7 @@ func TestValidateLayoutRejectsInvalidTopDataWikiConfig(t *testing.T) {
"topdata.wiki.manual_sections_file",
"topdata.wiki.page_index_file",
"topdata.wiki.manual_sections_dir",
+ "topdata.wiki.alignment_links.link_format",
"topdata.wiki.stale_pages.default",
"topdata.wiki.stale_pages.live_cleanup",
} {
diff --git a/internal/topdata/wiki_native.go b/internal/topdata/wiki_native.go
index 5f2ccaa..e3681cd 100644
--- a/internal/topdata/wiki_native.go
+++ b/internal/topdata/wiki_native.go
@@ -121,6 +121,7 @@ type wikiContext struct {
namespaceTitles map[string]string
namespacePaths map[string]string
alignmentLinkPattern string
+ alignmentLinkFormat string
}
func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progress func(string)) (wikiResult, error) {
@@ -155,6 +156,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
ctx.manualSections = manualSections
ctx.templateDir = p.TopDataWikiPageTemplatesDir()
ctx.alignmentLinkPattern = p.EffectiveConfig().TopData.Wiki.AlignmentLinks.TargetPattern
+ ctx.alignmentLinkFormat = p.EffectiveConfig().TopData.Wiki.AlignmentLinks.LinkFormat
ctx.wikiTablesPath = filepath.Join(p.TopDataWikiSourceDir(), filepath.FromSlash(p.EffectiveConfig().TopData.Wiki.TablesFile))
tableDefinitions, err := loadWikiTableDefinitions(ctx.wikiTablesPath)
if err != nil {
diff --git a/internal/topdata/wiki_native_test.go b/internal/topdata/wiki_native_test.go
index 9b473cc..b67edc6 100644
--- a/internal/topdata/wiki_native_test.go
+++ b/internal/topdata/wiki_native_test.go
@@ -1312,6 +1312,31 @@ func TestWikiTemplateFormatsPreferredAlignmentsWithConfiguredLinks(t *testing.T)
}
}
+func TestWikiTemplateFormatsPreferredAlignmentsWithConfiguredRootAnchors(t *testing.T) {
+ ctx := &wikiContext{
+ alignmentLinkPattern: "/wiki/Guides/Alignment/{alignment}",
+ alignmentLinkFormat: "html_anchor",
+ }
+ page := wikiTemplatePage{
+ PageID: "racialtypes:tiefling",
+ Category: "racialtypes",
+ Key: "racialtypes:tiefling",
+ Title: "Tiefling",
+ Row: map[string]any{
+ "PreferredAlignments": map[string]any{"list": []any{"le"}},
+ },
+ }
+
+ got, err := ctx.renderWikiTemplateString("racialtypes.html", `{{alignments(PreferredAlignments)}}`, page)
+ if err != nil {
+ t.Fatalf("render template: %v", err)
+ }
+ want := `Lawful Evil`
+ if got != want {
+ t.Fatalf("expected %q, got %q", want, got)
+ }
+}
+
func TestWikiTemplateDescriptionFiltersExtractParagraphsAndSections(t *testing.T) {
ctx := &wikiContext{}
page := wikiTemplatePage{
diff --git a/internal/topdata/wiki_template_expr.go b/internal/topdata/wiki_template_expr.go
index 3e05f1e..e3f632d 100644
--- a/internal/topdata/wiki_template_expr.go
+++ b/internal/topdata/wiki_template_expr.go
@@ -367,7 +367,7 @@ func joinWikiTemplateValue(value any, sep string) string {
}
}
-func (ctx *wikiContext) formatWikiTemplateAlignments(value any) (string, error) {
+func (ctx *wikiContext) formatWikiTemplateAlignments(value any) (any, error) {
values, err := wikiTemplateAlignmentValues(value)
if err != nil {
return "", err
@@ -389,13 +389,21 @@ func (ctx *wikiContext) formatWikiTemplateAlignments(value any) (string, error)
if len(labels) == 0 {
return "Any", nil
}
- return strings.Join(labels, ", "), nil
+ rendered := strings.Join(labels, ", ")
+ if ctx != nil && strings.TrimSpace(ctx.alignmentLinkFormat) == "html_anchor" {
+ return wikiTemplateHTML(rendered), nil
+ }
+ return rendered, nil
}
func (ctx *wikiContext) renderWikiTemplateAlignmentLabel(label string) string {
pattern := ""
+ format := "wiki_markup"
if ctx != nil {
pattern = strings.TrimSpace(ctx.alignmentLinkPattern)
+ if strings.TrimSpace(ctx.alignmentLinkFormat) != "" {
+ format = strings.TrimSpace(ctx.alignmentLinkFormat)
+ }
}
if pattern == "" {
return label
@@ -405,6 +413,9 @@ func (ctx *wikiContext) renderWikiTemplateAlignmentLabel(label string) string {
if strings.TrimSpace(target) == "" {
return label
}
+ if format == "html_anchor" {
+ return `` + html.EscapeString(label) + ``
+ }
return "[[" + target + "|" + label + "]]"
}