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 }