Wiki List Support
This commit is contained in:
@@ -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)
|
return nil, fmt.Errorf("wiki data providers %s: provider %s missing source", path, name)
|
||||||
}
|
}
|
||||||
switch strings.TrimSpace(def.Source) {
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("wiki data providers %s: provider %s has unknown source %q", path, name, def.Source)
|
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 nil, nil
|
||||||
}
|
}
|
||||||
return ctx.classFeatRows(page.Row), 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":
|
case "class_summary":
|
||||||
if page.Category != "classes" {
|
if page.Category != "classes" {
|
||||||
return nil, nil
|
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 {
|
func (ctx *wikiContext) raceFeatRows(raceRow map[string]any) []map[string]any {
|
||||||
values := []any{}
|
values := []any{}
|
||||||
switch typed := fieldValue(raceRow, "FeatsTable").(type) {
|
switch typed := fieldValue(raceRow, "FeatsTable").(type) {
|
||||||
|
|||||||
@@ -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 := `<ul class="wiki-list wiki-class-skills">
|
||||||
|
{{#each ClassSkills}}
|
||||||
|
<li>{{SkillKey|link:SkillName|wiki}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>`
|
||||||
|
|
||||||
|
got, err := ctx.renderWikiTemplateString("classes.html", template, page)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("render template: %v", err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{
|
||||||
|
`<li>[[skills/athletics|Athletics]]</li>`,
|
||||||
|
`<li>[[skills/parry|Parry]]</li>`,
|
||||||
|
} {
|
||||||
|
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", `<td>{{GrantedFeats|list:"wiki-list wiki-inline-list"}}</td>`, page)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("render template: %v", err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{
|
||||||
|
`<td><ul class="wiki-list wiki-inline-list">`,
|
||||||
|
`<li>[[feat/literate|Literate]]</li>`,
|
||||||
|
`<li>[[feat/armor-proficiency-light|Armor Proficiency (light)]]</li>`,
|
||||||
|
} {
|
||||||
|
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) {
|
func TestWikiTemplateCanRenderRacialFactsAndFeatListInHTML(t *testing.T) {
|
||||||
ctx := &wikiContext{
|
ctx := &wikiContext{
|
||||||
wikiDataProviders: map[string]wikiDataProviderDefinition{
|
wikiDataProviders: map[string]wikiDataProviderDefinition{
|
||||||
|
|||||||
@@ -185,6 +185,8 @@ func (ctx *wikiContext) applyWikiTemplateFilter(value any, filter string, state
|
|||||||
return text, nil
|
return text, nil
|
||||||
case "join":
|
case "join":
|
||||||
return joinWikiTemplateValue(value, firstArg(args)), nil
|
return joinWikiTemplateValue(value, firstArg(args)), nil
|
||||||
|
case "list":
|
||||||
|
return wikiTemplateHTML(renderWikiTemplateList(value, firstArg(args))), nil
|
||||||
case "ordinal":
|
case "ordinal":
|
||||||
return ordinalWikiTableValue(numericWikiTableValue(value)), nil
|
return ordinalWikiTableValue(numericWikiTableValue(value)), nil
|
||||||
case "default":
|
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("<ul")
|
||||||
|
out.WriteString(attrClass(class))
|
||||||
|
out.WriteString(">")
|
||||||
|
for _, item := range items {
|
||||||
|
out.WriteString("<li>")
|
||||||
|
out.WriteString(renderWikiLinks(item))
|
||||||
|
out.WriteString("</li>")
|
||||||
|
}
|
||||||
|
out.WriteString("</ul>")
|
||||||
|
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 {
|
func (ctx *wikiContext) renderTemplateReference(value any, dataset string) string {
|
||||||
return ctx.renderReference(value, ctx.wikiIDMapForDataset(dataset), ctx.wikiNameResolverForDataset(dataset))
|
return ctx.renderReference(value, ctx.wikiIDMapForDataset(dataset), ctx.wikiNameResolverForDataset(dataset))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user