191 lines
4.0 KiB
Go
191 lines
4.0 KiB
Go
package formatter
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const COLS = 80
|
|
|
|
type ColorMode uint8
|
|
|
|
const (
|
|
ColorModeNone ColorMode = iota
|
|
ColorModeTerminal
|
|
ColorModePassthrough
|
|
)
|
|
|
|
var SingletonFormatter = New(ColorModeTerminal)
|
|
|
|
func F(format string, args ...interface{}) string {
|
|
return SingletonFormatter.F(format, args...)
|
|
}
|
|
|
|
func Fi(indentation uint, format string, args ...interface{}) string {
|
|
return SingletonFormatter.Fi(indentation, format, args...)
|
|
}
|
|
|
|
func Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string {
|
|
return SingletonFormatter.Fiw(indentation, maxWidth, format, args...)
|
|
}
|
|
|
|
type Formatter struct {
|
|
ColorMode ColorMode
|
|
colors map[string]string
|
|
styleRe *regexp.Regexp
|
|
preserveColorStylingTags bool
|
|
}
|
|
|
|
func NewWithNoColorBool(noColor bool) Formatter {
|
|
if noColor {
|
|
return New(ColorModeNone)
|
|
}
|
|
return New(ColorModeTerminal)
|
|
}
|
|
|
|
func New(colorMode ColorMode) Formatter {
|
|
f := Formatter{
|
|
ColorMode: colorMode,
|
|
colors: map[string]string{
|
|
"/": "\x1b[0m",
|
|
"bold": "\x1b[1m",
|
|
"underline": "\x1b[4m",
|
|
|
|
"red": "\x1b[38;5;9m",
|
|
"orange": "\x1b[38;5;214m",
|
|
"coral": "\x1b[38;5;204m",
|
|
"magenta": "\x1b[38;5;13m",
|
|
"green": "\x1b[38;5;10m",
|
|
"dark-green": "\x1b[38;5;28m",
|
|
"yellow": "\x1b[38;5;11m",
|
|
"light-yellow": "\x1b[38;5;228m",
|
|
"cyan": "\x1b[38;5;14m",
|
|
"gray": "\x1b[38;5;243m",
|
|
"light-gray": "\x1b[38;5;246m",
|
|
"blue": "\x1b[38;5;12m",
|
|
},
|
|
}
|
|
colors := []string{}
|
|
for color := range f.colors {
|
|
colors = append(colors, color)
|
|
}
|
|
f.styleRe = regexp.MustCompile("{{(" + strings.Join(colors, "|") + ")}}")
|
|
return f
|
|
}
|
|
|
|
func (f Formatter) F(format string, args ...interface{}) string {
|
|
return f.Fi(0, format, args...)
|
|
}
|
|
|
|
func (f Formatter) Fi(indentation uint, format string, args ...interface{}) string {
|
|
return f.Fiw(indentation, 0, format, args...)
|
|
}
|
|
|
|
func (f Formatter) Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string {
|
|
out := fmt.Sprintf(f.style(format), args...)
|
|
|
|
if indentation == 0 && maxWidth == 0 {
|
|
return out
|
|
}
|
|
|
|
lines := strings.Split(out, "\n")
|
|
|
|
if maxWidth != 0 {
|
|
outLines := []string{}
|
|
|
|
maxWidth = maxWidth - indentation*2
|
|
for _, line := range lines {
|
|
if f.length(line) <= maxWidth {
|
|
outLines = append(outLines, line)
|
|
continue
|
|
}
|
|
outWords := []string{}
|
|
length := uint(0)
|
|
words := strings.Split(line, " ")
|
|
for _, word := range words {
|
|
wordLength := f.length(word)
|
|
if length+wordLength <= maxWidth {
|
|
length += wordLength
|
|
outWords = append(outWords, word)
|
|
continue
|
|
}
|
|
outLines = append(outLines, strings.Join(outWords, " "))
|
|
outWords = []string{word}
|
|
length = wordLength
|
|
}
|
|
if len(outWords) > 0 {
|
|
outLines = append(outLines, strings.Join(outWords, " "))
|
|
}
|
|
}
|
|
|
|
lines = outLines
|
|
}
|
|
|
|
if indentation == 0 {
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
padding := strings.Repeat(" ", int(indentation))
|
|
for i := range lines {
|
|
if lines[i] != "" {
|
|
lines[i] = padding + lines[i]
|
|
}
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
func (f Formatter) length(styled string) uint {
|
|
n := uint(0)
|
|
inStyle := false
|
|
for _, b := range styled {
|
|
if inStyle {
|
|
if b == 'm' {
|
|
inStyle = false
|
|
}
|
|
continue
|
|
}
|
|
if b == '\x1b' {
|
|
inStyle = true
|
|
continue
|
|
}
|
|
n += 1
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (f Formatter) CycleJoin(elements []string, joiner string, cycle []string) string {
|
|
if len(elements) == 0 {
|
|
return ""
|
|
}
|
|
n := len(cycle)
|
|
out := ""
|
|
for i, text := range elements {
|
|
out += cycle[i%n] + text
|
|
if i < len(elements)-1 {
|
|
out += joiner
|
|
}
|
|
}
|
|
out += "{{/}}"
|
|
return f.style(out)
|
|
}
|
|
|
|
func (f Formatter) style(s string) string {
|
|
switch f.ColorMode {
|
|
case ColorModeNone:
|
|
return f.styleRe.ReplaceAllString(s, "")
|
|
case ColorModePassthrough:
|
|
return s
|
|
case ColorModeTerminal:
|
|
return f.styleRe.ReplaceAllStringFunc(s, func(match string) string {
|
|
if out, ok := f.colors[strings.Trim(match, "{}")]; ok {
|
|
return out
|
|
}
|
|
return match
|
|
})
|
|
}
|
|
|
|
return ""
|
|
}
|