Files
sanspie cffa66514b
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Successful in 21m2s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 22m8s
Stage 9.6c2: prefab apply and data editing without the editor (D298)
2026-10-20 13:05:10 +03:00

154 lines
3.8 KiB
Go

package refactor
import (
"bytes"
"os"
"path/filepath"
"time"
"aego/asset/pipeline"
"aego/core/errs"
"aego/core/ids"
"aego/core/log"
"aego/project"
"aego/project/refs"
)
const RecoveryDir = ".aego/recovery"
type Edit struct {
ID ids.AssetID
Path string
Before []byte
After []byte
}
type Replacement struct {
From ids.AssetID
To ids.AssetID
Project bool
Files []string
}
func BackupDir(now time.Time) string {
return filepath.ToSlash(filepath.Join(RecoveryDir, "refs-"+now.Format("20060102-150405")))
}
func CheckReplace(src refs.Source, from, to ids.AssetID) error {
if from == to {
return errs.New(errs.EditorCommand, "an asset cannot replace itself")
}
if to.IsZero() {
return nil
}
fe, fok := src.Entry(from)
te, tok := src.Entry(to)
if !tok {
return errs.New(errs.EditorCommand, "the replacement is not an imported asset")
}
if fok && fe.Kind != te.Kind {
return errs.New(errs.EditorCommand, "the replacement is a "+string(te.Kind)+", the references expect a "+string(fe.Kind))
}
return nil
}
func PlanReplace(p *project.Project, src refs.Source, from, to ids.AssetID, skip func(rel string) bool) (*Replacement, error) {
if err := CheckReplace(src, from, to); err != nil {
return nil, err
}
r := &Replacement{From: from, To: to}
for _, ref := range refs.Referrers(p, src, from) {
switch {
case ref.Project:
r.Project = true
case skip != nil && skip(ref.Path):
default:
r.Files = append(r.Files, ref.Path)
}
}
return r, nil
}
func (r *Replacement) Edits(abs func(string) string) ([]Edit, error) {
old, now := refs.Token(r.From), refs.Token(r.To)
var out []Edit
for _, rel := range r.Files {
before, err := os.ReadFile(abs(rel))
if err != nil {
return nil, errs.Wrap(err, errs.VFSIO, "cannot read "+rel)
}
if !bytes.Contains(before, old) {
continue
}
out = append(out, Edit{Path: rel, Before: before, After: bytes.ReplaceAll(before, old, now)})
}
return out, nil
}
func (r *Replacement) Write(root string, abs func(string) string, backup string) ([]string, error) {
edits, err := r.Edits(abs)
if err != nil {
return nil, err
}
return WriteEdits(root, abs, backup, edits)
}
func (r *Replacement) ApplyProject(p *project.Project) error {
if !r.Project || r.To.IsZero() || p == nil {
return nil
}
prev := p.MainScene
p.MainScene = r.To
if err := p.Save(); err != nil {
p.MainScene = prev
return err
}
return nil
}
func WriteEdits(root string, abs func(string) string, backup string, edits []Edit) ([]string, error) {
var done []string
dir := filepath.Join(root, filepath.FromSlash(backup))
for _, e := range edits {
if backup != "" {
bak := filepath.Join(dir, filepath.FromSlash(e.Path))
if err := os.MkdirAll(filepath.Dir(bak), 0o755); err != nil {
return done, errs.Wrap(err, errs.VFSIO, "cannot create the backup folder").With(log.F("backup", backup))
}
if err := os.WriteFile(bak, e.Before, 0o644); err != nil {
return done, errs.Wrap(err, errs.VFSIO, "cannot back up "+e.Path).With(log.F("backup", backup))
}
}
if err := project.WriteAtomic(abs(e.Path), e.After); err != nil {
return done, errs.Wrap(err, errs.VFSIO, "cannot write "+e.Path).With(log.F("backup", backup))
}
done = append(done, e.Path)
}
return done, nil
}
func WriteAll(abs func(string) string, edits []Edit) error {
for i, e := range edits {
if err := project.WriteAtomic(abs(e.Path), e.After); err != nil {
for _, w := range edits[:i] {
project.WriteAtomic(abs(w.Path), w.Before)
}
return errs.Wrap(err, errs.VFSIO, "cannot write "+e.Path)
}
}
return nil
}
func Reimport(pl *pipeline.Pipeline, rels []string) (*pipeline.Batch, error) {
var list []ids.AssetID
for _, rel := range rels {
if id, ok := pl.Lookup(rel); ok {
list = append(list, id)
}
}
if len(list) == 0 {
return nil, nil
}
return pl.Reimport(list...)
}