repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/web/highlight.go
Rawpackage web
import (
"html/template"
"strings"
)
// HighlightLog renders a log tail as HTML with structural lines accented:
// [luci] markers, step failures, and common error shapes stand out, build
// noise recedes. All content stays escaped.
func HighlightLog(log string) template.HTML {
var b strings.Builder
for i, line := range strings.Split(log, "\n") {
if i > 0 {
b.WriteByte('\n')
}
if line == "" {
continue
}
escaped := template.HTMLEscapeString(line)
switch {
case strings.HasPrefix(line, "[luci] step ") && (strings.Contains(line, " failed after ") || strings.Contains(line, "done in")):
b.WriteString(`<span class="log-mark">` + escaped + `</span>`)
case strings.HasPrefix(line, "[luci]"):
b.WriteString(`<span class="log-mark">` + escaped + `</span>`)
case isErrorLine(line):
b.WriteString(`<span class="log-fail">` + escaped + `</span>`)
case isNoiseLine(line):
b.WriteString(`<span class="log-dim">` + escaped + `</span>`)
default:
b.WriteString(escaped)
}
}
return template.HTML(b.String())
}
func isErrorLine(line string) bool {
for _, prefix := range []string{"error", "Error", "ERROR", "error[", "failed", "FAILED", "Failed", "panic:", "warning:", "warning: ", "✖", "exit status"} {
if strings.HasPrefix(line, prefix) {
return true
}
}
return false
}
func isNoiseLine(line string) bool {
for _, prefix := range []string{" Compiling", " Downloaded", " Downloading", " Building", " Building", "info:", "notice:"} {
if strings.HasPrefix(line, prefix) {
return true
}
}
return false
}
// HighlightKDL accents syntax without interpreting configuration content.
// Every source byte is escaped before it reaches the template.
func HighlightKDL(source string) template.HTML {
var out strings.Builder
for i := 0; i < len(source); {
end := i
class := ""
switch source[i] {
case '"':
end++
for end < len(source) {
if source[end] == '\\' && end+1 < len(source) {
end += 2
continue
}
if source[end] == '"' {
end++
break
}
end++
}
class = "kdl-string"
case '/':
if i+1 < len(source) && source[i+1] == '/' {
end = strings.IndexByte(source[i:], '\n')
if end < 0 {
end = len(source)
} else {
end += i
}
class = "kdl-comment"
}
}
if end == i {
for end < len(source) && (source[end] == '_' || source[end] == '-' || (source[end] >= 'a' && source[end] <= 'z') || (source[end] >= 'A' && source[end] <= 'Z')) {
end++
}
if end == i {
end++
} else if isKDLKeyword(source[i:end]) {
class = "kdl-keyword"
}
}
text := template.HTMLEscapeString(source[i:end])
if class != "" {
out.WriteString(`<span class="` + class + `">` + text + `</span>`)
} else {
out.WriteString(text)
}
i = end
}
return template.HTML(out.String())
}
func isKDLKeyword(value string) bool {
switch value {
case "job", "image", "run", "trigger", "push", "branch", "tag", "include", "exclude", "schedule", "matrix", "cache", "key", "file", "secret", "publish", "from", "to":
return true
}
return false
}