claude: fold in old sow-tools

This commit is contained in:
2026-06-13 09:49:29 +02:00
parent d4a435883b
commit 56c67132f3
112 changed files with 70812 additions and 74 deletions
+407
View File
@@ -0,0 +1,407 @@
package topdata
import (
"fmt"
"os"
"sort"
"strings"
"gopkg.in/yaml.v3"
)
type wikiVisibilityDefinitions struct {
ReferenceSets map[string]wikiVisibilityReferenceSet `json:"reference_sets" yaml:"reference_sets"`
Datasets map[string]wikiVisibilityDataset `json:"datasets" yaml:"datasets"`
}
type wikiVisibilityDataset struct {
Eligibility wikiVisibilityPredicate `json:"eligibility" yaml:"eligibility"`
Include []wikiVisibilityRule `json:"include" yaml:"include"`
Exclude []wikiVisibilityRule `json:"exclude" yaml:"exclude"`
}
type wikiVisibilityPredicate struct {
When string `json:"when" yaml:"when"`
}
type wikiVisibilityRule struct {
When string `json:"when" yaml:"when"`
ReferenceSet string `json:"reference_set" yaml:"reference_set"`
}
type wikiVisibilityReferenceSet struct {
TargetDataset string `json:"target_dataset" yaml:"target_dataset"`
Sources []wikiVisibilityReferenceSource `json:"sources" yaml:"sources"`
}
type wikiVisibilityReferenceSource struct {
Dataset string `json:"dataset" yaml:"dataset"`
TableField string `json:"table_field" yaml:"table_field"`
TableKind string `json:"table_kind" yaml:"table_kind"`
RowRefField string `json:"row_ref_field" yaml:"row_ref_field"`
ExpandMasterFeat bool `json:"expand_masterfeat" yaml:"expand_masterfeat"`
}
func loadWikiVisibilityDefinitions(path string) (*wikiVisibilityDefinitions, error) {
raw, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("read wiki visibility definitions %s: %w", path, err)
}
policy, err := parseWikiVisibilityDefinitions(raw, path)
if err != nil {
return nil, err
}
return &policy, nil
}
func parseWikiVisibilityDefinitions(raw []byte, path string) (wikiVisibilityDefinitions, error) {
var policy wikiVisibilityDefinitions
if err := yaml.Unmarshal(raw, &policy); err != nil {
return wikiVisibilityDefinitions{}, fmt.Errorf("parse wiki visibility definitions %s: %w", path, err)
}
if policy.ReferenceSets == nil {
policy.ReferenceSets = map[string]wikiVisibilityReferenceSet{}
}
if policy.Datasets == nil {
policy.Datasets = map[string]wikiVisibilityDataset{}
}
for dataset, definition := range policy.Datasets {
if !isWikiVisibilityDataset(dataset) {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: unknown dataset %q", path, dataset)
}
if strings.TrimSpace(definition.Eligibility.When) != "" {
if err := validateWikiVisibilityExpression(definition.Eligibility.When); err != nil {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: dataset %s eligibility: %w", path, dataset, err)
}
}
for _, rules := range [][]wikiVisibilityRule{definition.Include, definition.Exclude} {
for _, rule := range rules {
if strings.TrimSpace(rule.When) == "" && strings.TrimSpace(rule.ReferenceSet) == "" {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: dataset %s rule requires when or reference_set", path, dataset)
}
if strings.TrimSpace(rule.When) != "" {
if err := validateWikiVisibilityExpression(rule.When); err != nil {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: dataset %s rule: %w", path, dataset, err)
}
}
if set := strings.TrimSpace(rule.ReferenceSet); set != "" {
referenceSet, ok := policy.ReferenceSets[set]
if !ok {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: dataset %s references unknown reference_set %q", path, dataset, set)
}
if referenceSet.TargetDataset != dataset {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: dataset %s cannot use reference_set %q targeting %s", path, dataset, set, referenceSet.TargetDataset)
}
}
}
}
}
for name, referenceSet := range policy.ReferenceSets {
if !isWikiVisibilityDataset(referenceSet.TargetDataset) {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: reference_set %s has unknown target_dataset %q", path, name, referenceSet.TargetDataset)
}
if len(referenceSet.Sources) == 0 {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: reference_set %s requires sources", path, name)
}
for _, source := range referenceSet.Sources {
if !isWikiVisibilityDataset(source.Dataset) {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: reference_set %s has unknown source dataset %q", path, name, source.Dataset)
}
if strings.TrimSpace(source.TableField) == "" || strings.TrimSpace(source.RowRefField) == "" {
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: reference_set %s source %s requires table_field and row_ref_field", path, name, source.Dataset)
}
switch source.TableKind {
case "class_feats", "class_bonus_feats", "race_feats":
default:
return wikiVisibilityDefinitions{}, fmt.Errorf("wiki visibility definitions %s: reference_set %s source %s has unsupported table_kind %q", path, name, source.Dataset, source.TableKind)
}
}
}
return policy, nil
}
func isWikiVisibilityDataset(name string) bool {
switch name {
case "baseitems", "classes", "feat", "racialtypes", "skills", "spells":
return true
default:
return false
}
}
func validateWikiVisibilityExpression(expression string) error {
parser := wikiExprParser{
tokens: tokenizeWikiExpr(wikiExpressionBody(expression)),
row: map[string]any{},
allowMissingFields: true,
}
if _, err := parser.parseComparison(); err != nil {
return err
}
if parser.peek().kind != wikiExprEOF {
return fmt.Errorf("unexpected token %q", parser.peek().text)
}
return nil
}
func wikiExpressionBody(expression string) string {
expression = strings.TrimSpace(expression)
if strings.HasPrefix(expression, "{{") && strings.HasSuffix(expression, "}}") {
return strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(expression, "{{"), "}}"))
}
return expression
}
func (ctx *wikiContext) loadWikiVisibility(path string) error {
policy, err := loadWikiVisibilityDefinitions(path)
if err != nil {
return err
}
ctx.visibilityPolicy = policy
if policy == nil {
ctx.visibleWikiKeys = nil
return nil
}
ctx.visibleWikiKeys, err = ctx.buildWikiVisibilityIndex(policy)
return err
}
func (ctx *wikiContext) buildWikiVisibilityIndex(policy *wikiVisibilityDefinitions) (map[string]bool, error) {
visible := map[string]bool{}
for _, dataset := range []string{"classes", "racialtypes", "skills", "spells", "baseitems", "feat"} {
definition, ok := policy.Datasets[dataset]
if !ok {
continue
}
rows := ctx.wikiRowsForDataset(dataset)
for _, key := range sortedKeys(rows) {
row := rows[key]
include, err := ctx.evaluateWikiVisibilityWithoutSets(dataset, key, row, definition)
if err != nil {
return nil, err
}
visible[key] = include
}
}
referenceSets := ctx.collectWikiVisibilityReferenceSets(policy, visible)
previousVisible := ctx.visibleWikiKeys
ctx.visibleWikiKeys = visible
defer func() {
ctx.visibleWikiKeys = previousVisible
}()
for pass := 0; pass <= len(visible); pass++ {
changed := false
for _, dataset := range []string{"classes", "racialtypes", "skills", "spells", "baseitems", "feat"} {
definition, ok := policy.Datasets[dataset]
if !ok {
continue
}
rows := ctx.wikiRowsForDataset(dataset)
for _, key := range sortedKeys(rows) {
include, err := ctx.evaluateWikiVisibility(dataset, key, rows[key], definition, referenceSets)
if err != nil {
return nil, err
}
if visible[key] != include {
changed = true
}
visible[key] = include
}
}
if !changed {
return visible, nil
}
}
return nil, fmt.Errorf("wiki visibility index did not converge")
}
func (ctx *wikiContext) evaluateWikiVisibilityWithoutSets(dataset, key string, row map[string]any, definition wikiVisibilityDataset) (bool, error) {
return ctx.evaluateWikiVisibility(dataset, key, row, definition, map[string]map[string]struct{}{})
}
func (ctx *wikiContext) evaluateWikiVisibility(dataset, key string, row map[string]any, definition wikiVisibilityDataset, referenceSets map[string]map[string]struct{}) (bool, error) {
if row == nil || !ctx.wikiRowCanRender(dataset, row) {
return false, nil
}
if expression := strings.TrimSpace(definition.Eligibility.When); expression != "" {
eligible, err := ctx.evalWikiVisibilityPredicate(expression, row)
if err != nil {
return false, fmt.Errorf("evaluate wiki visibility dataset %s eligibility for %s: %w", dataset, key, err)
}
if !eligible {
return false, nil
}
}
included := len(definition.Include) == 0
for _, rule := range definition.Include {
match, err := ctx.matchWikiVisibilityRule(rule, key, row, referenceSets)
if err != nil {
return false, fmt.Errorf("evaluate wiki visibility dataset %s include for %s: %w", dataset, key, err)
}
included = included || match
}
excluded := false
for _, rule := range definition.Exclude {
match, err := ctx.matchWikiVisibilityRule(rule, key, row, referenceSets)
if err != nil {
return false, fmt.Errorf("evaluate wiki visibility dataset %s exclude for %s: %w", dataset, key, err)
}
excluded = excluded || match
}
switch ctx.wikiGenerateOverride(row) {
case "0":
return false, nil
case "1":
return true, nil
default:
return included && !excluded, nil
}
}
func (ctx *wikiContext) matchWikiVisibilityRule(rule wikiVisibilityRule, key string, row map[string]any, referenceSets map[string]map[string]struct{}) (bool, error) {
match := false
if expression := strings.TrimSpace(rule.When); expression != "" {
value, err := ctx.evalWikiVisibilityPredicate(expression, row)
if err != nil {
return false, err
}
match = match || value
}
if set := strings.TrimSpace(rule.ReferenceSet); set != "" {
_, ok := referenceSets[set][key]
match = match || ok
}
return match, nil
}
func (ctx *wikiContext) evalWikiVisibilityPredicate(expression string, row map[string]any) (bool, error) {
parser := wikiExprParser{
tokens: tokenizeWikiExpr(wikiExpressionBody(expression)),
row: row,
ctx: ctx,
allowMissingFields: true,
}
value, err := parser.parseComparison()
if err != nil {
return false, err
}
if parser.peek().kind != wikiExprEOF {
return false, fmt.Errorf("unexpected token %q", parser.peek().text)
}
return truthyWikiTableValue(value), nil
}
func (ctx *wikiContext) collectWikiVisibilityReferenceSets(policy *wikiVisibilityDefinitions, visible map[string]bool) map[string]map[string]struct{} {
out := map[string]map[string]struct{}{}
names := make([]string, 0, len(policy.ReferenceSets))
for name := range policy.ReferenceSets {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
keys := map[string]struct{}{}
for _, source := range policy.ReferenceSets[name].Sources {
for key, row := range ctx.wikiRowsForDataset(source.Dataset) {
if !visible[key] {
continue
}
table := ctx.tableForValue(fieldValue(row, source.TableField), ctx.wikiVisibilityTables(source.TableKind))
if table == nil {
continue
}
for _, tableRow := range table.Rows {
refKey := resolveReferenceKey(fieldValue(tableRow, source.RowRefField), ctx.wikiIDMapForDataset(policy.ReferenceSets[name].TargetDataset))
if refKey == "" {
refKey = resolveReferenceKey(fieldValue(tableRow, source.RowRefField), nil)
}
if refKey == "" {
continue
}
if source.ExpandMasterFeat && strings.HasPrefix(refKey, "masterfeats:") {
for _, featKey := range ctx.masterFeatGroupKeys(refKey) {
keys[featKey] = struct{}{}
}
continue
}
keys[refKey] = struct{}{}
}
}
}
out[name] = keys
}
return out
}
func (ctx *wikiContext) wikiRowsForDataset(dataset string) map[string]map[string]any {
switch dataset {
case "baseitems":
return ctx.baseitemRows
case "classes":
return ctx.classRows
case "feat":
return ctx.featRows
case "racialtypes":
return ctx.raceRows
case "skills":
return ctx.skillRows
case "spells":
return ctx.spellRows
default:
return nil
}
}
func (ctx *wikiContext) wikiIDMapForDataset(dataset string) map[int]string {
switch dataset {
case "classes":
return ctx.classIDToKey
case "feat":
return ctx.featIDToKey
case "racialtypes":
return ctx.raceIDToKey
case "skills":
return ctx.skillIDToKey
default:
return nil
}
}
func (ctx *wikiContext) wikiVisibilityTables(kind string) map[string]wikiTable {
switch kind {
case "class_feats":
return ctx.classFeatTables
case "class_bonus_feats":
return ctx.classBonusTables
case "race_feats":
return ctx.raceFeatTables
default:
return nil
}
}
func (ctx *wikiContext) masterFeatGroupKeys(group string) []string {
keys := []string{}
for key, row := range ctx.featRows {
if masterfeatGroupKey(fieldValue(row, "MASTERFEAT"), ctx.masterfeatRows) == group {
keys = append(keys, key)
}
}
sort.Strings(keys)
return keys
}
func (ctx *wikiContext) canReferenceWikiKey(key string) bool {
if key == "" || ctx.visibilityPolicy == nil {
return true
}
visible, configured := ctx.visibleWikiKeys[key]
if configured {
return visible
}
dataset := strings.SplitN(key, ":", 2)[0]
_, configured = ctx.visibilityPolicy.Datasets[dataset]
return !configured
}