all repos — erl @ a23645522407dd2efae982050a2866152994def7

Execute Reload Loop

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
126
127
128
129
130
131
132
133
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 %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
}