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,84 @@
|
||||
// Package assets is the crucible `assets` builder: NWN:EE model/texture tools
|
||||
// that operate in place on a target directory. Mirrors internal/depot's
|
||||
// Run(args, stdout, stderr, getenv) shape and bypasses the legacy internal/app
|
||||
// surface entirely.
|
||||
package assets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// runner is the single external-command indirection so tests can observe and
|
||||
// stub every engine / ImageMagick / upscaler invocation. dir is the working
|
||||
// directory ("" = inherit); env replaces the child environment when non-nil.
|
||||
var runner = func(dir string, env []string, name string, args ...string) ([]byte, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
if env != nil {
|
||||
cmd.Env = env
|
||||
}
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
||||
// walk collects files under each root whose lower-cased extension is in exts. A
|
||||
// root that is itself a matching file is included. recursive controls descent
|
||||
// into subdirectories. Results are sorted and deduplicated.
|
||||
func walk(roots []string, exts map[string]bool, recursive bool) ([]string, error) {
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
add := func(p string) {
|
||||
if exts[strings.ToLower(filepath.Ext(p))] && !seen[p] {
|
||||
seen[p] = true
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
for _, root := range roots {
|
||||
fi, err := os.Stat(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
add(root)
|
||||
continue
|
||||
}
|
||||
err = filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
if !recursive && p != root {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
add(p)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// look returns the first bare name found on PATH, or the first candidate that
|
||||
// is an existing path. Returns "" if none resolve.
|
||||
func look(candidates ...string) string {
|
||||
for _, c := range candidates {
|
||||
if strings.ContainsRune(c, filepath.Separator) {
|
||||
if fi, err := os.Stat(c); err == nil && !fi.IsDir() {
|
||||
return c
|
||||
}
|
||||
continue
|
||||
}
|
||||
if p, err := exec.LookPath(c); err == nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user