Skip to content

Commit 881c9a5

Browse files
authored
Eliminate 4-bit colors, add RGB conversion tools, HTML parse mode #13
# Description This eliminates support for 16-color (4-bit) colors, an unused feature that increases complexity. It also adds a new parsing mode to convert tags into HTML output (`<span>`) ## Changes * Renaming `color256:` in ansi-alias file to simply `colors` * `color256` remains supported. * Eliminated code related to 4-bit colors * Updated unit tests * Added `rgb.go` - provides `RGB(int)` which accepts a color code and returns an `rgb` struct/object * unit tests * Added `ansitags.HTML` as a parse mode for output. ### Notes Since an XML-like tag structure is used for <ansi> tags, you could really cheat this with string (or regex) replacements, but this maintains the ability to process a stream of data, such as that piped in from another program. Whether that's useful or not remains to be seen. ### Example Here is a side-by-side of a web-render of the html output vs. an in-terminal output using ansi escape codes: ![image](https://github.com/user-attachments/assets/d044d03c-c631-4064-b282-0f69f66817c8)
2 parents f6325a8 + 0570062 commit 881c9a5

18 files changed

+418
-343
lines changed

Makefile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ build:
2929
stream-test: build
3030
cat testdata/ansitags_test_streaming.yaml | ./example/bin/$(BIN)
3131

32-
.PHONY: stream-test-256
33-
stream-test-256: build
34-
cat testdata/ansitags_test_streaming-256.yaml | COLOR_MODE=256 ./example/bin/$(BIN)
35-
3632
.PHONY: profile
3733
profile:
3834
go test -cpuprofile cpu.prof -memprofile mem.prof -bench .
35+
36+

README.md

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
- [ansitags](#ansitags)
44
- [Overview](#overview)
55
- [Quick Start](#quick-start)
6-
- [Future plans (Short term)](#future-plans-short-term)
7-
- [Future plans (Long term)](#future-plans-long-term)
86

97
## Overview
108

@@ -30,17 +28,3 @@ Result:
3028

3129
![alt text](https://user-images.githubusercontent.com/143822/185706504-99d32ed5-37cc-4266-b682-c74b719e4790.png)
3230

33-
Note: You can switch between 256 color mode and 8 color mode (The default is 8):
34-
35-
ansitags.SetColorMode(ansitags.Color8)
36-
ansitags.SetColorMode(ansitags.Color256)
37-
38-
39-
## Future plans (Short term)
40-
41-
- CSI sequence support such as cursor position
42-
43-
## Future plans (Long term)
44-
45-
- Stripping out color codes / tags from strings
46-
- Generating color-styled HTML from color codes / tags

aliases.yaml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,10 @@
1111
# ,_-
1212
#
1313
# Aliases are organized into groups, only the following groups are valid:
14-
# color8 - 3/4 bit color pallette
15-
# color256 - 256 color palette
14+
# colors - 256 color palette
1615
# position - for cursor x,y position
1716
#
18-
color8:
19-
date: magenta
20-
username: cyan
21-
color256:
17+
colors:
2218
date: 207
2319
username: 195
2420
position:

ansiproperties.go

Lines changed: 60 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,28 @@ const (
1515
matchPosTag int = 1
1616
matchPosValue int = 2
1717

18-
// special values to modify 8 bit color codes
19-
fgToBgIncrement int = 10
20-
boldIncrement int = 60
21-
22-
defaultFg int = 39
23-
defaultBg int = 49
24-
2518
defaultFg256 int = -2
2619
defaultBg256 int = -2
2720

2821
posMax int = 16000
22+
23+
ansiResetAll = "\033[0m"
24+
htmlResetAll = "</span>"
2925
)
3026

3127
const (
32-
// 8 bit color mode
33-
Color8 ColorMode = iota
3428
// 256 bit color mode
35-
Color256
29+
Color8Bit ColorMode = iota
30+
Color24Bit
3631
)
3732

3833
var (
3934

4035
// regular expressions
4136
propertyRegex, _ = regexp.Compile(" (bg|fg|bold|position|clear)=[\"']?([a-z0-9,_-]+)[\"']?")
4237

43-
// map of strings to 4 bit color codes
44-
colorMap8 map[string]int = map[string]int{
45-
"black": 30,
46-
"red": 31,
47-
"green": 32,
48-
"yellow": 33,
49-
"blue": 34,
50-
"magenta": 35,
51-
"cyan": 36,
52-
"white": 37,
53-
}
54-
5538
// map of strings to 8 bit color codes
56-
colorMap256 map[string]int = map[string]int{
39+
colorAliases map[string]int = map[string]int{
5740
"black": 0,
5841
"red": 1,
5942
"green": 2,
@@ -89,53 +72,62 @@ var (
8972
"scrollback": 3,
9073
}
9174

92-
colorMode ColorMode = Color8
75+
ansiFgSeq [256]string
76+
ansiBgSeq [256]string
9377

9478
rwLock = sync.RWMutex{}
9579
)
9680

9781
type ansiProperties struct {
9882
fg int
9983
bg int
100-
bold bool
10184
clear int
10285
position []uint16
86+
htmlOnly bool
10387
}
10488

10589
func (p *ansiProperties) AnsiReset() string {
106-
return "\033[39;49m"
90+
return ansiResetAll
10791
}
10892

10993
func (p ansiProperties) PropagateAnsiCode(previous *ansiProperties) string {
11094

11195
if previous != nil {
112-
if colorMode == Color8 {
113-
if p.fg == defaultFg {
114-
p.fg = previous.fg
115-
}
116-
if p.bg == defaultBg {
117-
p.bg = previous.bg
118-
}
119-
if !p.bold {
120-
p.bold = previous.bold
121-
}
122-
} else {
123-
if p.fg == defaultFg256 {
124-
p.fg = previous.fg
125-
}
126-
if p.bg == defaultBg256 {
127-
p.bg = previous.bg
128-
}
96+
97+
if p.fg == defaultFg256 {
98+
p.fg = previous.fg
99+
}
100+
if p.bg == defaultBg256 {
101+
p.bg = previous.bg
129102
}
130103
}
131104

132-
if p.bold && colorMode == Color8 {
133-
if p.fg < 90 && p.fg != defaultFg {
134-
p.fg += boldIncrement
105+
if p.htmlOnly {
106+
107+
if previous != nil {
108+
109+
if p.fg == previous.fg && p.bg == previous.bg {
110+
return `<span>`
111+
}
112+
}
113+
114+
if p.fg == defaultFg256 && p.bg == defaultBg256 {
115+
return `<span>`
116+
}
117+
118+
htmlStr := `<span style="`
119+
120+
if p.fg > -1 {
121+
clr := RGB(p.fg)
122+
htmlStr += `color:#` + clr.Hex + `;`
135123
}
136-
if p.bg < 90 && p.fg != defaultBg {
137-
p.bg += boldIncrement
124+
125+
if p.bg > -1 {
126+
clr := RGB(p.bg)
127+
htmlStr += `background-color:#` + clr.Hex + `;`
138128
}
129+
130+
return htmlStr + `">`
139131
}
140132

141133
var clearCode string = ""
@@ -149,29 +141,18 @@ func (p ansiProperties) PropagateAnsiCode(previous *ansiProperties) string {
149141
}
150142

151143
var colorCode string = ""
152-
if colorMode == Color8 {
153-
if p.fg > -1 || p.bg > -1 {
154-
colorCode = "\033["
155-
if p.fg > -1 {
156-
colorCode += strconv.Itoa(p.fg)
157-
if p.bg > -1 {
158-
colorCode += ";" + strconv.Itoa(p.bg)
159-
}
160-
colorCode += "m"
161-
} else {
162-
colorCode += strconv.Itoa(p.bg) + "m"
163-
}
164-
}
165-
} else {
166144

145+
if p.fg == defaultFg256 && p.bg == defaultBg256 {
146+
colorCode = "\033[0m"
147+
} else {
167148
if p.fg > -1 {
168-
colorCode += "\033[38;5;" + strconv.Itoa(p.fg) + `m`
149+
colorCode += ansiFgSeq[p.fg]
169150
} else if p.fg == defaultFg256 {
170151
colorCode += "\033[39m"
171152
}
172153

173154
if p.bg > -1 {
174-
colorCode += "\033[48;5;" + strconv.Itoa(p.bg) + `m`
155+
colorCode += ansiBgSeq[p.bg]
175156
} else if p.bg == defaultBg256 {
176157
colorCode += "\033[49m"
177158
}
@@ -181,77 +162,37 @@ func (p ansiProperties) PropagateAnsiCode(previous *ansiProperties) string {
181162
}
182163

183164
func SetColorMode(mode ColorMode) {
184-
colorMode = mode
185-
}
186-
187-
func AnsiResetAll() string {
188-
return "\033[0m"
165+
// This is a NOOP now, left for backwards compatibility
189166
}
190167

191168
func extractProperties(tagStr string) *ansiProperties {
192169

193-
var ret *ansiProperties
194-
195-
if colorMode == Color8 {
196-
ret = &ansiProperties{fg: defaultFg, bg: defaultBg, clear: -1}
197-
} else {
198-
ret = &ansiProperties{fg: defaultFg256, bg: defaultBg256, clear: -1}
199-
}
170+
var ret = &ansiProperties{fg: defaultFg256, bg: defaultBg256, clear: -1}
200171

201172
result := propertyRegex.FindAllStringSubmatch(tagStr, -1)
202173
var err error
203174
var colorVal int
204-
var aliasFound bool
175+
var ok bool
205176
for _, match := range result {
206177

207178
switch match[matchPosTag] {
208179
case "fg":
209180
if ret.fg, err = strconv.Atoi(match[matchPosValue]); err != nil {
210181

211-
if colorMode == Color8 {
212-
colorVal, aliasFound = colorMap8[match[matchPosValue]]
213-
} else {
214-
colorVal, aliasFound = colorMap256[match[matchPosValue]]
215-
}
216-
217-
if aliasFound {
182+
if colorVal, ok = colorAliases[match[matchPosValue]]; ok {
218183
ret.fg = colorVal
219184
} else {
220-
if colorMode == Color8 {
221-
ret.fg = defaultFg
222-
} else {
223-
ret.fg = defaultFg256
224-
}
185+
ret.fg = defaultFg256
225186
}
226-
227187
}
228188
case "bg":
229189
if ret.bg, err = strconv.Atoi(match[matchPosValue]); err != nil {
230190

231-
if colorMode == Color8 {
232-
colorVal, aliasFound = colorMap8[match[matchPosValue]]
233-
colorVal += 10
234-
} else {
235-
colorVal, aliasFound = colorMap256[match[matchPosValue]]
236-
}
237-
238-
if aliasFound {
191+
if colorVal, ok = colorAliases[match[matchPosValue]]; ok {
239192
ret.bg = colorVal
240193
} else {
241-
if colorMode == Color8 {
242-
ret.bg = defaultBg
243-
} else {
244-
ret.bg = defaultBg256
245-
}
194+
ret.bg = defaultBg256
246195
}
247-
248-
}
249-
case "bold":
250-
if colorMode != Color8 {
251-
continue
252-
}
253-
if ret.bold, err = strconv.ParseBool(match[matchPosValue]); err != nil {
254-
ret.bold = false
255196
}
256197
case "position":
257198

@@ -282,9 +223,16 @@ func extractProperties(tagStr string) *ansiProperties {
282223
ret.clear = val
283224
}
284225
}
285-
//fmt.Printf("%#v = %#v\n", val[matchPosTag], val[matchPosValue])
286226

287227
}
288228

289229
return ret
290230
}
231+
232+
// Speed up by pre-computing these values
233+
func init() {
234+
for i := 0; i < 256; i++ {
235+
ansiFgSeq[i] = "\033[38;5;" + strconv.Itoa(i) + "m"
236+
ansiBgSeq[i] = "\033[48;5;" + strconv.Itoa(i) + "m"
237+
}
238+
}

0 commit comments

Comments
 (0)