github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/formatter/formatter.go (about) 1 package formatter 2 3 import ( 4 "fmt" 5 "os" 6 "regexp" 7 "strings" 8 ) 9 10 // ColorableStdOut and ColorableStdErr enable color output support on Windows 11 var ColorableStdOut = newColorable(os.Stdout) 12 var ColorableStdErr = newColorable(os.Stderr) 13 14 const COLS = 80 15 16 type ColorMode uint8 17 18 const ( 19 ColorModeNone ColorMode = iota 20 ColorModeTerminal 21 ColorModePassthrough 22 ) 23 24 var SingletonFormatter = New(ColorModeTerminal) 25 26 func F(format string, args ...interface{}) string { 27 return SingletonFormatter.F(format, args...) 28 } 29 30 func Fi(indentation uint, format string, args ...interface{}) string { 31 return SingletonFormatter.Fi(indentation, format, args...) 32 } 33 34 func Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string { 35 return SingletonFormatter.Fiw(indentation, maxWidth, format, args...) 36 } 37 38 type Formatter struct { 39 ColorMode ColorMode 40 colors map[string]string 41 styleRe *regexp.Regexp 42 preserveColorStylingTags bool 43 } 44 45 func NewWithNoColorBool(noColor bool) Formatter { 46 if noColor { 47 return New(ColorModeNone) 48 } 49 return New(ColorModeTerminal) 50 } 51 52 func New(colorMode ColorMode) Formatter { 53 f := Formatter{ 54 ColorMode: colorMode, 55 colors: map[string]string{ 56 "/": "\x1b[0m", 57 "bold": "\x1b[1m", 58 "underline": "\x1b[4m", 59 60 "red": "\x1b[38;5;9m", 61 "orange": "\x1b[38;5;214m", 62 "coral": "\x1b[38;5;204m", 63 "magenta": "\x1b[38;5;13m", 64 "green": "\x1b[38;5;10m", 65 "dark-green": "\x1b[38;5;28m", 66 "yellow": "\x1b[38;5;11m", 67 "light-yellow": "\x1b[38;5;228m", 68 "cyan": "\x1b[38;5;14m", 69 "gray": "\x1b[38;5;243m", 70 "light-gray": "\x1b[38;5;246m", 71 "blue": "\x1b[38;5;12m", 72 }, 73 } 74 colors := []string{} 75 for color := range f.colors { 76 colors = append(colors, color) 77 } 78 f.styleRe = regexp.MustCompile("{{(" + strings.Join(colors, "|") + ")}}") 79 return f 80 } 81 82 func (f Formatter) F(format string, args ...interface{}) string { 83 return f.Fi(0, format, args...) 84 } 85 86 func (f Formatter) Fi(indentation uint, format string, args ...interface{}) string { 87 return f.Fiw(indentation, 0, format, args...) 88 } 89 90 func (f Formatter) Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string { 91 out := fmt.Sprintf(f.style(format), args...) 92 93 if indentation == 0 && maxWidth == 0 { 94 return out 95 } 96 97 lines := strings.Split(out, "\n") 98 99 if maxWidth != 0 { 100 outLines := []string{} 101 102 maxWidth = maxWidth - indentation*2 103 for _, line := range lines { 104 if f.length(line) <= maxWidth { 105 outLines = append(outLines, line) 106 continue 107 } 108 words := strings.Split(line, " ") 109 outWords := []string{words[0]} 110 length := uint(f.length(words[0])) 111 for _, word := range words[1:] { 112 wordLength := f.length(word) 113 if length+wordLength+1 <= maxWidth { 114 length += wordLength + 1 115 outWords = append(outWords, word) 116 continue 117 } 118 outLines = append(outLines, strings.Join(outWords, " ")) 119 outWords = []string{word} 120 length = wordLength 121 } 122 if len(outWords) > 0 { 123 outLines = append(outLines, strings.Join(outWords, " ")) 124 } 125 } 126 127 lines = outLines 128 } 129 130 if indentation == 0 { 131 return strings.Join(lines, "\n") 132 } 133 134 padding := strings.Repeat(" ", int(indentation)) 135 for i := range lines { 136 if lines[i] != "" { 137 lines[i] = padding + lines[i] 138 } 139 } 140 141 return strings.Join(lines, "\n") 142 } 143 144 func (f Formatter) length(styled string) uint { 145 n := uint(0) 146 inStyle := false 147 for _, b := range styled { 148 if inStyle { 149 if b == 'm' { 150 inStyle = false 151 } 152 continue 153 } 154 if b == '\x1b' { 155 inStyle = true 156 continue 157 } 158 n += 1 159 } 160 return n 161 } 162 163 func (f Formatter) CycleJoin(elements []string, joiner string, cycle []string) string { 164 if len(elements) == 0 { 165 return "" 166 } 167 n := len(cycle) 168 out := "" 169 for i, text := range elements { 170 out += cycle[i%n] + text 171 if i < len(elements)-1 { 172 out += joiner 173 } 174 } 175 out += "{{/}}" 176 return f.style(out) 177 } 178 179 func (f Formatter) style(s string) string { 180 switch f.ColorMode { 181 case ColorModeNone: 182 return f.styleRe.ReplaceAllString(s, "") 183 case ColorModePassthrough: 184 return s 185 case ColorModeTerminal: 186 return f.styleRe.ReplaceAllStringFunc(s, func(match string) string { 187 if out, ok := f.colors[strings.Trim(match, "{}")]; ok { 188 return out 189 } 190 return match 191 }) 192 } 193 194 return "" 195 }