Tool: Polish Pass 1

In sow-tools, extract now behaves like a real sync by default: it
overwrites changed extracted files, removes stale extracted files that
no longer exist in the built archives, and normalizes extracted resource
filenames to lowercase. I also made validation warn on uppercase
resource filenames so mixed-case names like I_ELVENCHAIN are surfaced
instead of quietly lingering. The extract command output now includes
overwritten and removed counts too.
This commit is contained in:
2026-04-02 20:04:41 +02:00
parent 97465e451e
commit 77b21081d7
6 changed files with 340 additions and 36 deletions
+24 -10
View File
@@ -76,6 +76,9 @@ func ValidateProject(p *project.Project) Report {
resolver := newAssetGroupResolver(p)
for _, rel := range p.Inventory.SourceFiles {
if hasUppercaseResourceName(rel) {
report.add(rel, "resource filenames should be lowercase", SeverityWarning)
}
abs := filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
document, resref, extension, err := loadDocument(abs)
if err != nil {
@@ -90,7 +93,7 @@ func ValidateProject(p *project.Project) Report {
Document: document,
})
key := resref + "." + extension
key := strings.ToLower(resref) + "." + extension
if previous, exists := resourceIndex[key]; exists {
report.add(rel, fmt.Sprintf("duplicate resource %s also defined by %s", key, previous), SeverityError)
} else {
@@ -101,7 +104,10 @@ func ValidateProject(p *project.Project) Report {
}
for _, rel := range p.Inventory.ScriptFiles {
base := strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel))
if hasUppercaseResourceName(rel) {
report.add(rel, "resource filenames should be lowercase", SeverityWarning)
}
base := strings.ToLower(strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel)))
if previous, exists := scriptIndex[base]; exists {
report.add(rel, fmt.Sprintf("duplicate script resource %s also defined by %s", base, previous), SeverityError)
} else {
@@ -110,9 +116,12 @@ func ValidateProject(p *project.Project) Report {
}
for _, rel := range p.Inventory.AssetFiles {
base := strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel))
if hasUppercaseResourceName(rel) {
report.add(rel, "resource filenames should be lowercase", SeverityWarning)
}
base := strings.ToLower(strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel)))
extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(rel)), ".")
key := base + "." + extension
key := strings.ToLower(base) + "." + extension
if _, exists := assetIndex[key]; !exists {
assetIndex[key] = rel
}
@@ -187,7 +196,7 @@ func loadDocument(path string) (gff.Document, string, string, error) {
if extension == "" {
return gff.Document{}, "", "", fmt.Errorf("missing resource extension before .json")
}
resref := strings.TrimSuffix(stem, "."+extension)
resref := strings.ToLower(strings.TrimSuffix(stem, "."+extension))
var document gff.Document
if err := json.Unmarshal(raw, &document); err != nil {
@@ -250,25 +259,25 @@ func validateReferences(report *Report, document loadedDocument, resources, scri
if isBuiltinScript(value) {
return
}
if _, exists := scripts[value]; !exists {
if _, exists := scripts[strings.ToLower(value)]; !exists {
report.add(document.Path, fmt.Sprintf("missing script reference %q from field %q", value, field.Label), SeverityError)
}
case field.Label == "Conversation":
key := value + ".dlg"
key := strings.ToLower(value) + ".dlg"
if _, exists := resources[key]; !exists {
report.add(document.Path, fmt.Sprintf("missing dialog reference %q", key), SeverityError)
}
case field.Label == "Mod_Entry_Area":
key := value + ".are"
key := strings.ToLower(value) + ".are"
if _, exists := resources[key]; !exists {
report.add(document.Path, fmt.Sprintf("missing area reference %q", key), SeverityError)
}
case field.Label == "Model":
if !hasAsset(value, []string{"mdl"}, assets) {
if !hasAsset(strings.ToLower(value), []string{"mdl"}, assets) {
report.add(document.Path, fmt.Sprintf("missing model asset for %q", value), SeverityError)
}
case field.Label == "Sound":
if !hasAsset(value, []string{"wav"}, assets) {
if !hasAsset(strings.ToLower(value), []string{"wav"}, assets) {
report.add(document.Path, fmt.Sprintf("missing sound asset for %q", value), SeverityError)
}
}
@@ -390,6 +399,11 @@ func walkFields(s gff.Struct, visit func(gff.Field)) {
}
}
func hasUppercaseResourceName(rel string) bool {
base := filepath.Base(rel)
return base != strings.ToLower(base)
}
func fieldStringValue(value gff.Value) (string, bool) {
switch typed := value.(type) {
case gff.StringValue: