|
@@ -4,6 +4,7 @@
|
4
|
4
|
package ircfmt
|
5
|
5
|
|
6
|
6
|
import (
|
|
7
|
+ "regexp"
|
7
|
8
|
"strings"
|
8
|
9
|
)
|
9
|
10
|
|
|
@@ -81,26 +82,6 @@ var (
|
81
|
82
|
"0": "white",
|
82
|
83
|
}
|
83
|
84
|
|
84
|
|
- // full and truncated colour codes
|
85
|
|
- colourcodesFull = map[string]string{
|
86
|
|
- "white": "00",
|
87
|
|
- "black": "01",
|
88
|
|
- "blue": "02",
|
89
|
|
- "green": "03",
|
90
|
|
- "red": "04",
|
91
|
|
- "brown": "05",
|
92
|
|
- "magenta": "06",
|
93
|
|
- "orange": "07",
|
94
|
|
- "yellow": "08",
|
95
|
|
- "light green": "09",
|
96
|
|
- "cyan": "10",
|
97
|
|
- "light cyan": "11",
|
98
|
|
- "light blue": "12",
|
99
|
|
- "pink": "13",
|
100
|
|
- "grey": "14",
|
101
|
|
- "light grey": "15",
|
102
|
|
- "default": "99",
|
103
|
|
- }
|
104
|
85
|
colourcodesTruncated = map[string]string{
|
105
|
86
|
"white": "0",
|
106
|
87
|
"black": "1",
|
|
@@ -120,6 +101,9 @@ var (
|
120
|
101
|
"light grey": "15",
|
121
|
102
|
"default": "99",
|
122
|
103
|
}
|
|
104
|
+
|
|
105
|
+ bracketedExpr = regexp.MustCompile(`^\[.*\]`)
|
|
106
|
+ colourDigits = regexp.MustCompile(`^[0-9]{1,2}$`)
|
123
|
107
|
)
|
124
|
108
|
|
125
|
109
|
// Escape takes a raw IRC string and returns it with our escapes.
|
|
@@ -242,87 +226,90 @@ func removeColour(runes []rune) []rune {
|
242
|
226
|
return runes
|
243
|
227
|
}
|
244
|
228
|
|
|
229
|
+// resolve "light blue" to "12", "12" to "12", "asdf" to "", etc.
|
|
230
|
+func resolveToColourCode(str string) (result string) {
|
|
231
|
+ str = strings.ToLower(strings.TrimSpace(str))
|
|
232
|
+ if colourDigits.MatchString(str) {
|
|
233
|
+ return str
|
|
234
|
+ }
|
|
235
|
+ return colourcodesTruncated[str]
|
|
236
|
+}
|
|
237
|
+
|
|
238
|
+// resolve "[light blue, black]" to ("13, "1")
|
|
239
|
+func resolveToColourCodes(namedColors string) (foreground, background string) {
|
|
240
|
+ // cut off the brackets
|
|
241
|
+ namedColors = strings.TrimPrefix(namedColors, "[")
|
|
242
|
+ namedColors = strings.TrimSuffix(namedColors, "]")
|
|
243
|
+
|
|
244
|
+ var foregroundStr, backgroundStr string
|
|
245
|
+ commaIdx := strings.IndexByte(namedColors, ',')
|
|
246
|
+ if commaIdx != -1 {
|
|
247
|
+ foregroundStr = namedColors[:commaIdx]
|
|
248
|
+ backgroundStr = namedColors[commaIdx+1:]
|
|
249
|
+ } else {
|
|
250
|
+ foregroundStr = namedColors
|
|
251
|
+ }
|
|
252
|
+
|
|
253
|
+ return resolveToColourCode(foregroundStr), resolveToColourCode(backgroundStr)
|
|
254
|
+}
|
|
255
|
+
|
245
|
256
|
// Unescape takes our escaped string and returns a raw IRC string.
|
246
|
257
|
//
|
247
|
258
|
// IE, it turns this: "This is a $bcool$b, $c[red]red$r message!"
|
248
|
259
|
// into this: "This is a \x02cool\x02, \x034red\x0f message!"
|
249
|
260
|
func Unescape(in string) string {
|
250
|
|
- out := strings.Builder{}
|
|
261
|
+ var out strings.Builder
|
251
|
262
|
|
252
|
|
- remaining := []rune(in)
|
253
|
|
- for 0 < len(remaining) {
|
|
263
|
+ remaining := in
|
|
264
|
+ for len(remaining) != 0 {
|
254
|
265
|
char := remaining[0]
|
255
|
266
|
remaining = remaining[1:]
|
256
|
267
|
|
257
|
|
- if char == '$' && 0 < len(remaining) {
|
258
|
|
- char = remaining[0]
|
259
|
|
- remaining = remaining[1:]
|
260
|
|
-
|
261
|
|
- val, exists := escapetoval[char]
|
262
|
|
- if exists {
|
263
|
|
- out.WriteString(val)
|
264
|
|
- } else if char == 'c' {
|
265
|
|
- out.WriteString(colour)
|
266
|
|
-
|
267
|
|
- if len(remaining) < 2 || remaining[0] != '[' {
|
268
|
|
- continue
|
269
|
|
- }
|
270
|
|
-
|
271
|
|
- // get colour names
|
272
|
|
- var coloursBuffer string
|
273
|
|
- remaining = remaining[1:]
|
274
|
|
- for remaining[0] != ']' {
|
275
|
|
- coloursBuffer += string(remaining[0])
|
276
|
|
- remaining = remaining[1:]
|
277
|
|
- }
|
278
|
|
- remaining = remaining[1:] // strip final ']'
|
279
|
|
-
|
280
|
|
- colours := strings.Split(coloursBuffer, ",")
|
281
|
|
- var foreColour, backColour string
|
282
|
|
- foreColour = colours[0]
|
283
|
|
- if 1 < len(colours) {
|
284
|
|
- backColour = colours[1]
|
285
|
|
- }
|
|
268
|
+ if char != '$' || len(remaining) == 0 {
|
|
269
|
+ // not an escape
|
|
270
|
+ out.WriteByte(char)
|
|
271
|
+ continue
|
|
272
|
+ }
|
286
|
273
|
|
287
|
|
- // decide whether we can use truncated colour codes
|
288
|
|
- canUseTruncated := len(remaining) < 1 || !strings.Contains(colours1, string(remaining[0]))
|
|
274
|
+ // ingest the next character of the escape
|
|
275
|
+ char = remaining[0]
|
|
276
|
+ remaining = remaining[1:]
|
289
|
277
|
|
290
|
|
- // turn colour names into real codes
|
291
|
|
- var foreColourCode, backColourCode string
|
292
|
|
- var exists bool
|
|
278
|
+ if char == 'c' {
|
|
279
|
+ out.WriteString(colour)
|
293
|
280
|
|
294
|
|
- if backColour != "" || canUseTruncated {
|
295
|
|
- foreColourCode, exists = colourcodesTruncated[foreColour]
|
296
|
|
- } else {
|
297
|
|
- foreColourCode, exists = colourcodesFull[foreColour]
|
298
|
|
- }
|
299
|
|
- if exists {
|
300
|
|
- foreColour = foreColourCode
|
|
281
|
+ namedColors := bracketedExpr.FindString(remaining)
|
|
282
|
+ if namedColors == "" {
|
|
283
|
+ // for a non-bracketed color code, output the following characters directly,
|
|
284
|
+ // e.g., `$c1,8` will become `\x031,8`
|
|
285
|
+ continue
|
|
286
|
+ }
|
|
287
|
+ // process bracketed color codes:
|
|
288
|
+ remaining = remaining[len(namedColors):]
|
|
289
|
+ followedByDigit := len(remaining) != 0 && ('0' <= remaining[0] && remaining[0] <= '9')
|
|
290
|
+
|
|
291
|
+ foreground, background := resolveToColourCodes(namedColors)
|
|
292
|
+ if foreground != "" {
|
|
293
|
+ if len(foreground) == 1 && background == "" && followedByDigit {
|
|
294
|
+ out.WriteByte('0')
|
301
|
295
|
}
|
302
|
|
-
|
303
|
|
- if backColour != "" {
|
304
|
|
- if canUseTruncated {
|
305
|
|
- backColourCode, exists = colourcodesTruncated[backColour]
|
306
|
|
- } else {
|
307
|
|
- backColourCode, exists = colourcodesFull[backColour]
|
308
|
|
- }
|
309
|
|
- if exists {
|
310
|
|
- backColour = backColourCode
|
|
296
|
+ out.WriteString(foreground)
|
|
297
|
+ if background != "" {
|
|
298
|
+ out.WriteByte(',')
|
|
299
|
+ if len(background) == 1 && followedByDigit {
|
|
300
|
+ out.WriteByte('0')
|
311
|
301
|
}
|
|
302
|
+ out.WriteString(background)
|
312
|
303
|
}
|
313
|
|
-
|
314
|
|
- // output colour codes
|
315
|
|
- out.WriteString(foreColour)
|
316
|
|
- if backColour != "" {
|
317
|
|
- out.WriteRune(',')
|
318
|
|
- out.WriteString(backColour)
|
319
|
|
- }
|
320
|
|
- } else {
|
321
|
|
- // unknown char
|
322
|
|
- out.WriteRune(char)
|
323
|
304
|
}
|
324
|
305
|
} else {
|
325
|
|
- out.WriteRune(char)
|
|
306
|
+ val, exists := escapetoval[rune(char)]
|
|
307
|
+ if exists {
|
|
308
|
+ out.WriteString(val)
|
|
309
|
+ } else {
|
|
310
|
+ // invalid escape, use the raw char
|
|
311
|
+ out.WriteByte(char)
|
|
312
|
+ }
|
326
|
313
|
}
|
327
|
314
|
}
|
328
|
315
|
|