Skip to content

Commit 903cd0f

Browse files
committed
feat(devx): creating proc and waitfor
1 parent 39cc2b1 commit 903cd0f

18 files changed

+979
-0
lines changed

.golangci.yml

+5
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ issues:
186186
- gosec
187187
- gas
188188

189+
- text: "G204:"
190+
linters:
191+
- gosec
192+
- gas
193+
189194
# Independently from option `exclude` we use default exclude patterns,
190195
# it can be disabled by this option. To list all
191196
# excluded by default patterns execute `golangci-lint run --help`.

devx/runproc/filewatcher.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"sort"
7+
"strconv"
8+
"strings"
9+
)
10+
11+
type Watcher struct {
12+
p []string
13+
14+
state string
15+
}
16+
17+
func calcTree(patterns []string) string {
18+
var files []string
19+
for _, p := range patterns {
20+
f, err := filepath.Glob(p)
21+
if err != nil {
22+
continue
23+
}
24+
files = append(files, f...)
25+
}
26+
27+
sort.Strings(files)
28+
29+
var t strings.Builder
30+
for _, f := range files {
31+
info, err := os.Stat(f)
32+
if err != nil {
33+
continue
34+
}
35+
t.WriteString(f + "\n")
36+
t.WriteString(info.ModTime().String() + "\n")
37+
t.WriteString(strconv.FormatInt(info.Size(), 10) + "\n")
38+
}
39+
40+
return t.String()
41+
}
42+
43+
func (w *Watcher) Changed() bool {
44+
tree := calcTree(w.p)
45+
if tree == w.state {
46+
return false
47+
}
48+
49+
w.state = tree
50+
return true
51+
}
52+
53+
func Watch(patterns []string) *Watcher {
54+
sort.Strings(patterns)
55+
56+
return &Watcher{p: patterns, state: calcTree(patterns)}
57+
}

devx/runproc/main.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"flag"
6+
"log"
7+
"os"
8+
"os/signal"
9+
)
10+
11+
func main() {
12+
file := flag.String("f", "", "Procfile to run.")
13+
localFile := flag.String("l", "", "Local Procfile to append.")
14+
flag.Parse()
15+
16+
log.SetFlags(log.Lshortfile)
17+
18+
if *file == "" {
19+
log.Fatal("No Procfile specified.")
20+
}
21+
22+
data, err := os.ReadFile(*file)
23+
if err != nil {
24+
log.Fatal(err)
25+
}
26+
27+
var envData []byte
28+
if *localFile != "" {
29+
envData, _ = os.ReadFile(*localFile)
30+
}
31+
32+
buf := bytes.NewBuffer(data)
33+
buf.WriteString("\n")
34+
buf.Write(envData)
35+
36+
tasks, err := Parse(buf)
37+
if err != nil {
38+
log.Fatalln(err)
39+
}
40+
log.SetFlags(log.Lshortfile)
41+
42+
run := NewRunner(tasks)
43+
44+
ch := make(chan os.Signal, 3)
45+
signal.Notify(ch, shutdownSignals...)
46+
go func() {
47+
<-ch
48+
go run.Stop()
49+
<-ch
50+
os.Exit(1)
51+
}()
52+
53+
err = run.Run()
54+
if err != nil {
55+
log.Fatalln(err)
56+
}
57+
}

devx/runproc/parse.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"strings"
8+
)
9+
10+
func isAlphaNum(r byte) bool {
11+
switch {
12+
case r >= 'a' && r <= 'z':
13+
case r >= 'A' && r <= 'Z':
14+
case r >= '0' && r <= '9':
15+
case r == '_':
16+
default:
17+
return false
18+
}
19+
return true
20+
}
21+
22+
func Parse(r io.Reader) ([]Task, error) {
23+
s := bufio.NewScanner(r)
24+
var tasks []Task
25+
var t Task
26+
var line int
27+
for s.Scan() {
28+
line++
29+
str := strings.TrimSpace(s.Text())
30+
if str == "" {
31+
t = Task{}
32+
continue
33+
}
34+
if str[0] == '@' {
35+
// parameter
36+
parts := strings.SplitN(str[1:], "=", 2)
37+
switch strings.TrimSpace(parts[0]) {
38+
case "oneshot":
39+
t.OneShot = true
40+
case "watch-file":
41+
if len(parts) != 2 {
42+
return nil, fmt.Errorf("line %d: missing file path for watch-file", line)
43+
}
44+
t.WatchFiles = append(t.WatchFiles, parts[1])
45+
default:
46+
return nil, fmt.Errorf("line %d: invalid option '%s'", line, parts[0])
47+
}
48+
}
49+
if !isAlphaNum(str[0]) {
50+
// comment/ignored
51+
continue
52+
}
53+
54+
parts := strings.SplitN(str, ":", 2)
55+
if len(parts) != 2 {
56+
return nil, fmt.Errorf("line %d: invalid proc definition '%s' (missing ':')", line, parts[0])
57+
}
58+
t.Name = strings.TrimSpace(parts[0])
59+
t.Command = strings.TrimSpace(parts[1])
60+
tasks = append(tasks, t)
61+
t = Task{}
62+
}
63+
return tasks, s.Err()
64+
}

devx/runproc/prefixer.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"io"
6+
)
7+
8+
type prefixer struct {
9+
prefix string
10+
out io.Writer
11+
buf []byte
12+
}
13+
14+
func NewPrefixer(out io.Writer, prefix string) io.Writer {
15+
return &prefixer{
16+
out: out,
17+
prefix: prefix,
18+
}
19+
}
20+
21+
func (w *prefixer) writePrefix() error {
22+
_, err := io.WriteString(w.out, w.prefix+" | ")
23+
return err
24+
}
25+
26+
func (w *prefixer) Write(p []byte) (int, error) {
27+
var n int
28+
for {
29+
l := bytes.IndexByte(p, '\n')
30+
if l == -1 {
31+
w.buf = append(w.buf, p...)
32+
return n + len(p), nil
33+
}
34+
w.buf = append(w.buf, p[:l+1]...)
35+
n += l + 1
36+
37+
err := w.writePrefix()
38+
if err != nil {
39+
return n, err
40+
}
41+
42+
// replace yarn escape sequences
43+
w.buf = bytes.ReplaceAll(w.buf, []byte("\x1b[2K"), nil)
44+
w.buf = bytes.ReplaceAll(w.buf, []byte("\x1b[1G"), nil)
45+
46+
_, err = w.out.Write(w.buf)
47+
if err != nil {
48+
return n, err
49+
}
50+
w.buf = w.buf[:0]
51+
52+
_, err = w.out.Write([]byte("\x1b[0m"))
53+
if err != nil {
54+
return n, err
55+
}
56+
57+
p = p[l+1:]
58+
}
59+
}

0 commit comments

Comments
 (0)