command/command.go (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | 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 {
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)
time.Sleep(timeout)
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 %s]\n", cmd.name, strings.Join(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
}
|