package topdata import ( "fmt" "os" "strings" "gopkg.in/yaml.v3" ) type wikiDataProvidersDocument struct { Providers map[string]wikiDataProviderDefinition `json:"providers" yaml:"providers"` } type wikiDataProviderDefinition struct { Source string `json:"source" yaml:"source"` Levels string `json:"levels" yaml:"levels"` } func loadWikiDataProviders(path string) (map[string]wikiDataProviderDefinition, error) { raw, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return map[string]wikiDataProviderDefinition{}, nil } return nil, fmt.Errorf("read wiki data providers %s: %w", path, err) } var doc wikiDataProvidersDocument if err := yaml.Unmarshal(raw, &doc); err != nil { return nil, fmt.Errorf("parse wiki data providers %s: %w", path, err) } for name, def := range doc.Providers { if strings.TrimSpace(name) == "" { return nil, fmt.Errorf("wiki data providers %s: provider name must not be empty", path) } if strings.TrimSpace(def.Source) == "" { 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": default: return nil, fmt.Errorf("wiki data providers %s: provider %s has unknown source %q", path, name, def.Source) } } if doc.Providers == nil { return map[string]wikiDataProviderDefinition{}, nil } return doc.Providers, nil } func (ctx *wikiContext) wikiDataProviderRows(name string, page wikiTemplatePage) ([]map[string]any, error) { def, ok := ctx.wikiDataProviders[name] if !ok { return nil, fmt.Errorf("missing wiki data provider %q in %s", name, ctx.wikiDataPath) } switch strings.TrimSpace(def.Source) { case "class_progression": if page.Category != "classes" { return nil, nil } return ctx.classProgressionRows(wikiTableDefinition{Rows: wikiTableRowsConfig{Levels: def.Levels}}, page.Row) case "class_feats": if page.Category != "classes" { return nil, nil } return ctx.classFeatRows(page.Row), nil case "class_summary": if page.Category != "classes" { return nil, nil } return ctx.classSummaryRows(page.Row), nil case "race_feats": if page.Category != "racialtypes" { return nil, nil } return ctx.raceFeatRows(page.Row), nil case "race_name_forms": if page.Category != "racialtypes" { return nil, nil } return ctx.raceNameFormRows(page.Row), nil default: return nil, fmt.Errorf("unknown wiki data provider source %q", def.Source) } } func (ctx *wikiContext) raceFeatRows(raceRow map[string]any) []map[string]any { values := []any{} switch typed := fieldValue(raceRow, "FeatsTable").(type) { case []any: values = append(values, typed...) case []string: for _, value := range typed { values = append(values, value) } default: if table := ctx.tableForValue(typed, ctx.raceFeatTables); table != nil { for _, row := range table.Rows { values = append(values, fieldValue(row, "FeatIndex")) } } } rows := []map[string]any{} for _, value := range values { key := resolveReferenceKey(value, ctx.featIDToKey) if key == "" { key = resolveReferenceKey(value, nil) } if key == "" || !ctx.canReferenceWikiKey(key) { continue } rows = append(rows, map[string]any{ "FeatKey": key, "FeatName": ctx.resolveFeatName(key), }) } return rows } func (ctx *wikiContext) raceNameFormRows(raceRow map[string]any) []map[string]any { candidates := []struct { label string field string }{ {"Plural", "NamePlural"}, {"Converted Name", "ConverName"}, {"Lower Name", "ConverNameLower"}, } rows := []map[string]any{} for _, candidate := range candidates { if value := ctx.resolveTextValue(fieldValue(raceRow, candidate.field), nil); value != "" { rows = append(rows, map[string]any{"Label": candidate.label, "Value": value}) } } return rows }