package command import ( "errors" "fmt" "io" "os" "os/exec" "strings" "time" "alin.ovh/erl/output" ) type Command interface { Start() error Wait() error Stop() error } type Cmd struct { *exec.Cmd name string args []string out output.Output opts Options } type Options struct { Output io.Writer Stdout io.Writer Stderr io.Writer } const timeout = 1 * time.Second func New(name string, args []string, options Options) *Cmd { if options.Stdout == nil { options.Stdout = os.Stdout } if options.Stderr == nil { options.Stderr = os.Stderr } if options.Output == nil { options.Output = os.Stderr } return &Cmd{ name: name, args: args, opts: options, out: output.New(options.Output), } } func (cmd *Cmd) Stop() error { if cmd.Cmd == nil || cmd.Process == nil || cmd.ProcessState != nil { return nil } err := cmd.Process.Signal(os.Interrupt) if err != nil { return fmt.Errorf("error killing command: %v", err) } if cmd.ProcessState == nil { cmd.out.Info("[command not stopped, waiting %s seconds before killing]\n", timeout) t := timeout / 10 for range 10 { time.Sleep(t) if cmd.ProcessState != nil { return nil } } if cmd.ProcessState == nil { cmd.out.Info("[command not stopped, killing]\n") err := cmd.Process.Kill() if err != nil { return fmt.Errorf("error killing command: %v", err) } } } return nil } func (cmd *Cmd) Exited() bool { return cmd.Cmd != nil && cmd.ProcessState != nil && cmd.ProcessState.Exited() } func (cmd *Cmd) Wait() error { err := cmd.Cmd.Wait() if err != nil { var exitErr *exec.ExitError if errors.As(err, &exitErr) { if cmd.Exited() { cmd.out.Fail("[command exited with code %d]\n", cmd.ProcessState.ExitCode()) } else { cmd.out.Fail("[command stopped]\n") } } else { return fmt.Errorf("error waiting for command: %v", err) } } if cmd.Exited() && cmd.ProcessState.Success() { cmd.out.Success("[command finished]\n") } return nil } func (cmd *Cmd) Start() error { cmd.makeCommand() cmd.out.Success("[running: %s]\n", strings.Join(append([]string{cmd.name}, cmd.args...), " ")) err := cmd.Cmd.Start() if err != nil { return fmt.Errorf("error starting command: %v", err) } return err } func (cmd *Cmd) makeCommand() { cmd.Cmd = exec.Command(cmd.name, cmd.args...) cmd.Stdout = cmd.opts.Stdout cmd.Stderr = cmd.opts.Stderr }