Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9860c12c6 | ||
|
|
825fff8b67 |
@@ -4712,6 +4712,24 @@ func (r *valueResolver) resolveValue(row map[string]any, field string, value any
|
||||
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
if radial, ok := typed["spellradial"].(map[string]any); ok && len(typed) == 1 {
|
||||
if r.dataset.Name != "spells" || field != "FeatID" {
|
||||
return tlkCompiledValue{}, fmt.Errorf("spellradial reference is only valid for spells.FeatID")
|
||||
}
|
||||
radialKey, ok := radial["id"].(string)
|
||||
if !ok || len(radial) != 1 {
|
||||
return tlkCompiledValue{}, fmt.Errorf("spellradial reference requires exactly one id")
|
||||
}
|
||||
featID, ok := r.keyToID[radialKey]
|
||||
if !ok {
|
||||
return tlkCompiledValue{}, fmt.Errorf("unknown spellradial feat reference: %s", radialKey)
|
||||
}
|
||||
spellID, ok := row["id"].(int)
|
||||
if !ok {
|
||||
return tlkCompiledValue{}, fmt.Errorf("spellradial reference requires a numeric spell row id")
|
||||
}
|
||||
return tlkCompiledValue{Value: spellID<<16 + featID}, nil
|
||||
}
|
||||
if encoding, ok := r.valueEncodingForField(field); ok {
|
||||
mode := strings.TrimSpace(encoding.Mode)
|
||||
_, hasList := typed["list"]
|
||||
|
||||
@@ -211,11 +211,12 @@ func collectTopPackageResources(p *project.Project, compiled2DADir string) ([]er
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(filepath.Base(path), ".") {
|
||||
return nil
|
||||
rel, err := filepath.Rel(assetsDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(filepath.Ext(path), ".md") {
|
||||
return nil // docs (AGENTS.md, README.md, ...) are never HAK resources
|
||||
if skipTopPackageAsset(rel) {
|
||||
return nil
|
||||
}
|
||||
resource, err := topPackageResourceFromPath(path)
|
||||
if err != nil {
|
||||
@@ -388,6 +389,25 @@ func newestMatchingAutogenOverrideInput(scanRoot string, include []string) (time
|
||||
return newest, newestPath, nil
|
||||
}
|
||||
|
||||
// skipTopPackageAsset reports whether a file under assets/ is not a HAK
|
||||
// resource and must be ignored by both validation and packing: anything in a
|
||||
// hidden or underscore-prefixed directory (working dirs like _candidates),
|
||||
// hidden files, docs, and any extension that is not a known NWN ResType
|
||||
// (erf.extensionTypes is the whitelist). rel is the path relative to assets/.
|
||||
func skipTopPackageAsset(rel string) bool {
|
||||
for _, part := range strings.Split(filepath.ToSlash(rel), "/") {
|
||||
if strings.HasPrefix(part, ".") || strings.HasPrefix(part, "_") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if strings.EqualFold(filepath.Ext(rel), ".md") {
|
||||
return true
|
||||
}
|
||||
ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(rel)), ".")
|
||||
_, ok := erf.HAKResourceTypeForExtension(ext)
|
||||
return !ok
|
||||
}
|
||||
|
||||
func topPackageResourceFromPath(path string) (erf.Resource, error) {
|
||||
extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(path)), ".")
|
||||
resourceType, ok := erf.HAKResourceTypeForExtension(extension)
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf"
|
||||
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
||||
)
|
||||
|
||||
@@ -2230,22 +2229,11 @@ func validateTopPackageAssets(sourceDir, dataDir string, report *ValidationRepor
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(filepath.Base(path), ".") {
|
||||
return nil
|
||||
}
|
||||
if strings.EqualFold(filepath.Ext(path), ".md") {
|
||||
return nil // docs (AGENTS.md, README.md, ...) are never HAK resources
|
||||
if skipTopPackageAsset(rel) {
|
||||
return nil // not a NWN ResType (script, doc, _work dir, ...): never packed, never checked
|
||||
}
|
||||
base := strings.ToLower(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
|
||||
ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(path)), ".")
|
||||
if _, ok := erf.HAKResourceTypeForExtension(ext); !ok {
|
||||
report.Diagnostics = append(report.Diagnostics, Diagnostic{
|
||||
Severity: SeverityError,
|
||||
Path: path,
|
||||
Message: fmt.Sprintf("unsupported topdata asset HAK resource extension %q", filepath.Ext(path)),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
key := base + "." + ext
|
||||
if previous, ok := seen[key]; ok {
|
||||
report.Diagnostics = append(report.Diagnostics, Diagnostic{
|
||||
|
||||
@@ -11887,7 +11887,7 @@ func TestBuildSupportsCanonicalSpells(t *testing.T) {
|
||||
"spells:disarm": {
|
||||
"Label": "Disarm",
|
||||
"Name": "501",
|
||||
"FeatID": {"id": "feat:disarm"},
|
||||
"FeatID": {"spellradial": {"id": "feat:specialattacks"}},
|
||||
"Master": {"id": "spells:specialattacks"},
|
||||
"Category": {"ref": "spells:specialattacks", "field": "Category"}
|
||||
}
|
||||
@@ -11939,7 +11939,7 @@ func TestBuildSupportsCanonicalSpells(t *testing.T) {
|
||||
!strings.Contains(text, "\t1000\t") ||
|
||||
!strings.Contains(text, "841\tDisarm\t501") ||
|
||||
!strings.Contains(text, "\t840\t") ||
|
||||
!strings.Contains(text, "\t1001\t") ||
|
||||
!strings.Contains(text, "\t55116776\t") ||
|
||||
!strings.Contains(text, "849\tBard_Fascinate\t600") ||
|
||||
!strings.Contains(text, "\t601\t") ||
|
||||
!strings.Contains(text, "\t602\t") ||
|
||||
@@ -15116,6 +15116,60 @@ func writeBytes(t *testing.T, path string, content []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipTopPackageAsset(t *testing.T) {
|
||||
skipped := []string{
|
||||
"gui/regions/comfyui-generate.sh", // non-NWN extension
|
||||
"gui/regions/_candidates/abyss.opt4.png",
|
||||
"gui/.hidden/banner.png",
|
||||
"gui/.DS_Store",
|
||||
"gui/AGENTS.md",
|
||||
"gui/noextension",
|
||||
}
|
||||
kept := []string{
|
||||
"gui/regions/abyss.png",
|
||||
"2da-src/placeables.2da",
|
||||
"tex/floor01.dds",
|
||||
}
|
||||
for _, rel := range skipped {
|
||||
if !skipTopPackageAsset(rel) {
|
||||
t.Errorf("expected %s to be skipped", rel)
|
||||
}
|
||||
}
|
||||
for _, rel := range kept {
|
||||
if skipTopPackageAsset(rel) {
|
||||
t.Errorf("expected %s to be kept", rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTopPackageAssetsIgnoresNonNWNFiles(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
assets := filepath.Join(dir, "assets", "gui", "regions")
|
||||
candidates := filepath.Join(assets, "_candidates")
|
||||
if err := os.MkdirAll(candidates, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for path, content := range map[string]string{
|
||||
filepath.Join(assets, "comfyui-generate.sh"): "#!/bin/sh",
|
||||
filepath.Join(assets, "abyss.png"): "png",
|
||||
filepath.Join(candidates, "abyss.opt4.png"): "png",
|
||||
filepath.Join(candidates, "notes.txt.backup"): "junk",
|
||||
} {
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
dataDir := filepath.Join(dir, "data")
|
||||
if err := os.MkdirAll(dataDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var report ValidationReport
|
||||
validateTopPackageAssets(dir, dataDir, &report)
|
||||
for _, d := range report.Diagnostics {
|
||||
t.Errorf("unexpected diagnostic: %s: %s", d.Path, d.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTopPackageAssetsSkipsMarkdown(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
assets := filepath.Join(dir, "assets", "gui")
|
||||
|
||||
Reference in New Issue
Block a user