Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
208 lines
4.8 KiB
Go
208 lines
4.8 KiB
Go
package xgenny
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/gobuffalo/genny/v2"
|
|
|
|
"github.com/ignite/cli/v29/ignite/pkg/randstr"
|
|
"github.com/ignite/cli/v29/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
|
|
}
|
|
}
|