code.gitea.io/gitea@v1.19.3/modules/log/colors.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package log
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"reflect"
    10  	"strconv"
    11  	"strings"
    12  )
    13  
    14  const escape = "\033"
    15  
    16  // ColorAttribute defines a single SGR Code
    17  type ColorAttribute int
    18  
    19  // Base ColorAttributes
    20  const (
    21  	Reset ColorAttribute = iota
    22  	Bold
    23  	Faint
    24  	Italic
    25  	Underline
    26  	BlinkSlow
    27  	BlinkRapid
    28  	ReverseVideo
    29  	Concealed
    30  	CrossedOut
    31  )
    32  
    33  // Foreground text colors
    34  const (
    35  	FgBlack ColorAttribute = iota + 30
    36  	FgRed
    37  	FgGreen
    38  	FgYellow
    39  	FgBlue
    40  	FgMagenta
    41  	FgCyan
    42  	FgWhite
    43  )
    44  
    45  // Foreground Hi-Intensity text colors
    46  const (
    47  	FgHiBlack ColorAttribute = iota + 90
    48  	FgHiRed
    49  	FgHiGreen
    50  	FgHiYellow
    51  	FgHiBlue
    52  	FgHiMagenta
    53  	FgHiCyan
    54  	FgHiWhite
    55  )
    56  
    57  // Background text colors
    58  const (
    59  	BgBlack ColorAttribute = iota + 40
    60  	BgRed
    61  	BgGreen
    62  	BgYellow
    63  	BgBlue
    64  	BgMagenta
    65  	BgCyan
    66  	BgWhite
    67  )
    68  
    69  // Background Hi-Intensity text colors
    70  const (
    71  	BgHiBlack ColorAttribute = iota + 100
    72  	BgHiRed
    73  	BgHiGreen
    74  	BgHiYellow
    75  	BgHiBlue
    76  	BgHiMagenta
    77  	BgHiCyan
    78  	BgHiWhite
    79  )
    80  
    81  var colorAttributeToString = map[ColorAttribute]string{
    82  	Reset:        "Reset",
    83  	Bold:         "Bold",
    84  	Faint:        "Faint",
    85  	Italic:       "Italic",
    86  	Underline:    "Underline",
    87  	BlinkSlow:    "BlinkSlow",
    88  	BlinkRapid:   "BlinkRapid",
    89  	ReverseVideo: "ReverseVideo",
    90  	Concealed:    "Concealed",
    91  	CrossedOut:   "CrossedOut",
    92  	FgBlack:      "FgBlack",
    93  	FgRed:        "FgRed",
    94  	FgGreen:      "FgGreen",
    95  	FgYellow:     "FgYellow",
    96  	FgBlue:       "FgBlue",
    97  	FgMagenta:    "FgMagenta",
    98  	FgCyan:       "FgCyan",
    99  	FgWhite:      "FgWhite",
   100  	FgHiBlack:    "FgHiBlack",
   101  	FgHiRed:      "FgHiRed",
   102  	FgHiGreen:    "FgHiGreen",
   103  	FgHiYellow:   "FgHiYellow",
   104  	FgHiBlue:     "FgHiBlue",
   105  	FgHiMagenta:  "FgHiMagenta",
   106  	FgHiCyan:     "FgHiCyan",
   107  	FgHiWhite:    "FgHiWhite",
   108  	BgBlack:      "BgBlack",
   109  	BgRed:        "BgRed",
   110  	BgGreen:      "BgGreen",
   111  	BgYellow:     "BgYellow",
   112  	BgBlue:       "BgBlue",
   113  	BgMagenta:    "BgMagenta",
   114  	BgCyan:       "BgCyan",
   115  	BgWhite:      "BgWhite",
   116  	BgHiBlack:    "BgHiBlack",
   117  	BgHiRed:      "BgHiRed",
   118  	BgHiGreen:    "BgHiGreen",
   119  	BgHiYellow:   "BgHiYellow",
   120  	BgHiBlue:     "BgHiBlue",
   121  	BgHiMagenta:  "BgHiMagenta",
   122  	BgHiCyan:     "BgHiCyan",
   123  	BgHiWhite:    "BgHiWhite",
   124  }
   125  
   126  func (c *ColorAttribute) String() string {
   127  	return colorAttributeToString[*c]
   128  }
   129  
   130  var colorAttributeFromString = map[string]ColorAttribute{}
   131  
   132  // ColorAttributeFromString will return a ColorAttribute given a string
   133  func ColorAttributeFromString(from string) ColorAttribute {
   134  	lowerFrom := strings.TrimSpace(strings.ToLower(from))
   135  	return colorAttributeFromString[lowerFrom]
   136  }
   137  
   138  // ColorString converts a list of ColorAttributes to a color string
   139  func ColorString(attrs ...ColorAttribute) string {
   140  	return string(ColorBytes(attrs...))
   141  }
   142  
   143  // ColorBytes converts a list of ColorAttributes to a byte array
   144  func ColorBytes(attrs ...ColorAttribute) []byte {
   145  	bytes := make([]byte, 0, 20)
   146  	bytes = append(bytes, escape[0], '[')
   147  	if len(attrs) > 0 {
   148  		bytes = append(bytes, strconv.Itoa(int(attrs[0]))...)
   149  		for _, a := range attrs[1:] {
   150  			bytes = append(bytes, ';')
   151  			bytes = append(bytes, strconv.Itoa(int(a))...)
   152  		}
   153  	} else {
   154  		bytes = append(bytes, strconv.Itoa(int(Bold))...)
   155  	}
   156  	bytes = append(bytes, 'm')
   157  	return bytes
   158  }
   159  
   160  var levelToColor = map[Level][]byte{
   161  	TRACE:    ColorBytes(Bold, FgCyan),
   162  	DEBUG:    ColorBytes(Bold, FgBlue),
   163  	INFO:     ColorBytes(Bold, FgGreen),
   164  	WARN:     ColorBytes(Bold, FgYellow),
   165  	ERROR:    ColorBytes(Bold, FgRed),
   166  	CRITICAL: ColorBytes(Bold, BgMagenta),
   167  	FATAL:    ColorBytes(Bold, BgRed),
   168  	NONE:     ColorBytes(Reset),
   169  }
   170  
   171  var (
   172  	resetBytes   = ColorBytes(Reset)
   173  	fgCyanBytes  = ColorBytes(FgCyan)
   174  	fgGreenBytes = ColorBytes(FgGreen)
   175  	fgBoldBytes  = ColorBytes(Bold)
   176  )
   177  
   178  type protectedANSIWriterMode int
   179  
   180  const (
   181  	escapeAll protectedANSIWriterMode = iota
   182  	allowColor
   183  	removeColor
   184  )
   185  
   186  type protectedANSIWriter struct {
   187  	w    io.Writer
   188  	mode protectedANSIWriterMode
   189  }
   190  
   191  // Write will protect against unusual characters
   192  func (c *protectedANSIWriter) Write(bytes []byte) (int, error) {
   193  	end := len(bytes)
   194  	totalWritten := 0
   195  normalLoop:
   196  	for i := 0; i < end; {
   197  		lasti := i
   198  
   199  		if c.mode == escapeAll {
   200  			for i < end && (bytes[i] >= ' ' || bytes[i] == '\n' || bytes[i] == '\t') {
   201  				i++
   202  			}
   203  		} else {
   204  			// Allow tabs if we're not escaping everything
   205  			for i < end && (bytes[i] >= ' ' || bytes[i] == '\t') {
   206  				i++
   207  			}
   208  		}
   209  
   210  		if i > lasti {
   211  			written, err := c.w.Write(bytes[lasti:i])
   212  			totalWritten += written
   213  			if err != nil {
   214  				return totalWritten, err
   215  			}
   216  
   217  		}
   218  		if i >= end {
   219  			break
   220  		}
   221  
   222  		// If we're not just escaping all we should prefix all newlines with a \t
   223  		if c.mode != escapeAll {
   224  			if bytes[i] == '\n' {
   225  				written, err := c.w.Write([]byte{'\n', '\t'})
   226  				if written > 0 {
   227  					totalWritten++
   228  				}
   229  				if err != nil {
   230  					return totalWritten, err
   231  				}
   232  				i++
   233  				continue normalLoop
   234  			}
   235  
   236  			if bytes[i] == escape[0] && i+1 < end && bytes[i+1] == '[' {
   237  				for j := i + 2; j < end; j++ {
   238  					if bytes[j] >= '0' && bytes[j] <= '9' {
   239  						continue
   240  					}
   241  					if bytes[j] == ';' {
   242  						continue
   243  					}
   244  					if bytes[j] == 'm' {
   245  						if c.mode == allowColor {
   246  							written, err := c.w.Write(bytes[i : j+1])
   247  							totalWritten += written
   248  							if err != nil {
   249  								return totalWritten, err
   250  							}
   251  						} else {
   252  							totalWritten = j
   253  						}
   254  						i = j + 1
   255  						continue normalLoop
   256  					}
   257  					break
   258  				}
   259  			}
   260  		}
   261  
   262  		// Process naughty character
   263  		if _, err := fmt.Fprintf(c.w, `\%#03o`, bytes[i]); err != nil {
   264  			return totalWritten, err
   265  		}
   266  		i++
   267  		totalWritten++
   268  	}
   269  	return totalWritten, nil
   270  }
   271  
   272  // ColorSprintf returns a colored string from a format and arguments
   273  // arguments will be wrapped in ColoredValues to protect against color spoofing
   274  func ColorSprintf(format string, args ...interface{}) string {
   275  	if len(args) > 0 {
   276  		v := make([]interface{}, len(args))
   277  		for i := 0; i < len(v); i++ {
   278  			v[i] = NewColoredValuePointer(&args[i])
   279  		}
   280  		return fmt.Sprintf(format, v...)
   281  	}
   282  	return format
   283  }
   284  
   285  // ColorFprintf will write to the provided writer similar to ColorSprintf
   286  func ColorFprintf(w io.Writer, format string, args ...interface{}) (int, error) {
   287  	if len(args) > 0 {
   288  		v := make([]interface{}, len(args))
   289  		for i := 0; i < len(v); i++ {
   290  			v[i] = NewColoredValuePointer(&args[i])
   291  		}
   292  		return fmt.Fprintf(w, format, v...)
   293  	}
   294  	return fmt.Fprint(w, format)
   295  }
   296  
   297  // ColorFormatted structs provide their own colored string when formatted with ColorSprintf
   298  type ColorFormatted interface {
   299  	// ColorFormat provides the colored representation of the value
   300  	ColorFormat(s fmt.State)
   301  }
   302  
   303  var colorFormattedType = reflect.TypeOf((*ColorFormatted)(nil)).Elem()
   304  
   305  // ColoredValue will Color the provided value
   306  type ColoredValue struct {
   307  	colorBytes *[]byte
   308  	resetBytes *[]byte
   309  	Value      *interface{}
   310  }
   311  
   312  // NewColoredValue is a helper function to create a ColoredValue from a Value
   313  // If no color is provided it defaults to Bold with standard Reset
   314  // If a ColoredValue is provided it is not changed
   315  func NewColoredValue(value interface{}, color ...ColorAttribute) *ColoredValue {
   316  	return NewColoredValuePointer(&value, color...)
   317  }
   318  
   319  // NewColoredValuePointer is a helper function to create a ColoredValue from a Value Pointer
   320  // If no color is provided it defaults to Bold with standard Reset
   321  // If a ColoredValue is provided it is not changed
   322  func NewColoredValuePointer(value *interface{}, color ...ColorAttribute) *ColoredValue {
   323  	if val, ok := (*value).(*ColoredValue); ok {
   324  		return val
   325  	}
   326  	if len(color) > 0 {
   327  		bytes := ColorBytes(color...)
   328  		return &ColoredValue{
   329  			colorBytes: &bytes,
   330  			resetBytes: &resetBytes,
   331  			Value:      value,
   332  		}
   333  	}
   334  	return &ColoredValue{
   335  		colorBytes: &fgBoldBytes,
   336  		resetBytes: &resetBytes,
   337  		Value:      value,
   338  	}
   339  }
   340  
   341  // NewColoredValueBytes creates a value from the provided value with color bytes
   342  // If a ColoredValue is provided it is not changed
   343  func NewColoredValueBytes(value interface{}, colorBytes *[]byte) *ColoredValue {
   344  	if val, ok := value.(*ColoredValue); ok {
   345  		return val
   346  	}
   347  	return &ColoredValue{
   348  		colorBytes: colorBytes,
   349  		resetBytes: &resetBytes,
   350  		Value:      &value,
   351  	}
   352  }
   353  
   354  // NewColoredIDValue is a helper function to create a ColoredValue from a Value
   355  // The Value will be colored with FgCyan
   356  // If a ColoredValue is provided it is not changed
   357  func NewColoredIDValue(value interface{}) *ColoredValue {
   358  	return NewColoredValueBytes(value, &fgCyanBytes)
   359  }
   360  
   361  // Format will format the provided value and protect against ANSI color spoofing within the value
   362  // If the wrapped value is ColorFormatted and the format is "%-v" then its ColorString will
   363  // be used. It is presumed that this ColorString is safe.
   364  func (cv *ColoredValue) Format(s fmt.State, c rune) {
   365  	if c == 'v' && s.Flag('-') {
   366  		if val, ok := (*cv.Value).(ColorFormatted); ok {
   367  			val.ColorFormat(s)
   368  			return
   369  		}
   370  		v := reflect.ValueOf(*cv.Value)
   371  		t := v.Type()
   372  
   373  		if reflect.PtrTo(t).Implements(colorFormattedType) {
   374  			vp := reflect.New(t)
   375  			vp.Elem().Set(v)
   376  			val := vp.Interface().(ColorFormatted)
   377  			val.ColorFormat(s)
   378  			return
   379  		}
   380  	}
   381  	s.Write(*cv.colorBytes)
   382  	fmt.Fprintf(&protectedANSIWriter{w: s}, fmtString(s, c), *(cv.Value))
   383  	s.Write(*cv.resetBytes)
   384  }
   385  
   386  // ColorFormatAsString returns the result of the ColorFormat without the color
   387  func ColorFormatAsString(colorVal ColorFormatted) string {
   388  	s := new(strings.Builder)
   389  	_, _ = ColorFprintf(&protectedANSIWriter{w: s, mode: removeColor}, "%-v", colorVal)
   390  	return s.String()
   391  }
   392  
   393  // SetColorBytes will allow a user to set the colorBytes of a colored value
   394  func (cv *ColoredValue) SetColorBytes(colorBytes []byte) {
   395  	cv.colorBytes = &colorBytes
   396  }
   397  
   398  // SetColorBytesPointer will allow a user to set the colorBytes pointer of a colored value
   399  func (cv *ColoredValue) SetColorBytesPointer(colorBytes *[]byte) {
   400  	cv.colorBytes = colorBytes
   401  }
   402  
   403  // SetResetBytes will allow a user to set the resetBytes pointer of a colored value
   404  func (cv *ColoredValue) SetResetBytes(resetBytes []byte) {
   405  	cv.resetBytes = &resetBytes
   406  }
   407  
   408  // SetResetBytesPointer will allow a user to set the resetBytes pointer of a colored value
   409  func (cv *ColoredValue) SetResetBytesPointer(resetBytes *[]byte) {
   410  	cv.resetBytes = resetBytes
   411  }
   412  
   413  func fmtString(s fmt.State, c rune) string {
   414  	var width, precision string
   415  	base := make([]byte, 0, 8)
   416  	base = append(base, '%')
   417  	for _, c := range []byte(" +-#0") {
   418  		if s.Flag(int(c)) {
   419  			base = append(base, c)
   420  		}
   421  	}
   422  	if w, ok := s.Width(); ok {
   423  		width = strconv.Itoa(w)
   424  	}
   425  	if p, ok := s.Precision(); ok {
   426  		precision = "." + strconv.Itoa(p)
   427  	}
   428  	return fmt.Sprintf("%s%s%s%c", base, width, precision, c)
   429  }
   430  
   431  func init() {
   432  	for attr, from := range colorAttributeToString {
   433  		colorAttributeFromString[strings.ToLower(from)] = attr
   434  	}
   435  }