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