Phase 5: scaffold Crucible (sow-tools) — dispatcher + builder shims, container, PR-first CI, fail-closed (D11, D19).
This commit is contained in:
@@ -1,226 +0,0 @@
|
||||
package topdata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var legacyClassesPlainFamilies = []string{"feats", "skills", "savthr", "bfeat", "pres"}
|
||||
|
||||
func importLegacyClasses(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) {
|
||||
legacyRoot := filepath.Join(referenceBuilderDir, "data", "classes")
|
||||
if _, err := os.Stat(legacyRoot); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
targetRoot := filepath.Join(dataDir, "classes")
|
||||
if canonicalClassesPresent(targetRoot) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
coreCollected, plainCollected, err := collectLegacyClassesDatasets(legacyRoot)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
tableKeyByOutput := map[string]string{}
|
||||
for _, dataset := range plainCollected {
|
||||
if dataset.TableKey == "" {
|
||||
continue
|
||||
}
|
||||
registerLegacyClassesTableKey(tableKeyByOutput, dataset.TableKey, dataset.Dataset.OutputName)
|
||||
}
|
||||
|
||||
coreRows := make([]map[string]any, 0, len(coreCollected.Rows))
|
||||
for _, rawRow := range coreCollected.Rows {
|
||||
row, ok := deepCopyValue(rawRow).(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if legacyTLK != nil {
|
||||
if _, _, err := inlineLegacyTLKValue(row, legacyTLK); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
rewriteLegacyClassesCoreRow(row, tableKeyByOutput)
|
||||
coreRows = append(coreRows, row)
|
||||
}
|
||||
|
||||
if err := removePathIfExists(targetRoot); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(targetRoot, "core"), 0o755); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
writes := []struct {
|
||||
path string
|
||||
obj map[string]any
|
||||
}{
|
||||
{
|
||||
path: filepath.Join(targetRoot, "core", "base.json"),
|
||||
obj: map[string]any{
|
||||
"output": coreCollected.Dataset.OutputName,
|
||||
"columns": stringSliceToAny(coreCollected.Columns),
|
||||
"rows": rowsToAny(coreRows),
|
||||
},
|
||||
},
|
||||
{
|
||||
path: filepath.Join(targetRoot, "core", "lock.json"),
|
||||
obj: anyMapInt(coreCollected.LockData),
|
||||
},
|
||||
}
|
||||
|
||||
for _, collected := range plainCollected {
|
||||
obj := map[string]any{
|
||||
"output": collected.Dataset.OutputName,
|
||||
"columns": stringSliceToAny(collected.Columns),
|
||||
"rows": rowsToAny(collected.Rows),
|
||||
}
|
||||
if strings.TrimSpace(collected.TableKey) != "" {
|
||||
obj["key"] = collected.TableKey
|
||||
}
|
||||
writes = append(writes, struct {
|
||||
path string
|
||||
obj map[string]any
|
||||
}{
|
||||
path: filepath.Join(dataDir, collected.Dataset.Name+".json"),
|
||||
obj: obj,
|
||||
})
|
||||
}
|
||||
|
||||
for _, write := range writes {
|
||||
if err := os.MkdirAll(filepath.Dir(write.path), 0o755); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
raw, err := json.MarshalIndent(write.obj, "", " ")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
raw = append(raw, '\n')
|
||||
if err := os.WriteFile(write.path, raw, 0o644); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return len(writes), nil
|
||||
}
|
||||
|
||||
func canonicalClassesPresent(targetRoot string) bool {
|
||||
if !fileExists(filepath.Join(targetRoot, "core", "base.json")) || !fileExists(filepath.Join(targetRoot, "core", "lock.json")) {
|
||||
return false
|
||||
}
|
||||
for _, family := range legacyClassesPlainFamilies {
|
||||
ok, err := hasJSONFiles(filepath.Join(targetRoot, family))
|
||||
if err != nil || !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func collectLegacyClassesDatasets(legacyRoot string) (nativeCollectedDataset, []nativeCollectedDataset, error) {
|
||||
coreCollected, err := collectBaseDataset(nativeDataset{
|
||||
Name: "classes/core",
|
||||
BasePath: filepath.Join(legacyRoot, "core", "base.json"),
|
||||
LockPath: filepath.Join(legacyRoot, "core", "lock.json"),
|
||||
ModulesDir: filepath.Join(legacyRoot, "core", "modules"),
|
||||
OutputName: "classes.2da",
|
||||
Spec: specForDataset("classes"),
|
||||
})
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, nil, err
|
||||
}
|
||||
coreCollected.Dataset.OutputName = "classes.2da"
|
||||
|
||||
plainCollected := make([]nativeCollectedDataset, 0)
|
||||
for _, family := range legacyClassesPlainFamilies {
|
||||
familyDir := filepath.Join(legacyRoot, family)
|
||||
entries, err := os.ReadDir(familyDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nativeCollectedDataset{}, nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" || strings.HasPrefix(entry.Name(), ".") || entry.Name() == "lock.json" {
|
||||
continue
|
||||
}
|
||||
filePath := filepath.Join(familyDir, entry.Name())
|
||||
tableData, err := loadJSONObject(filePath)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, nil, err
|
||||
}
|
||||
outputName, _ := tableData["output"].(string)
|
||||
if strings.TrimSpace(outputName) == "" {
|
||||
outputName = strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name())) + ".2da"
|
||||
}
|
||||
collected, err := collectPlainDataset(nativeDataset{
|
||||
Name: filepath.ToSlash(filepath.Join("classes", family, strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name())))),
|
||||
BasePath: filePath,
|
||||
LockPath: filepath.Join(familyDir, "lock.json"),
|
||||
OutputName: outputName,
|
||||
Spec: specForDataset(filepath.ToSlash(filepath.Join("classes", family, strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name()))))),
|
||||
})
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, nil, err
|
||||
}
|
||||
plainCollected = append(plainCollected, collected)
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(plainCollected, func(a, b nativeCollectedDataset) int {
|
||||
return strings.Compare(a.Dataset.Name, b.Dataset.Name)
|
||||
})
|
||||
return coreCollected, plainCollected, nil
|
||||
}
|
||||
|
||||
func registerLegacyClassesTableKey(tableKeyByOutput map[string]string, tableKey, outputName string) {
|
||||
trimmedOutput := strings.TrimSpace(outputName)
|
||||
if trimmedOutput != "" {
|
||||
tableKeyByOutput[trimmedOutput] = tableKey
|
||||
}
|
||||
trimmedStem := strings.TrimSpace(outputStem(outputName))
|
||||
if trimmedStem != "" {
|
||||
tableKeyByOutput[trimmedStem] = tableKey
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteLegacyClassesCoreRow(row map[string]any, tableKeyByOutput map[string]string) {
|
||||
for _, field := range []string{"FeatsTable", "SavingThrowTable", "SkillsTable", "BonusFeatsTable", "PreReqTable"} {
|
||||
tableKey := legacyClassesTableKeyForValue(row[field], tableKeyByOutput)
|
||||
if tableKey == "" {
|
||||
continue
|
||||
}
|
||||
row[field] = map[string]any{"table": tableKey}
|
||||
}
|
||||
}
|
||||
|
||||
func legacyClassesTableKeyForValue(value any, tableKeyByOutput map[string]string) string {
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
tableKey, _ := typed["table"].(string)
|
||||
return strings.TrimSpace(tableKey)
|
||||
case string:
|
||||
trimmed := strings.TrimSpace(typed)
|
||||
if trimmed == "" || trimmed == nullValue {
|
||||
return ""
|
||||
}
|
||||
if trimmed != strings.ToLower(trimmed) {
|
||||
return ""
|
||||
}
|
||||
if tableKey, ok := tableKeyByOutput[trimmed]; ok {
|
||||
return tableKey
|
||||
}
|
||||
return ""
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user