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 }