diff --git a/internal/topdata/wiki_data_providers.go b/internal/topdata/wiki_data_providers.go
index 05d2798..1618510 100644
--- a/internal/topdata/wiki_data_providers.go
+++ b/internal/topdata/wiki_data_providers.go
@@ -37,7 +37,7 @@ func loadWikiDataProviders(path string) (map[string]wikiDataProviderDefinition,
return nil, fmt.Errorf("wiki data providers %s: provider %s missing source", path, name)
}
switch strings.TrimSpace(def.Source) {
- case "class_progression", "class_feats", "class_summary", "race_feats", "race_name_forms":
+ case "class_progression", "class_feats", "class_skills", "class_summary", "race_feats", "race_name_forms":
default:
return nil, fmt.Errorf("wiki data providers %s: provider %s has unknown source %q", path, name, def.Source)
}
@@ -64,6 +64,11 @@ func (ctx *wikiContext) wikiDataProviderRows(name string, page wikiTemplatePage)
return nil, nil
}
return ctx.classFeatRows(page.Row), nil
+ case "class_skills":
+ if page.Category != "classes" {
+ return nil, nil
+ }
+ return ctx.classSkillRows(page.Row), nil
case "class_summary":
if page.Category != "classes" {
return nil, nil
@@ -84,6 +89,31 @@ func (ctx *wikiContext) wikiDataProviderRows(name string, page wikiTemplatePage)
}
}
+func (ctx *wikiContext) classSkillRows(classRow map[string]any) []map[string]any {
+ table := ctx.tableForValue(fieldValue(classRow, "SkillsTable"), ctx.classSkillTables)
+ if table == nil {
+ return nil
+ }
+ rows := []map[string]any{}
+ for _, source := range table.Rows {
+ if stringValue(source, "ClassSkill") != "1" {
+ continue
+ }
+ key := resolveReferenceKey(fieldValue(source, "SkillIndex"), ctx.skillIDToKey)
+ if key == "" {
+ key = resolveReferenceKey(fieldValue(source, "SkillIndex"), nil)
+ }
+ if key == "" || !ctx.canReferenceWikiKey(key) {
+ continue
+ }
+ row := cloneRowMap(source)
+ row["SkillKey"] = key
+ row["SkillName"] = ctx.resolveSkillName(key)
+ rows = append(rows, row)
+ }
+ return rows
+}
+
func (ctx *wikiContext) raceFeatRows(raceRow map[string]any) []map[string]any {
values := []any{}
switch typed := fieldValue(raceRow, "FeatsTable").(type) {
diff --git a/internal/topdata/wiki_native_test.go b/internal/topdata/wiki_native_test.go
index baf9158..419bb9a 100644
--- a/internal/topdata/wiki_native_test.go
+++ b/internal/topdata/wiki_native_test.go
@@ -311,6 +311,99 @@ func TestWikiTemplateEachBlockRendersExplicitClassProgressionTable(t *testing.T)
}
}
+func TestWikiTemplateEachBlockRendersClassSkillsAsIndividualListItems(t *testing.T) {
+ ctx := &wikiContext{
+ wikiDataProviders: map[string]wikiDataProviderDefinition{
+ "ClassSkills": {Source: "class_skills"},
+ },
+ classSkillTables: map[string]wikiTable{
+ "classes/skills:fighter": {
+ Rows: []map[string]any{
+ {"SkillIndex": "1", "ClassSkill": "1"},
+ {"SkillIndex": "2", "ClassSkill": "1"},
+ {"SkillIndex": "3", "ClassSkill": "0"},
+ },
+ },
+ },
+ skillIDToKey: map[int]string{
+ 1: "skills:athletics",
+ 2: "skills:parry",
+ 3: "skills:secret",
+ },
+ skillRows: map[string]map[string]any{
+ "skills:athletics": {"Name": "Athletics"},
+ "skills:parry": {"Name": "Parry"},
+ "skills:secret": {"Name": "Secret"},
+ },
+ }
+ page := wikiTemplatePage{
+ PageID: "classes:fighter",
+ Category: "classes",
+ Key: "classes:fighter",
+ Title: "Fighter",
+ Row: map[string]any{
+ "SkillsTable": map[string]any{"table": "classes/skills:fighter"},
+ },
+ }
+
+ template := `
+{{#each ClassSkills}}
+ - {{SkillKey|link:SkillName|wiki}}
+{{/each}}
+
`
+
+ got, err := ctx.renderWikiTemplateString("classes.html", template, page)
+ if err != nil {
+ t.Fatalf("render template: %v", err)
+ }
+ for _, want := range []string{
+ `[[skills/athletics|Athletics]]`,
+ `[[skills/parry|Parry]]`,
+ } {
+ if !strings.Contains(got, want) {
+ t.Fatalf("expected rendered output to contain %q:\n%s", want, got)
+ }
+ }
+ for _, reject := range []string{"Athletics, ", "Secret"} {
+ if strings.Contains(got, reject) {
+ t.Fatalf("expected rendered output not to contain %q:\n%s", reject, got)
+ }
+ }
+}
+
+func TestWikiTemplateListFilterRendersSliceAsListItems(t *testing.T) {
+ ctx := &wikiContext{}
+ page := wikiTemplatePage{
+ PageID: "classes:fighter",
+ Category: "classes",
+ Key: "classes:fighter",
+ Title: "Fighter",
+ Row: map[string]any{
+ "GrantedFeats": []string{
+ "[[feat/literate|Literate]]",
+ "[[feat/armor-proficiency-light|Armor Proficiency (light)]]",
+ },
+ },
+ }
+
+ got, err := ctx.renderWikiTemplateString("classes.html", `{{GrantedFeats|list:"wiki-list wiki-inline-list"}} | `, page)
+ if err != nil {
+ t.Fatalf("render template: %v", err)
+ }
+ for _, want := range []string{
+ ``,
+ `- [[feat/literate|Literate]]
`,
+ `- [[feat/armor-proficiency-light|Armor Proficiency (light)]]
`,
+ } {
+ if !strings.Contains(got, want) {
+ t.Fatalf("expected rendered output to contain %q:\n%s", want, got)
+ }
+ }
+ if strings.Contains(got, "Literate]], [[") {
+ t.Fatalf("expected list filter not to render comma-separated links:\n%s", got)
+ }
+}
+
func TestWikiTemplateCanRenderRacialFactsAndFeatListInHTML(t *testing.T) {
ctx := &wikiContext{
wikiDataProviders: map[string]wikiDataProviderDefinition{
diff --git a/internal/topdata/wiki_template_expr.go b/internal/topdata/wiki_template_expr.go
index a7d9f96..877353b 100644
--- a/internal/topdata/wiki_template_expr.go
+++ b/internal/topdata/wiki_template_expr.go
@@ -185,6 +185,8 @@ func (ctx *wikiContext) applyWikiTemplateFilter(value any, filter string, state
return text, nil
case "join":
return joinWikiTemplateValue(value, firstArg(args)), nil
+ case "list":
+ return wikiTemplateHTML(renderWikiTemplateList(value, firstArg(args))), nil
case "ordinal":
return ordinalWikiTableValue(numericWikiTableValue(value)), nil
case "default":
@@ -361,6 +363,50 @@ func joinWikiTemplateValue(value any, sep string) string {
}
}
+func renderWikiTemplateList(value any, class string) string {
+ items := wikiTemplateListItems(value)
+ if len(items) == 0 {
+ return ""
+ }
+ var out strings.Builder
+ out.WriteString("")
+ for _, item := range items {
+ out.WriteString("- ")
+ out.WriteString(renderWikiLinks(item))
+ out.WriteString("
")
+ }
+ out.WriteString(" ")
+ return out.String()
+}
+
+func wikiTemplateListItems(value any) []string {
+ switch typed := value.(type) {
+ case []string:
+ out := make([]string, 0, len(typed))
+ for _, item := range typed {
+ if text := strings.TrimSpace(item); text != "" {
+ out = append(out, text)
+ }
+ }
+ return out
+ case []any:
+ out := make([]string, 0, len(typed))
+ for _, item := range typed {
+ if text := strings.TrimSpace(stringifyWikiTemplatePlain(item)); text != "" {
+ out = append(out, text)
+ }
+ }
+ return out
+ default:
+ if text := strings.TrimSpace(stringifyWikiTemplatePlain(value)); text != "" {
+ return []string{text}
+ }
+ return nil
+ }
+}
+
func (ctx *wikiContext) renderTemplateReference(value any, dataset string) string {
return ctx.renderReference(value, ctx.wikiIDMapForDataset(dataset), ctx.wikiNameResolverForDataset(dataset))
}
|