/
support.go
139 lines (118 loc) · 3.1 KB
/
support.go
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
134
135
136
137
138
139
package procker
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strings"
"github.com/flynn/go-shlex"
)
// PrefixedWriter implements prefixed output for an io.Writer object.
type PrefixedWriter struct {
Prefix string
writer io.Writer
inline bool
}
// NewPrefixedWriter creates a PrefixedWriter
func NewPrefixedWriter(w io.Writer, prefix string) io.Writer {
return &PrefixedWriter{Prefix: prefix, writer: w}
}
// Writes a Prefix string before writing to the underlying writer.
func (w *PrefixedWriter) Write(p []byte) (n int, err error) {
for _, b := range p {
if !w.inline {
io.WriteString(w.writer, w.Prefix)
}
w.writer.Write([]byte{b})
w.inline = b != '\n'
}
return len(p), nil
}
var procfileRegexp = regexp.MustCompile("^([A-Za-z0-9_]+):\\s*(.+)$")
// ParseProcfile parses io.Reader into a process's map.
// Read more about Procfiles: https://devcenter.heroku.com/articles/procfile
func ParseProcfile(r io.Reader) (map[string]string, error) {
p := make(map[string]string)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if len(line) == 0 {
continue
}
matches := procfileRegexp.FindStringSubmatch(line)
if matches == nil {
return nil, fmt.Errorf("procker: parse procfile error: invalid line found: '%s'", line)
}
name, command := matches[1], matches[2]
p[name] = command
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("procker: parse procfile error: %s", err)
}
return p, nil
}
// ParseEnv parses io.Reader into an arrays of strings
// representing the environment, in the form "key=value".
func ParseEnv(r io.Reader) ([]string, error) {
sysenv := env2Map(os.Environ())
localenv := make(map[string]string)
mapping := func(key string) string {
value, ok := localenv[key]
if !ok {
value, ok = sysenv[key]
}
return value
}
env := []string{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
entry := strings.TrimSpace(scanner.Text())
if len(entry) == 0 {
continue
}
pair := strings.SplitN(entry, "=", 2)
key := pair[0]
value := os.Expand(pair[1], mapping)
localenv[key] = value
env = append(env, fmt.Sprintf("%s=%s", key, value))
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("procker: parse env error: %s", err)
}
return env, nil
}
func env2Map(env []string) map[string]string {
m := make(map[string]string)
for _, value := range env {
pair := strings.SplitN(value, "=", 2)
if len(pair) == 2 {
m[pair[0]] = pair[1]
}
}
return m
}
var envvarRegexp = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*=")
// NewShellCommand creates a exec.Cmd based upon shell-style rules for
// quoting, escaping, and spaces.
//
// It extracts environment variables specified at the start of
// a command since Bourne-style shells allow it.
func NewShellCommand(cmd string) (*exec.Cmd, error) {
args, err := shlex.Split(cmd)
if err != nil {
return nil, err
}
var env []string
for _, arg := range args {
if !envvarRegexp.MatchString(arg) {
break
}
env = append(env, arg)
args = args[1:]
}
c := exec.Command(args[0], args[1:]...)
c.Env = env
return c, nil
}