Monorepo for Tangled
tangled.org
1package pipelines
2
3import (
4 "html/template"
5 "regexp"
6 "strings"
7
8 terminal "github.com/buildkite/terminal-to-html/v3"
9 "github.com/gorilla/websocket"
10 "tangled.org/core/appview/pages/markup"
11)
12
13// matches any ANSI escape sequence: ESC [ <params> m
14var sequenceRe = regexp.MustCompile(`\x1b\[([\d;]*)m`)
15
16// ansiState tracks the active stack across log lines
17// each non-reset SGR code is pushed onto the stack; a reset clears it.
18//
19// the stack contents are prepended to each new line so colours carry over.
20type ansiState struct {
21 stack []string
22 sanitizer markup.Sanitizer
23}
24
25func NewAnsiState() *ansiState {
26 return &ansiState{
27 stack: []string{},
28 sanitizer: markup.NewSanitizer(),
29 }
30}
31
32func (a *ansiState) Render(line string) template.HTML {
33 // prepend whatever sequences are still open from the previous line
34 prefix := strings.Join(a.stack, "")
35 // render current line with the existing prefix
36 rendered := terminal.Render([]byte(prefix + line))
37 // sanitize
38 sanitized := a.sanitizer.SanitizeLogs(rendered)
39
40 // update the stack with sequences from current line
41 for _, m := range sequenceRe.FindAllStringSubmatch(line, -1) {
42 params := m[1]
43 if params == "" || params == "0" || params == "00" {
44 a.stack = a.stack[:0]
45 } else {
46 a.stack = append(a.stack, m[0])
47 }
48 }
49
50 return template.HTML(sanitized)
51}
52
53type LogEvent struct {
54 Msg []byte
55 Err error
56}
57
58func (ev *LogEvent) IsCloseError() bool {
59 return websocket.IsCloseError(
60 ev.Err,
61 websocket.CloseNormalClosure,
62 websocket.CloseGoingAway,
63 websocket.CloseAbnormalClosure,
64 )
65}
66
67func ReadLogs(conn *websocket.Conn, ch chan LogEvent) {
68 defer close(ch)
69 for {
70 if conn == nil {
71 return
72 }
73 _, msg, err := conn.ReadMessage()
74 if err != nil {
75 ch <- LogEvent{Err: err}
76 return
77 }
78 ch <- LogEvent{Msg: msg}
79 }
80}
81
82func SpindleURL(dev bool, spindle, knot, rkey, workflow string) string {
83 scheme := "wss"
84 if dev {
85 scheme = "ws"
86 }
87 return scheme + "://" + strings.Join([]string{spindle, "logs", knot, rkey, workflow}, "/")
88}