gobuffalo / genny

A framework for writing modular generators
MIT License
65 stars 14 forks source link

genny v3 proposal: change API to make usage easier #43

Open fasmat opened 2 years ago

fasmat commented 2 years ago

genny v2 could be improved imho by making the API feel more like one is writing go code using the standard library instead of an abstraction of such. This issue serves as a collection of sorts for those API proposals:

remove all usages of packr

With the io/fs package introduced in go 1.16 we can remove all usages of packr and replace them with their new equivalents.

genny.Runner should not change behavior after instantiation

All fields of genny.Runner should be private to ensure they are only set during instantiation of the object and not modified afterwards. Provide a New function with options like WithLogger, WithExecFn, etc. to set those fields. WithDisk can be used by gentest.NewRunner to return a runner for testing together with a reference to the virtual disk for seeding.

type Runner struct {
        logger     LoggerInterface    // Instead of a concrete type Logger should just be an interface
    context    context.Context
    execFn     func(*exec.Cmd) error
    fileFn     func(File) (File, error)
    chdirFn    func(string, func() error) error
    deleteFn   func(string) error
    requestFn  func(*http.Request, *http.Client) (*http.Response, error)
    lookPathFn func(string) (string, error)
    root       string
    disk       *Disk
    steps      map[string]*Step
    moot       *sync.RWMutex
    results    Results
    curGen     *Generator
}

genny.Runner should abstract usage of virtual / real files

The disk object represents a virtual file system that is used in a dry run to simulate the actual file system. It should not be accessed from outside genny.Runner to prevent the user from thinking that modifying the disk actually changes something on the hard drive when a wet runner is used. Instead most of it's functionality will be added to genny.Runner:

// replaces File. Create creates a new file the returned file is a proxy of either os.File or a virtual file
// mkdir and mkdirall behave like their os package equivalents or create directories on the virtual disk
func (r *Runner) Create(name string) (*File, error)
func (r *Runner) Mkdir(name string, perm os.FileMode) error
func (r *Runner) MkdirAll(path string, perm os.FileMode) error

// new method signature, runner remembers where it chdir'd to correctly access to the virtual disk after changing the directory
// it also changes back to the original directory when the runner finishes
func (r *Runner) Chdir(dir string) error

// replaces from FindFile. tries to open a file on disk or the virtual file system
func (r *Runner) Open(name string) (*File, error)

// renamed from delete to be more in line with the "os" package
func (r *Runner) Remove(name string) error
func (r *Runner) RemoveAll(path string) error

// updated method signature to be more in line with "net/http" package:
func (r *Runner) DoRequest(req *http.Request) (*http.Response, error)
func (r *Runner) DoRequestWithClient(c *http.Client, req *http.Request) (*http.Response, error)

// unchanged
func (r *Runner) Exec(cmd *exec.Cmd) error
func (r *Runner) FindStep(name string) (*Step, error)
func (r *Runner) LookPath(file string) (string, error)
func (r *Runner) ReplaceStep(name string, s *Step) error
func (r *Runner) Results() Results
func (r *Runner) Run() error
func (r *Runner) Steps() []*Step
func (r *Runner) With(g *Generator) error
func (r *Runner) WithFn(fn func() (*Generator, error)) error
func (r *Runner) WithGroup(gg *Group)
func (r *Runner) WithNew(g *Generator, err error) error
func (r *Runner) WithRun(fn RunFn)
func (r *Runner) WithStep(name string, step *Step) error

gentest package updates

To make it easier to setup a virtual filesystem in tests now gentest.NewRunner() returns access to the internal disk of the runner that is used for testing:

func NewRunner() (*genny.Runner, *genny.Disk)

implementation of io/fs interfaces

Disk should implement the following interfaces; Runner should probably implement most of these interfaces too:

fs.FS{}         // because it is a FS
fs.StatFS{}     // allows to walk the Disk with fs.WalkDir
fs.GlobFS{}     // to easily find all files matching a pattern
fs.ReadDirFS{}  // equivalent of os.ReadDir; allows more efficient walking
fs.ReadFileFS{} // equivalent of os.ReadFile
fs.SubFS{}      // allows the creation of sub-filesystems

Returned virtual files and directories should implement the io/fs interfaces like their os equivalents:

fs.File{}
fs.DirEntry{}
tbruyelle commented 1 year ago

Hey @fasmat,

I just figured out that current runners doesn't support fs.FS, so this issue would be a great enhancement.

What I have in mind is a single NewRunner(fs.FS) :

That said DryRunner and WetRunner could be kept, but they will just call NewRunner with the appropriate fs.

What do you think ?

EDIT: seems like I went a little too fast, fs.FS is only a readable interface, so it cannot be used by a runner. Maybe this will change in the future.