Null key overrides add null rows 2
This commit is contained in:
@@ -974,6 +974,10 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
|||||||
rowByKey[rawKey] = row
|
rowByKey[rawKey] = row
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if overrideRequestsNullRow(override) {
|
||||||
|
nullifyOverrideRow(row, columns, rowByKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
candidate := map[string]any{}
|
candidate := map[string]any{}
|
||||||
for field, value := range row {
|
for field, value := range row {
|
||||||
candidate[field] = value
|
candidate[field] = value
|
||||||
@@ -4058,6 +4062,41 @@ func resolveOverrideTarget(datasetName string, override map[string]any, rowByID
|
|||||||
return nil, fmt.Errorf("dataset %s: override must specify id or key", datasetName)
|
return nil, fmt.Errorf("dataset %s: override must specify id or key", datasetName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func overrideRequestsNullRow(override map[string]any) bool {
|
||||||
|
rawKey, ok := override["key"]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rawKey == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
typed, ok := rawKey.(string)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(typed) == nullValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullifyOverrideRow(row map[string]any, columns []string, rowByKey map[string]map[string]any) {
|
||||||
|
if oldKey, ok := row["key"].(string); ok && oldKey != "" {
|
||||||
|
if mapped, exists := rowByKey[oldKey]; exists {
|
||||||
|
mappedID, mappedHasID := mapped["id"].(int)
|
||||||
|
rowID, rowHasID := row["id"].(int)
|
||||||
|
if mappedHasID && rowHasID && mappedID == rowID {
|
||||||
|
delete(rowByKey, oldKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for field := range row {
|
||||||
|
if field != "id" {
|
||||||
|
delete(row, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, column := range columns {
|
||||||
|
row[column] = nullValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateOverrideRowKey(datasetName string, row map[string]any, expanded map[string]any, rowByKey map[string]map[string]any, lockData map[string]int) (bool, error) {
|
func updateOverrideRowKey(datasetName string, row map[string]any, expanded map[string]any, rowByKey map[string]map[string]any, lockData map[string]int) (bool, error) {
|
||||||
oldKey, _ := row["key"].(string)
|
oldKey, _ := row["key"].(string)
|
||||||
newKeyValue, hasKey := expanded["key"]
|
newKeyValue, hasKey := expanded["key"]
|
||||||
|
|||||||
@@ -6776,6 +6776,51 @@ func TestBuildGeneratedApplyAfterPrunesLowLockedID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildOverrideNullValueKeyNullifiesEntireRow(t *testing.T) {
|
||||||
|
root := testProjectRoot(t)
|
||||||
|
mkdirAll(t, filepath.Join(root, "topdata", "data", "dense", "modules"))
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "dense", "base.json"), `{
|
||||||
|
"output": "dense.2da",
|
||||||
|
"columns": ["Label", "Value"],
|
||||||
|
"rows": [
|
||||||
|
{"id": 7, "key": "dense:old", "Label": "Old", "Value": "old"}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "dense", "lock.json"), `{
|
||||||
|
"dense:old": 7
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "dense", "modules", "nullify.json"), `{
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"key": "****",
|
||||||
|
"Label": "ShouldNotLeak",
|
||||||
|
"Value": "still-not-kept"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
|
||||||
|
result, err := BuildNative(testProject(root), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildNative failed: %v", err)
|
||||||
|
}
|
||||||
|
got, err := os.ReadFile(filepath.Join(result.Output2DADir, "dense.2da"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read dense.2da: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(got), "7\t****\t****\n") {
|
||||||
|
t.Fatalf("expected override key \"****\" to nullify row 7, got:\n%s", string(got))
|
||||||
|
}
|
||||||
|
lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "dense", "lock.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read lock: %v", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(lockRaw), `"dense:old"`) {
|
||||||
|
t.Fatalf("expected nullified row key to be pruned from lock data, got:\n%s", string(lockRaw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildCanonicalBaseitemsMatchesReference(t *testing.T) {
|
func TestBuildCanonicalBaseitemsMatchesReference(t *testing.T) {
|
||||||
root := testProjectRoot(t)
|
root := testProjectRoot(t)
|
||||||
mkdirAll(t, filepath.Join(root, "topdata", "data"))
|
mkdirAll(t, filepath.Join(root, "topdata", "data"))
|
||||||
@@ -8116,7 +8161,7 @@ func TestBuildSupportsCanonicalSkills(t *testing.T) {
|
|||||||
}
|
}
|
||||||
text := string(got)
|
text := string(got)
|
||||||
if !strings.Contains(text, "Label\tName\tDescription\tIcon") ||
|
if !strings.Contains(text, "Label\tName\tDescription\tIcon") ||
|
||||||
!strings.Contains(text, "3\tDiscipline_REMOVED\t343\t347\tisk_discipline\t1\tSTR\t0\t0\t****\t****\t****\t0\t1\n") ||
|
!strings.Contains(text, "3\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\n") ||
|
||||||
!strings.Contains(text, "25\tCraftarmorsmithing\t1002\t1003\tisk_x2carm\t0\tDEX\t1\t1\t****\t****\tSKILL_CRAFT_ARMORSMITHING\t0\t0\n") ||
|
!strings.Contains(text, "25\tCraftarmorsmithing\t1002\t1003\tisk_x2carm\t0\tDEX\t1\t1\t****\t****\tSKILL_CRAFT_ARMORSMITHING\t0\t0\n") ||
|
||||||
!strings.Contains(text, "28\tAthletics\t1000\t1001\tisk_athletics\t1\tSTR\t1\t1\t****\t****\tSKILL_ATHLETICS\t0\t0\n") ||
|
!strings.Contains(text, "28\tAthletics\t1000\t1001\tisk_athletics\t1\tSTR\t1\t1\t****\t****\tSKILL_ATHLETICS\t0\t0\n") ||
|
||||||
!strings.Contains(text, "40\tKnowledgearcana\t1004\t****\tisk_knarcana\t1\tINT\t0\t1\t****\t****\tSKILL_KNOWLEDGE_ARCANA\t0\t0\n") {
|
!strings.Contains(text, "40\tKnowledgearcana\t1004\t****\tisk_knarcana\t1\tINT\t0\t1\t****\t****\tSKILL_KNOWLEDGE_ARCANA\t0\t0\n") {
|
||||||
@@ -8170,7 +8215,7 @@ with open(os.path.join(out, "skills.2da"), "w", encoding="utf-8") as f:
|
|||||||
emit(0, {"Label": "AnimalEmpathy", "Name": "269", "Description": "344", "Icon": "isk_aniemp", "Untrained": "0", "KeyAbility": "CHA", "ArmorCheckPenalty": "0", "AllClassesCanUse": "0", "Category": "****", "MaxCR": "1", "Constant": "SKILL_ANIMAL_EMPATHY", "HostileSkill": "1", "HideFromLevelUp": "0"})
|
emit(0, {"Label": "AnimalEmpathy", "Name": "269", "Description": "344", "Icon": "isk_aniemp", "Untrained": "0", "KeyAbility": "CHA", "ArmorCheckPenalty": "0", "AllClassesCanUse": "0", "Category": "****", "MaxCR": "1", "Constant": "SKILL_ANIMAL_EMPATHY", "HostileSkill": "1", "HideFromLevelUp": "0"})
|
||||||
emit(1, {"Label": "Concentration", "Name": "270", "Description": "345", "Icon": "isk_concen", "Untrained": "1", "KeyAbility": "CON", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CONCENTRATION", "HostileSkill": "0", "HideFromLevelUp": "0"})
|
emit(1, {"Label": "Concentration", "Name": "270", "Description": "345", "Icon": "isk_concen", "Untrained": "1", "KeyAbility": "CON", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CONCENTRATION", "HostileSkill": "0", "HideFromLevelUp": "0"})
|
||||||
emit(2, {})
|
emit(2, {})
|
||||||
emit(3, {"Label": "Discipline_REMOVED", "Name": "343", "Description": "347", "Icon": "isk_discipline", "Untrained": "1", "KeyAbility": "STR", "ArmorCheckPenalty": "0", "AllClassesCanUse": "0", "Category": "****", "MaxCR": "****", "Constant": "****", "HostileSkill": "0", "HideFromLevelUp": "1"})
|
emit(3, {})
|
||||||
for row_id in range(4, 25):
|
for row_id in range(4, 25):
|
||||||
emit(row_id, {})
|
emit(row_id, {})
|
||||||
emit(25, {"Label": "Craftarmorsmithing", "Name": "1002", "Description": "1003", "Icon": "isk_x2carm", "Untrained": "0", "KeyAbility": "DEX", "ArmorCheckPenalty": "1", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CRAFT_ARMORSMITHING", "HostileSkill": "0", "HideFromLevelUp": "0"})
|
emit(25, {"Label": "Craftarmorsmithing", "Name": "1002", "Description": "1003", "Icon": "isk_x2carm", "Untrained": "0", "KeyAbility": "DEX", "ArmorCheckPenalty": "1", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CRAFT_ARMORSMITHING", "HostileSkill": "0", "HideFromLevelUp": "0"})
|
||||||
@@ -8545,7 +8590,7 @@ func TestBuildSupportsCanonicalSpells(t *testing.T) {
|
|||||||
}
|
}
|
||||||
text := string(got)
|
text := string(got)
|
||||||
if !strings.Contains(text, "Label\tName\tIconResRef") ||
|
if !strings.Contains(text, "Label\tName\tIconResRef") ||
|
||||||
!strings.Contains(text, "7\tBless_Weapon_OLD\t123") ||
|
!strings.Contains(text, "7\t****\t****\t****") ||
|
||||||
!strings.Contains(text, "840\tSpecialAttacks\t500") ||
|
!strings.Contains(text, "840\tSpecialAttacks\t500") ||
|
||||||
!strings.Contains(text, "\t841\t") ||
|
!strings.Contains(text, "\t841\t") ||
|
||||||
!strings.Contains(text, "\t22\t") ||
|
!strings.Contains(text, "\t22\t") ||
|
||||||
@@ -8611,7 +8656,7 @@ with open(os.path.join(out, "spells.2da"), "w", encoding="utf-8") as f:
|
|||||||
f.write("\t".join(cols) + "\n")
|
f.write("\t".join(cols) + "\n")
|
||||||
for row_id in range(0, 7):
|
for row_id in range(0, 7):
|
||||||
emit(row_id, {})
|
emit(row_id, {})
|
||||||
emit(7, {"Label": "Bless_Weapon_OLD", "Name": "123", "Category": "8"})
|
emit(7, {})
|
||||||
for row_id in range(8, 840):
|
for row_id in range(8, 840):
|
||||||
emit(row_id, {})
|
emit(row_id, {})
|
||||||
emit(840, {"Label": "SpecialAttacks", "Name": "16777741", "SubRadSpell1": "841", "Category": "22", "FeatID": "1000"})
|
emit(840, {"Label": "SpecialAttacks", "Name": "16777741", "SubRadSpell1": "841", "Category": "22", "FeatID": "1000"})
|
||||||
|
|||||||
Reference in New Issue
Block a user