-
-
Notifications
You must be signed in to change notification settings - Fork 94
/
colour.go
96 lines (87 loc) · 2.77 KB
/
colour.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package text
import (
"fmt"
"golang.org/x/net/html"
"regexp"
"strings"
)
// cleaner represents the regex used to clean Minecraft formatting codes from a string.
var cleaner = regexp.MustCompile("§[0-9a-u]")
// Clean removes all Minecraft formatting codes from the string passed.
func Clean(s string) string {
return cleaner.ReplaceAllString(s, "")
}
// ANSI converts all Minecraft text formatting codes in the values passed to ANSI formatting codes, so that
// it may be displayed properly in the terminal.
func ANSI(a ...any) string {
str := make([]string, len(a))
for i, v := range a {
str[i] = minecraftReplacer.Replace(fmt.Sprint(v))
}
return strings.Join(str, " ")
}
// Colourf colours the format string using HTML tags after first escaping all parameters passed and
// substituting them in the format string. The following colours and formatting may be used:
//
// black, dark-blue, dark-green, dark-aqua, dark-red, dark-purple, gold, grey, dark-grey, blue, green, aqua,
// red, purple, yellow, white, dark-yellow, quartz, iron, netherite, redstone, copper, gold, emerald, diamond,
// lapis, amethyst, obfuscated, bold (b), and italic (i).
//
// These HTML tags may also be nested, like so:
// `<red>Hello <bold>World</bold>!</red>`
func Colourf(format string, a ...any) string {
str := fmt.Sprintf(format, a...)
e := &enc{w: &strings.Builder{}, first: true}
t := html.NewTokenizer(strings.NewReader(str))
for {
if t.Next() == html.ErrorToken {
break
}
e.process(t.Token())
}
return e.w.String()
}
// enc holds the state of a string to be processed for colour substitution.
type enc struct {
w *strings.Builder
formatStack []string
first bool
}
// process handles a single html.Token and either writes the string to the strings.Builder, adds a colour to
// the stack or removes a colour from the stack.
func (e *enc) process(tok html.Token) {
if e.first {
e.w.WriteString(Reset)
e.first = false
}
switch tok.Type {
case html.TextToken:
e.writeText(tok.Data)
case html.StartTagToken:
if format, ok := strMap[tok.Data]; ok {
e.formatStack = append(e.formatStack, format)
return
}
// Not a known colour, so just write the token as a string.
e.writeText("<" + tok.Data + ">")
case html.EndTagToken:
for i, format := range e.formatStack {
if f, ok := strMap[tok.Data]; ok && f == format {
e.formatStack = append(e.formatStack[:i], e.formatStack[i+1:]...)
return
}
}
// Not a known colour, so just write the token as a string.
e.writeText("</" + tok.Data + ">")
}
}
// writeText writes text to the encoder by encasing it in the current format stack.
func (e *enc) writeText(s string) {
for _, format := range e.formatStack {
e.w.WriteString(format)
}
e.w.WriteString(s)
if len(e.formatStack) != 0 {
e.w.WriteString(Reset)
}
}