package util
import (
"io"
"os"
"os/exec"
)
// CommandOpts contains options to attach Stdout/err to a command to run
// or set its initial directory
type CommandOpts struct {
Stdout io.Writer
Stderr io.Writer
Dir string
EnvAppend []string
}
// CommandRunner executes OS commands with the given parameters and options
type CommandRunner interface {
RunWithOptions(opts CommandOpts, name string, arg ...string) error
Run(name string, arg ...string) error
StartWithStdoutPipe(opts CommandOpts, name string, arg ...string) (io.ReadCloser, error)
Wait() error
}
// NewCommandRunner creates a new instance of the default implementation of
// CommandRunner
func NewCommandRunner() CommandRunner {
return &runner{}
}
type runner struct {
cmd *exec.Cmd
}
// RunWithOptions runs a command with the provided options
func (c *runner) RunWithOptions(opts CommandOpts, name string, arg ...string) error {
cmd := exec.Command(name, arg...)
if opts.Stdout != nil {
cmd.Stdout = opts.Stdout
}
if opts.Stderr != nil {
cmd.Stderr = opts.Stderr
}
if opts.Dir != "" {
cmd.Dir = opts.Dir
}
if len(opts.EnvAppend) > 0 {
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, opts.EnvAppend...)
}
return cmd.Run()
}
// Run executes a command with default options
func (c *runner) Run(name string, arg ...string) error {
return c.RunWithOptions(CommandOpts{}, name, arg...)
}
// StartWithStdoutPipe executes a command returning a ReadCloser connected to
// the command's stdout.
func (c *runner) StartWithStdoutPipe(opts CommandOpts, name string, arg ...string) (io.ReadCloser, error) {
c.cmd = exec.Command(name, arg...)
if opts.Stderr != nil {
c.cmd.Stderr = opts.Stderr
}
if opts.Dir != "" {
c.cmd.Dir = opts.Dir
}
if len(opts.EnvAppend) > 0 {
c.cmd.Env = os.Environ()
c.cmd.Env = append(c.cmd.Env, opts.EnvAppend...)
}
r, err := c.cmd.StdoutPipe()
if err != nil {
return nil, err
}
return r, c.cmd.Start()
}
// Wait waits for the command to exit.
func (c *runner) Wait() error {
return c.cmd.Wait()
}