package 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(`` + escaped + ``) case strings.HasPrefix(line, "[luci]"): b.WriteString(`` + escaped + ``) case isErrorLine(line): b.WriteString(`` + escaped + ``) case isNoiseLine(line): b.WriteString(`` + escaped + ``) 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(`` + text + ``) } 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 }