feat(assets): seven asset commands + Run dispatch/helpers
check-dupes, clean-dupes, check-mdl, fix-mdl, convert, upscale, compile behind a single runner indirection, mirroring internal/depot's Run shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/assets/mdl"
|
||||
)
|
||||
|
||||
// runFixMDL lowercases .mdl filenames and rewrites ASCII model identity to
|
||||
// match each file's stem. Binary models are skipped (the engine owns those).
|
||||
func runFixMDL(args []string, stdout, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("fix-mdl", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
dryRun := fs.Bool("dry-run", false, "report changes without touching disk")
|
||||
nonRecursive := fs.Bool("non-recursive", false, "do not descend into subdirectories")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return exitUsage
|
||||
}
|
||||
paths := fs.Args()
|
||||
if len(paths) == 0 {
|
||||
fmt.Fprintln(stderr, "assets fix-mdl: usage: fix-mdl [--dry-run] <path>...")
|
||||
return exitUsage
|
||||
}
|
||||
|
||||
files, err := walk(paths, mdlExt, !*nonRecursive)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "assets fix-mdl:", err)
|
||||
return exitUsage
|
||||
}
|
||||
|
||||
changed := 0
|
||||
failed := false
|
||||
for _, f := range files {
|
||||
// 1. Lowercase the basename if needed.
|
||||
//
|
||||
// ponytail: 35k files are each read once here (CheckNames + the rewrite
|
||||
// on candidates). The shell reference batched an awk pass to avoid
|
||||
// per-file subprocess spawns; in Go a plain read is cheap, so the batch
|
||||
// is not worth porting. If profiling ever shows this hot, parallelize
|
||||
// the loop.
|
||||
lower := f
|
||||
if base := filepath.Base(f); base != strings.ToLower(base) {
|
||||
lower = filepath.Join(filepath.Dir(f), strings.ToLower(base))
|
||||
if _, err := os.Stat(lower); err == nil {
|
||||
fmt.Fprintf(stderr, "assets fix-mdl: lowercase collision: %s -> %s\n", f, lower)
|
||||
failed = true
|
||||
continue
|
||||
}
|
||||
if *dryRun {
|
||||
fmt.Fprintf(stdout, "would lowercase: %s -> %s\n", f, lower)
|
||||
} else {
|
||||
if err := os.Rename(f, lower); err != nil {
|
||||
fmt.Fprintf(stderr, "assets fix-mdl: failed to lowercase %s: %v\n", f, err)
|
||||
failed = true
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(stdout, "lowercased: %s -> %s\n", f, lower)
|
||||
}
|
||||
changed++
|
||||
}
|
||||
|
||||
// 2. Rewrite model identity if the (lowercased) file has a mismatch.
|
||||
// In dry-run the on-disk file is still the original path.
|
||||
readPath := lower
|
||||
if *dryRun && lower != f {
|
||||
readPath = f
|
||||
}
|
||||
data, err := os.ReadFile(readPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "assets fix-mdl:", err)
|
||||
failed = true
|
||||
continue
|
||||
}
|
||||
out, wasChanged := mdl.FixNames(data, mdl.ExpectedName(lower))
|
||||
if !wasChanged {
|
||||
continue
|
||||
}
|
||||
if *dryRun {
|
||||
fmt.Fprintf(stdout, "would fix: %s\n", lower)
|
||||
} else {
|
||||
if err := os.WriteFile(lower, out, 0o644); err != nil {
|
||||
fmt.Fprintln(stderr, "assets fix-mdl:", err)
|
||||
failed = true
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(stdout, "fixed: %s\n", lower)
|
||||
}
|
||||
changed++
|
||||
}
|
||||
|
||||
if changed == 0 {
|
||||
fmt.Fprintln(stdout, "no broken mdl model names found")
|
||||
}
|
||||
if failed {
|
||||
return exitFail
|
||||
}
|
||||
return exitOK
|
||||
}
|
||||
Reference in New Issue
Block a user