diff options
Diffstat (limited to 'modules/log/colors.go')
-rw-r--r-- | modules/log/colors.go | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/modules/log/colors.go b/modules/log/colors.go new file mode 100644 index 0000000000..fa8a664f08 --- /dev/null +++ b/modules/log/colors.go @@ -0,0 +1,348 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package log + +import ( + "fmt" + "io" + "strconv" + "strings" +) + +const escape = "\033" + +// ColorAttribute defines a single SGR Code +type ColorAttribute int + +// Base ColorAttributes +const ( + Reset ColorAttribute = iota + Bold + Faint + Italic + Underline + BlinkSlow + BlinkRapid + ReverseVideo + Concealed + CrossedOut +) + +// Foreground text colors +const ( + FgBlack ColorAttribute = iota + 30 + FgRed + FgGreen + FgYellow + FgBlue + FgMagenta + FgCyan + FgWhite +) + +// Foreground Hi-Intensity text colors +const ( + FgHiBlack ColorAttribute = iota + 90 + FgHiRed + FgHiGreen + FgHiYellow + FgHiBlue + FgHiMagenta + FgHiCyan + FgHiWhite +) + +// Background text colors +const ( + BgBlack ColorAttribute = iota + 40 + BgRed + BgGreen + BgYellow + BgBlue + BgMagenta + BgCyan + BgWhite +) + +// Background Hi-Intensity text colors +const ( + BgHiBlack ColorAttribute = iota + 100 + BgHiRed + BgHiGreen + BgHiYellow + BgHiBlue + BgHiMagenta + BgHiCyan + BgHiWhite +) + +var colorAttributeToString = map[ColorAttribute]string{ + Reset: "Reset", + Bold: "Bold", + Faint: "Faint", + Italic: "Italic", + Underline: "Underline", + BlinkSlow: "BlinkSlow", + BlinkRapid: "BlinkRapid", + ReverseVideo: "ReverseVideo", + Concealed: "Concealed", + CrossedOut: "CrossedOut", + FgBlack: "FgBlack", + FgRed: "FgRed", + FgGreen: "FgGreen", + FgYellow: "FgYellow", + FgBlue: "FgBlue", + FgMagenta: "FgMagenta", + FgCyan: "FgCyan", + FgWhite: "FgWhite", + FgHiBlack: "FgHiBlack", + FgHiRed: "FgHiRed", + FgHiGreen: "FgHiGreen", + FgHiYellow: "FgHiYellow", + FgHiBlue: "FgHiBlue", + FgHiMagenta: "FgHiMagenta", + FgHiCyan: "FgHiCyan", + FgHiWhite: "FgHiWhite", + BgBlack: "BgBlack", + BgRed: "BgRed", + BgGreen: "BgGreen", + BgYellow: "BgYellow", + BgBlue: "BgBlue", + BgMagenta: "BgMagenta", + BgCyan: "BgCyan", + BgWhite: "BgWhite", + BgHiBlack: "BgHiBlack", + BgHiRed: "BgHiRed", + BgHiGreen: "BgHiGreen", + BgHiYellow: "BgHiYellow", + BgHiBlue: "BgHiBlue", + BgHiMagenta: "BgHiMagenta", + BgHiCyan: "BgHiCyan", + BgHiWhite: "BgHiWhite", +} + +func (c *ColorAttribute) String() string { + return colorAttributeToString[*c] +} + +var colorAttributeFromString = map[string]ColorAttribute{} + +// ColorAttributeFromString will return a ColorAttribute given a string +func ColorAttributeFromString(from string) ColorAttribute { + lowerFrom := strings.TrimSpace(strings.ToLower(from)) + return colorAttributeFromString[lowerFrom] +} + +// ColorString converts a list of ColorAttributes to a color string +func ColorString(attrs ...ColorAttribute) string { + return string(ColorBytes(attrs...)) +} + +// ColorBytes converts a list of ColorAttributes to a byte array +func ColorBytes(attrs ...ColorAttribute) []byte { + bytes := make([]byte, 0, 20) + bytes = append(bytes, escape[0], '[') + if len(attrs) > 0 { + bytes = append(bytes, strconv.Itoa(int(attrs[0]))...) + for _, a := range attrs[1:] { + bytes = append(bytes, ';') + bytes = append(bytes, strconv.Itoa(int(a))...) + } + } else { + bytes = append(bytes, strconv.Itoa(int(Bold))...) + } + bytes = append(bytes, 'm') + return bytes +} + +var levelToColor = map[Level]string{ + TRACE: ColorString(Bold, FgCyan), + DEBUG: ColorString(Bold, FgBlue), + INFO: ColorString(Bold, FgGreen), + WARN: ColorString(Bold, FgYellow), + ERROR: ColorString(Bold, FgRed), + CRITICAL: ColorString(Bold, BgMagenta), + FATAL: ColorString(Bold, BgRed), + NONE: ColorString(Reset), +} + +var resetBytes = ColorBytes(Reset) +var fgCyanBytes = ColorBytes(FgCyan) +var fgGreenBytes = ColorBytes(FgGreen) +var fgBoldBytes = ColorBytes(Bold) + +type protectedANSIWriterMode int + +const ( + escapeAll protectedANSIWriterMode = iota + allowColor + removeColor +) + +type protectedANSIWriter struct { + w io.Writer + mode protectedANSIWriterMode +} + +// Write will protect against unusual characters +func (c *protectedANSIWriter) Write(bytes []byte) (int, error) { + end := len(bytes) + totalWritten := 0 +normalLoop: + for i := 0; i < end; { + lasti := i + + if c.mode == escapeAll { + for i < end && (bytes[i] >= ' ' || bytes[i] == '\n') { + i++ + } + } else { + for i < end && bytes[i] >= ' ' { + i++ + } + } + + if i > lasti { + written, err := c.w.Write(bytes[lasti:i]) + totalWritten = totalWritten + written + if err != nil { + return totalWritten, err + } + + } + if i >= end { + break + } + + // If we're not just escaping all we should prefix all newlines with a \t + if c.mode != escapeAll { + if bytes[i] == '\n' { + written, err := c.w.Write([]byte{'\n', '\t'}) + if written > 0 { + totalWritten++ + } + if err != nil { + return totalWritten, err + } + i++ + continue normalLoop + } + + if bytes[i] == escape[0] && i+1 < end && bytes[i+1] == '[' { + for j := i + 2; j < end; j++ { + if bytes[j] >= '0' && bytes[j] <= '9' { + continue + } + if bytes[j] == ';' { + continue + } + if bytes[j] == 'm' { + if c.mode == allowColor { + written, err := c.w.Write(bytes[i : j+1]) + totalWritten = totalWritten + written + if err != nil { + return totalWritten, err + } + } else { + totalWritten = j + } + i = j + 1 + continue normalLoop + } + break + } + } + } + + // Process naughty character + if _, err := fmt.Fprintf(c.w, `\%#o03d`, bytes[i]); err != nil { + return totalWritten, err + } + i++ + totalWritten++ + } + return totalWritten, nil +} + +// ColoredValue will Color the provided value +type ColoredValue struct { + ColorBytes *[]byte + ResetBytes *[]byte + Value *interface{} +} + +// NewColoredValue is a helper function to create a ColoredValue from a Value +// If no color is provided it defaults to Bold with standard Reset +// If a ColoredValue is provided it is not changed +func NewColoredValue(value interface{}, color ...ColorAttribute) *ColoredValue { + return NewColoredValuePointer(&value, color...) +} + +// NewColoredValuePointer is a helper function to create a ColoredValue from a Value Pointer +// If no color is provided it defaults to Bold with standard Reset +// If a ColoredValue is provided it is not changed +func NewColoredValuePointer(value *interface{}, color ...ColorAttribute) *ColoredValue { + if val, ok := (*value).(*ColoredValue); ok { + return val + } + if len(color) > 0 { + bytes := ColorBytes(color...) + return &ColoredValue{ + ColorBytes: &bytes, + ResetBytes: &resetBytes, + Value: value, + } + } + return &ColoredValue{ + ColorBytes: &fgBoldBytes, + ResetBytes: &resetBytes, + Value: value, + } + +} + +// NewColoredValueBytes creates a value from the provided value with color bytes +// If a ColoredValue is provided it is not changed +func NewColoredValueBytes(value interface{}, colorBytes *[]byte) *ColoredValue { + if val, ok := value.(*ColoredValue); ok { + return val + } + return &ColoredValue{ + ColorBytes: colorBytes, + ResetBytes: &resetBytes, + Value: &value, + } +} + +// Format will format the provided value and protect against ANSI spoofing within the value +func (cv *ColoredValue) Format(s fmt.State, c rune) { + s.Write([]byte(*cv.ColorBytes)) + fmt.Fprintf(&protectedANSIWriter{w: s}, fmtString(s, c), *(cv.Value)) + s.Write([]byte(*cv.ResetBytes)) +} + +func fmtString(s fmt.State, c rune) string { + var width, precision string + base := make([]byte, 0, 8) + base = append(base, '%') + for _, c := range []byte(" +-#0") { + if s.Flag(int(c)) { + base = append(base, c) + } + } + if w, ok := s.Width(); ok { + width = strconv.Itoa(w) + } + if p, ok := s.Precision(); ok { + precision = "." + strconv.Itoa(p) + } + return fmt.Sprintf("%s%s%s%c", base, width, precision, c) +} + +func init() { + for attr, from := range colorAttributeToString { + colorAttributeFromString[strings.ToLower(from)] = attr + } +} |