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