Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
334 lines (296 sloc) 7.13 KB
package genny
import (
"context"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/markbates/oncer"
"github.com/markbates/safe"
"github.com/sirupsen/logrus"
)
type RunFn func(r *Runner) error
// Runner will run the generators
type Runner struct {
Logger Logger // Logger to use for the run
Context context.Context // context to use for the run
ExecFn func(*exec.Cmd) error // function to use when executing files
FileFn func(File) (File, error) // function to use when writing files
ChdirFn func(string, func() error) error // function to use when changing directories
DeleteFn func(string) error // function used to delete files/folders
RequestFn func(*http.Request, *http.Client) (*http.Response, error) // function used to make http requests
LookPathFn func(string) (string, error) // function used to make exec.LookPath lookups
Root string // the root of the write path
Disk *Disk
steps map[string]*Step
generators []*Generator
moot *sync.RWMutex
results Results
curGen *Generator
}
func (r *Runner) Results() Results {
r.moot.Lock()
defer r.moot.Unlock()
r.results.Files = r.Disk.Files()
return r.results
}
func (r *Runner) WithRun(fn RunFn) {
g := New()
g.RunFn(fn)
r.With(g)
}
// With adds a Generator to the Runner
func (r *Runner) With(g *Generator) error {
r.moot.Lock()
step, ok := r.steps[g.StepName]
if !ok {
var err error
step, err = NewStep(g, len(r.steps))
if err != nil {
return err
}
}
r.moot.Unlock()
return r.WithStep(g.StepName, step)
}
func (r *Runner) WithGroup(gg *Group) {
for _, g := range gg.Generators {
r.With(g)
}
}
// WithNew takes a Generator and an error.
// Perfect for new-ing up generators
/*
// foo.New(Options) (*genny.Generator, error)
if err := run.WithNew(foo.New(opts)); err != nil {
return err
}
*/
func (r *Runner) WithNew(g *Generator, err error) error {
if err != nil {
return err
}
r.With(g)
return nil
}
// WithFn will evaluate the function and if successful it will add
// the Generator to the Runner, otherwise it will return the error
// Deprecated
func (r *Runner) WithFn(fn func() (*Generator, error)) error {
oncer.Deprecate(5, "genny.Runner#WithFn", "")
return safe.RunE(func() error {
g, err := fn()
if err != nil {
return err
}
r.With(g)
return nil
})
}
func (r *Runner) WithStep(name string, step *Step) error {
r.moot.Lock()
defer r.moot.Unlock()
if len(name) == 0 {
name = stepName()
}
r.steps[name] = step
return nil
}
func (r *Runner) Steps() []*Step {
r.moot.RLock()
var steps []*Step
for _, step := range r.steps {
steps = append(steps, step)
}
sort.Slice(steps, func(a, b int) bool {
return steps[a].index < steps[b].index
})
r.moot.RUnlock()
return steps
}
func (r *Runner) FindStep(name string) (*Step, error) {
r.moot.RLock()
s, ok := r.steps[name]
r.moot.RUnlock()
if !ok {
return nil, fmt.Errorf("could not find step %s", name)
}
return s, nil
}
func (r *Runner) ReplaceStep(name string, s *Step) error {
os, err := r.FindStep(name)
if err != nil {
return err
}
s.index = os.index
return r.WithStep(name, s)
}
func (r *Runner) Run() error {
if f, ok := r.Logger.(io.Closer); ok {
defer f.Close()
}
steps := r.Steps()
// payload := events.Payload{
// "runner": r,
// "steps": steps,
// }
// events.EmitPayload(EvtStarted, payload)
for _, step := range steps {
if err := step.Run(r); err != nil {
// payload = events.Payload{
// "runner": r,
// "step": step,
// }
// events.EmitError(EvtFinishedErr, err, payload)
return err
}
}
// events.EmitPayload(EvtFinished, payload)
return nil
}
// Exec can be used inside of Generators to run commands
func (r *Runner) Exec(cmd *exec.Cmd) error {
r.results.Commands = append(r.results.Commands, cmd)
r.Logger.Debug("Exec: ", strings.Join(cmd.Args, " "))
if r.ExecFn == nil {
return nil
}
return safe.RunE(func() error {
return r.ExecFn(cmd)
})
}
func (r *Runner) LookPath(s string) (string, error) {
r.Logger.Debug("LookPath: ", s)
if r.LookPathFn != nil {
return r.LookPathFn(s)
}
return s, nil
}
// File can be used inside of Generators to write files
func (r *Runner) File(f File) error {
if r.curGen != nil {
var err error
f, err = r.curGen.Transform(f)
if err != nil {
return err
}
}
name := f.Name()
if !filepath.IsAbs(name) {
name = filepath.Join(r.Root, name)
}
_, isDir := f.(Dir)
if isDir {
r.Logger.Debug("Dir: ", name)
} else {
r.Logger.Debug("File: ", name)
}
if r.FileFn != nil {
err := safe.RunE(func() error {
var e error
if f, e = r.FileFn(f); e != nil {
return e
}
if s, ok := f.(io.Seeker); ok {
s.Seek(0, 0)
}
return nil
})
if err != nil {
return err
}
}
f = NewFile(f.Name(), f)
if s, ok := f.(io.Seeker); ok {
s.Seek(0, 0)
}
r.Disk.Add(f)
return nil
}
func (r *Runner) FindFile(name string) (File, error) {
return r.Disk.Find(name)
}
// Chdir will change to the specified directory
// and revert back to the current directory when
// the runner function has returned.
// If the directory does not exist, it will be
// created for you.
func (r *Runner) Chdir(path string, fn func() error) error {
if len(path) == 0 {
return fn()
}
r.Logger.Debug("Chdir: ", path)
if r.ChdirFn != nil {
return safe.RunE(func() error {
return r.ChdirFn(path, fn)
})
}
if err := safe.RunE(fn); err != nil {
return err
}
return nil
}
func (r *Runner) Delete(path string) error {
r.Logger.Debug("Delete: ", path)
defer r.Disk.Remove(path)
if r.DeleteFn != nil {
return safe.RunE(func() error {
return r.DeleteFn(path)
})
}
return nil
}
func (r *Runner) Request(req *http.Request) (*http.Response, error) {
return r.RequestWithClient(req, http.DefaultClient)
}
func (r *Runner) RequestWithClient(req *http.Request, c *http.Client) (*http.Response, error) {
key := fmt.Sprintf("[%s] %s\n", strings.ToUpper(req.Method), req.URL)
r.Logger.Debug("Request: ", key)
store := func(res *http.Response, err error) (*http.Response, error) {
r.moot.Lock()
r.results.Requests = append(r.results.Requests, RequestResult{
Request: req,
Response: res,
Client: c,
Error: err,
})
r.moot.Unlock()
return res, err
}
if r.RequestFn == nil {
return store(nil, nil)
}
var res *http.Response
err := safe.RunE(func() error {
var e error
res, e = r.RequestFn(req, c)
if e != nil {
return e
}
return nil
})
return store(res, err)
}
// NewRunner will NOT execute commands and write files
// it is NOT destructive it is just the most basic Runner
// you can have.
func NewRunner(ctx context.Context) *Runner {
pwd, _ := os.Getwd()
l := logrus.New()
l.Out = os.Stdout
l.SetLevel(logrus.DebugLevel)
r := &Runner{
Logger: l,
Context: ctx,
Root: pwd,
moot: &sync.RWMutex{},
steps: map[string]*Step{},
}
r.Disk = newDisk(r)
return r
}
You can’t perform that action at this time.