Expose template options
This commit is contained in:
@@ -18,6 +18,7 @@ type wikiDataProviderDefinition struct {
|
||||
Levels string `json:"levels" yaml:"levels"`
|
||||
ClassFeats wikiDataProviderClassFeatConfig `json:"class_feats" yaml:"class_feats"`
|
||||
Attacks wikiDataProviderAttacksConfig `json:"attacks" yaml:"attacks"`
|
||||
Facts wikiDataProviderFactsConfig `json:"facts" yaml:"facts"`
|
||||
Provider string `json:"provider" yaml:"provider"`
|
||||
Columns int `json:"columns" yaml:"columns"`
|
||||
ItemsPerColumn int `json:"items_per_column" yaml:"items_per_column"`
|
||||
@@ -32,6 +33,17 @@ type wikiDataProviderProjectionField struct {
|
||||
When string `json:"when" yaml:"when"`
|
||||
}
|
||||
|
||||
type wikiDataProviderFactsConfig struct {
|
||||
Rows []wikiDataProviderFactRow `json:"rows" yaml:"rows"`
|
||||
}
|
||||
|
||||
type wikiDataProviderFactRow struct {
|
||||
Key string `json:"key" yaml:"key"`
|
||||
Label string `json:"label" yaml:"label"`
|
||||
Value string `json:"value" yaml:"value"`
|
||||
When string `json:"when" yaml:"when"`
|
||||
}
|
||||
|
||||
type wikiDataProviderAttacksConfig struct {
|
||||
Field string `json:"field" yaml:"field"`
|
||||
CountField string `json:"count_field" yaml:"count_field"`
|
||||
@@ -106,6 +118,18 @@ func loadWikiDataProviders(path string) (map[string]wikiDataProviderDefinition,
|
||||
if def.ItemsPerColumn <= 0 {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s items_per_column must be a positive integer", path, name)
|
||||
}
|
||||
case "facts":
|
||||
if len(def.Facts.Rows) == 0 {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s facts source requires rows", path, name)
|
||||
}
|
||||
for index, row := range def.Facts.Rows {
|
||||
if strings.TrimSpace(row.Label) == "" {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s facts row %d missing label", path, name, index+1)
|
||||
}
|
||||
if strings.TrimSpace(row.Value) == "" {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s facts row %d missing value", path, name, index+1)
|
||||
}
|
||||
}
|
||||
case "class_feats", "class_skills", "class_summary", "class_spellbook", "race_feats", "race_name_forms":
|
||||
if len(def.ClassFeats.Fields) > 0 {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s class feat projections require source class_progression", path, name)
|
||||
@@ -148,6 +172,8 @@ func (ctx *wikiContext) wikiDataProviderRows(name string, page wikiTemplatePage)
|
||||
return nil, nil
|
||||
}
|
||||
return ctx.classProgressionRows(def.Levels, def.ClassFeats.Fields, def.Attacks, page.Key, page.Row)
|
||||
case "facts":
|
||||
return ctx.wikiFactRows(name, def.Facts, page)
|
||||
case "class_feats":
|
||||
if page.Category != "classes" {
|
||||
return nil, nil
|
||||
@@ -183,6 +209,80 @@ func (ctx *wikiContext) wikiDataProviderRows(name string, page wikiTemplatePage)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) wikiFactRows(providerName string, cfg wikiDataProviderFactsConfig, page wikiTemplatePage) ([]map[string]any, error) {
|
||||
out := []map[string]any{}
|
||||
renderCtx := wikiTableRenderContext{Page: page, TableName: providerName, Path: ctx.wikiDataPath}
|
||||
for index, spec := range cfg.Rows {
|
||||
if strings.TrimSpace(spec.When) != "" {
|
||||
value, err := ctx.evalWikiDataProviderTemplate(spec.When, page.Row, renderCtx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("facts provider %s row %d condition %q: %w", providerName, index+1, spec.When, err)
|
||||
}
|
||||
if !truthyWikiTableValue(value) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
value, err := ctx.evalWikiDataProviderTemplate(spec.Value, page.Row, renderCtx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("facts provider %s row %d value %q: %w", providerName, index+1, spec.Value, err)
|
||||
}
|
||||
text := stringifyWikiTableValue(value)
|
||||
if strings.TrimSpace(text) == "" || strings.TrimSpace(text) == nullValue {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(spec.Key)
|
||||
if key == "" {
|
||||
key = wikiFactKeyFromLabel(spec.Label)
|
||||
}
|
||||
out = append(out, map[string]any{
|
||||
"Key": key,
|
||||
"Label": spec.Label,
|
||||
"Value": text,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) evalWikiDataProviderTemplate(template string, row map[string]any, renderCtx wikiTableRenderContext) (any, error) {
|
||||
template = strings.TrimSpace(template)
|
||||
if strings.HasPrefix(template, "{{") && strings.HasSuffix(template, "}}") {
|
||||
expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(template, "{{"), "}}"))
|
||||
parser := wikiExprParser{tokens: tokenizeWikiExpr(expr), row: row, ctx: ctx, renderCtx: renderCtx, allowMissingFields: true}
|
||||
value, err := parser.parseComparison()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parser.peek().kind != wikiExprEOF {
|
||||
return nil, fmt.Errorf("unexpected token %q", parser.peek().text)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
if strings.Contains(template, "{{") {
|
||||
return nil, fmt.Errorf("mixed literal/expression templates are not supported")
|
||||
}
|
||||
return template, nil
|
||||
}
|
||||
|
||||
func wikiFactKeyFromLabel(label string) string {
|
||||
key := strings.ToLower(strings.TrimSpace(label))
|
||||
key = strings.Map(func(r rune) rune {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
return r
|
||||
case r >= '0' && r <= '9':
|
||||
return r
|
||||
case r == ' ' || r == '-' || r == '_':
|
||||
return '_'
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}, key)
|
||||
for strings.Contains(key, "__") {
|
||||
key = strings.ReplaceAll(key, "__", "_")
|
||||
}
|
||||
return strings.Trim(key, "_")
|
||||
}
|
||||
|
||||
func validateWikiDataProviderAttacks(path, provider string, cfg wikiDataProviderAttacksConfig) error {
|
||||
if strings.TrimSpace(cfg.Mode) == "" {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user