github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/log/colors.go (about)

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