mukan-ignite/ignite/pkg/xgenny/runner.go
Mukan Erkin Törük c32551b6f7
Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:24 +03:00

208 lines
4.9 KiB
Go

package xgenny
import (
"context"
"io"
"os"
"path/filepath"
"github.com/gobuffalo/genny/v2"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/randstr"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/xos"
)
type Runner struct {
*genny.Runner
ctx context.Context
results []genny.File
tmpPath string
root string
}
// NewRunner is a xgenny Runner with a logger.
func NewRunner(ctx context.Context, root string) *Runner {
var (
runner = genny.WetRunner(ctx)
tmpPath = filepath.Join(os.TempDir(), randstr.Runes(5))
)
runner.Root = root
r := &Runner{
ctx: ctx,
Runner: runner,
tmpPath: tmpPath,
results: make([]genny.File, 0),
root: root,
}
runner.FileFn = wetFileFn(r)
return r
}
// cleanup clears the underlying genny runner state so previously executed
// generators are not re-run on subsequent calls.
func (r *Runner) cleanup() {
runner := genny.WetRunner(r.ctx)
runner.Root = r.root
runner.FileFn = wetFileFn(r)
r.Runner = runner
}
type (
OverwriteCallback func(_, _, duplicated []string) error
// ApplyOption holds the ApplyModifications options.
applyOptions struct {
preRun OverwriteCallback
postRun OverwriteCallback
}
// ApplyOption configures the ApplyModifications options.
ApplyOption func(r *applyOptions)
)
// ApplyPreRun sets pre-runner for the ApplyModifications function.
func ApplyPreRun(preRun OverwriteCallback) ApplyOption {
return func(o *applyOptions) {
o.preRun = preRun
}
}
// ApplyPostRun sets pos-runner for the ApplyModifications function.
func ApplyPostRun(postRun OverwriteCallback) ApplyOption {
return func(o *applyOptions) {
o.postRun = postRun
}
}
// ApplyModifications copy all modifications from the temporary folder to the target path.
func (r *Runner) ApplyModifications(options ...ApplyOption) (SourceModification, error) {
opts := applyOptions{}
for _, apply := range options {
apply(&opts)
}
// fetch the source modification
sm := NewSourceModification()
for _, file := range r.results {
fileName := file.Name()
_, err := os.Stat(fileName)
switch {
case os.IsNotExist(err):
sm.AppendCreatedFiles(fileName) // if the file doesn't exist in the source, it means it has been created by the runner
case err != nil:
return sm, err
default:
sm.AppendModifiedFiles(fileName) // the file has been modified by the runner
}
}
r.results = make([]genny.File, 0)
if _, err := os.Stat(r.tmpPath); os.IsNotExist(err) {
return sm, nil
}
duplicatedFiles, err := xos.ValidateFolderCopy(r.tmpPath, r.Root, sm.ModifiedFiles()...)
if err != nil {
return sm, err
}
if opts.preRun != nil {
if err := opts.preRun(sm.CreatedFiles(), sm.ModifiedFiles(), duplicatedFiles); err != nil {
return sm, err
}
}
// Create the target path and copy the content from the temporary folder.
if err := os.MkdirAll(r.Root, os.ModePerm); err != nil {
return sm, err
}
if err := xos.CopyFolder(r.tmpPath, r.Root); err != nil {
return sm, err
}
if err := os.RemoveAll(r.tmpPath); err != nil {
return sm, err
}
if opts.postRun != nil {
if err := opts.postRun(sm.CreatedFiles(), sm.ModifiedFiles(), duplicatedFiles); err != nil {
return sm, err
}
}
return sm, nil
}
// RunAndApply run the generators and apply the modifications to the target path.
func (r *Runner) RunAndApply(gens *genny.Generator, options ...ApplyOption) (SourceModification, error) {
if err := r.Run(gens); err != nil {
return SourceModification{}, err
}
return r.ApplyModifications(options...)
}
// Run all generators into a temp folder for we can apply the modifications later.
func (r *Runner) Run(gens ...*genny.Generator) error {
// ensure the underlying genny runner starts clean to avoid re-running previous generators
r.cleanup()
for _, gen := range gens {
if err := r.Runner.With(gen); err != nil {
return err
}
}
if err := r.Runner.Run(); err != nil {
return err
}
r.results = append(r.results, r.Results().Files...)
// reset again so a future Run call starts fresh
r.cleanup()
return nil
}
func wetFileFn(runner *Runner) func(genny.File) (genny.File, error) {
return func(f genny.File) (genny.File, error) {
if d, ok := f.(genny.Dir); ok {
if err := os.MkdirAll(d.Name(), d.Perm); err != nil {
return f, err
}
return d, nil
}
var err error
if !filepath.IsAbs(runner.Root) {
runner.Root, err = filepath.Abs(runner.Root)
if err != nil {
return f, err
}
}
name := f.Name()
if !filepath.IsAbs(name) {
name = filepath.Join(runner.Root, name)
}
relPath, err := filepath.Rel(runner.Root, name)
if err != nil {
return f, err
}
dstPath := filepath.Join(runner.tmpPath, relPath)
dir := filepath.Dir(dstPath)
if err := os.MkdirAll(dir, 0o755); err != nil {
return f, err
}
ff, err := os.Create(dstPath)
if err != nil {
return f, err
}
defer ff.Close()
if _, err := io.Copy(ff, f); err != nil {
return f, err
}
return f, nil
}
}