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