This repository has no description
1package pipelines
2
3import (
4 "errors"
5 "html/template"
6 "regexp"
7 "strings"
8 "time"
9
10 terminal "github.com/buildkite/terminal-to-html/v3"
11 "github.com/gorilla/websocket"
12 "tangled.org/core/appview/pages/markup/sanitizer"
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}
25
26func NewAnsiState() *ansiState {
27 return &ansiState{
28 stack: []string{},
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 := 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
53// isExpectedClose reports whether err is a clean websocket close (or nil).
54func isExpectedClose(err error) bool {
55 if err == nil {
56 return true
57 }
58 var ce *websocket.CloseError
59 if errors.As(err, &ce) {
60 switch ce.Code {
61 case websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseAbnormalClosure:
62 return true
63 }
64 }
65 return false
66}
67
68func derefStr(s *string) string {
69 if s == nil {
70 return ""
71 }
72 return *s
73}
74
75func parseRFC3339(s string) time.Time {
76 t, err := time.Parse(time.RFC3339, s)
77 if err != nil {
78 return time.Time{}
79 }
80 return t
81}