Skip to content

Commit f6325a8

Browse files
authored
Alias set functions, bug fixes, and more
# Changes * Added a RWMutex for when updating color aliases or parsing strings * Fixed [bug](#10) and included tests for it. * New functions `SetAlias()` and `SetAliases()` to set/override specific aliases * `LoadAliases()` now has a variadic argument, allowing multiple files to be loaded in sequence.
2 parents c8b646c + 3c91b4b commit f6325a8

File tree

4 files changed

+225
-41
lines changed

4 files changed

+225
-41
lines changed

ansiproperties.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"regexp"
55
"strconv"
66
"strings"
7+
"sync"
78
)
89

910
type ColorMode uint8
@@ -89,6 +90,8 @@ var (
8990
}
9091

9192
colorMode ColorMode = Color8
93+
94+
rwLock = sync.RWMutex{}
9295
)
9396

9497
type ansiProperties struct {

ansitags.go

Lines changed: 109 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ansitags
33
import (
44
"bufio"
55
"bytes"
6+
"fmt"
67
"io"
78
"os"
89
"strconv"
@@ -15,18 +16,19 @@ type parseMode uint8
1516
type ParseBehavior uint8
1617

1718
const (
18-
tagStart byte = '<'
19-
tagEnd byte = '>'
20-
21-
tagOpen string = "ansi" // will be wrapped in tagStart and tagEnd
22-
tagClose string = "/ansi" // will be wrapped in tagStart and tagEnd
23-
2419
parseModeNone parseMode = 0
2520
parseModeMatching parseMode = 1
2621

2722
StripTags ParseBehavior = iota // remove all valid ansitags
2823
Monochrome // ignore any color changing properties
24+
)
2925

26+
var (
27+
tagStart byte = '<'
28+
tagEnd byte = '>'
29+
30+
tagOpen string = "ansi" // will be wrapped in tagStart and tagEnd
31+
tagClose string = "/ansi" // will be wrapped in tagStart and tagEnd
3032
)
3133

3234
func Parse(str string, behaviors ...ParseBehavior) string {
@@ -42,6 +44,9 @@ func Parse(str string, behaviors ...ParseBehavior) string {
4244

4345
func ParseStreaming(inbound *bufio.Reader, outbound *bufio.Writer, behaviors ...ParseBehavior) {
4446

47+
rwLock.RLock()
48+
defer rwLock.RUnlock()
49+
4550
var stripAllTags bool = false
4651
var stripAllColor bool = false
4752

@@ -64,13 +69,13 @@ func ParseStreaming(inbound *bufio.Reader, outbound *bufio.Writer, behaviors ...
6469

6570
for {
6671
input, err := inbound.ReadByte()
72+
6773
if err != nil && err == io.EOF {
6874
break
6975
}
7076

7177
// If not currently in any modes, look for any tags
7278
if mode == parseModeNone {
73-
7479
if input != tagStart {
7580
// If it's not an opening tag and we're looking for it (zero position)
7681
// Write it to the output string and go to next
@@ -89,6 +94,7 @@ func ParseStreaming(inbound *bufio.Reader, outbound *bufio.Writer, behaviors ...
8994
closeMatch, closeMatchDone := closeMatcher.MatchNext(input)
9095

9196
if openMatch {
97+
9298
currentTagBuilder.WriteByte(input)
9399

94100
if !openMatchDone {
@@ -161,6 +167,10 @@ func ParseStreaming(inbound *bufio.Reader, outbound *bufio.Writer, behaviors ...
161167
// No close match was found. Reset the matcher
162168
closeMatcher.Reset()
163169

170+
if closeMatchDone && openMatchDone {
171+
currentTagBuilder.WriteByte(input)
172+
}
173+
164174
// open and close both failed to match. Reset everything
165175
mode = parseModeNone
166176

@@ -188,57 +198,115 @@ func ParseStreaming(inbound *bufio.Reader, outbound *bufio.Writer, behaviors ...
188198
outbound.Flush()
189199
}
190200

191-
func LoadAliases(yamlFilePath string) error {
201+
func SetAlias(alias string, value int, aliasGroup ...string) error {
192202

193-
data := make(map[string]map[string]string, 100)
203+
rwLock.Lock()
204+
defer rwLock.Unlock()
205+
206+
if value < 0 || value > 255 {
207+
return fmt.Errorf(`value "%d" out of allowable range for alias "%s"`, value, alias)
208+
}
194209

195-
if yfile, err := os.ReadFile(yamlFilePath); err != nil {
196-
return err
210+
g := `color256`
211+
if len(aliasGroup) > 0 {
212+
if aliasGroup[0] == `color8` {
213+
g = `color8`
214+
}
215+
}
216+
217+
if g == "color8" {
218+
colorMap8[alias] = value
197219
} else {
198-
if err := yaml.Unmarshal(yfile, &data); err != nil {
199-
return err
220+
colorMap256[alias] = value
221+
}
222+
223+
return nil
224+
}
225+
226+
func SetAliases(aliases map[string]int, aliasGroup ...string) error {
227+
228+
rwLock.Lock()
229+
defer rwLock.Unlock()
230+
231+
g := `color256`
232+
if len(aliasGroup) > 0 {
233+
if aliasGroup[0] == `color8` {
234+
g = `color8`
200235
}
201236
}
202237

203-
for aliasGroup, aliases := range data {
204-
205-
if aliasGroup == "color8" {
206-
for alias, real := range aliases {
207-
// try mapping to an existing color alias
208-
if val, ok := colorMap8[real]; ok {
209-
colorMap8[alias] = val
210-
} else {
211-
// allow a numeric mapping
212-
if numVal, err := strconv.Atoi(real); err == nil {
213-
colorMap8[alias] = numVal
238+
for alias, value := range aliases {
239+
if value < 0 || value > 255 {
240+
return fmt.Errorf(`value "%d" out of allowable range for alias "%s"`, value, alias)
241+
}
242+
243+
if g == "color8" {
244+
colorMap8[alias] = value
245+
} else {
246+
colorMap256[alias] = value
247+
}
248+
}
249+
250+
return nil
251+
}
252+
253+
func LoadAliases(yamlFilePaths ...string) error {
254+
255+
rwLock.Lock()
256+
defer rwLock.Unlock()
257+
258+
data := make(map[string]map[string]string, 100)
259+
260+
for _, yamlFilePath := range yamlFilePaths {
261+
262+
if yfile, err := os.ReadFile(yamlFilePath); err != nil {
263+
return err
264+
} else {
265+
if err := yaml.Unmarshal(yfile, &data); err != nil {
266+
return err
267+
}
268+
}
269+
270+
for aliasGroup, aliases := range data {
271+
272+
if aliasGroup == "color8" {
273+
for alias, real := range aliases {
274+
// try mapping to an existing color alias
275+
if val, ok := colorMap8[real]; ok {
276+
colorMap8[alias] = val
277+
} else {
278+
// allow a numeric mapping
279+
if numVal, err := strconv.Atoi(real); err == nil {
280+
colorMap8[alias] = numVal
281+
}
214282
}
215283
}
216284
}
217-
}
218285

219-
if aliasGroup == "color256" {
220-
for alias, real := range aliases {
221-
// try mapping to an existing color alias
222-
if val, ok := colorMap256[real]; ok {
223-
colorMap256[alias] = val
224-
} else {
225-
// allow a numeric mapping
226-
if numVal, err := strconv.Atoi(real); err == nil {
227-
colorMap256[alias] = numVal
286+
if aliasGroup == "color256" {
287+
for alias, real := range aliases {
288+
// try mapping to an existing color alias
289+
if val, ok := colorMap256[real]; ok {
290+
colorMap256[alias] = val
291+
} else {
292+
// allow a numeric mapping
293+
if numVal, err := strconv.Atoi(real); err == nil {
294+
colorMap256[alias] = numVal
295+
}
228296
}
229297
}
230298
}
231-
}
232299

233-
if aliasGroup == "position" {
234-
for alias, real := range aliases {
235-
posArr := strings.Split(real, ",")
236-
if len(posArr) == 2 {
237-
positionMap[alias] = posArr
300+
if aliasGroup == "position" {
301+
for alias, real := range aliases {
302+
posArr := strings.Split(real, ",")
303+
if len(posArr) == 2 {
304+
positionMap[alias] = posArr
305+
}
238306
}
239307
}
240-
}
241308

309+
}
242310
}
243311

244312
return nil

ansitags_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@ type TestCase struct {
1616
Expected string `yaml:"expected"`
1717
}
1818

19+
func TestParseTagOpenerMismatches(t *testing.T) {
20+
21+
testTable := loadTestFile("testdata/ansitags_test_tag_openers.yaml")
22+
23+
for name, testCase := range testTable {
24+
25+
t.Run(name, func(t *testing.T) {
26+
27+
output := Parse(testCase.Input)
28+
assert.Equal(t, testCase.Expected, output)
29+
30+
//fmt.Println(output)
31+
//bytes, _ := json.Marshal(output)
32+
//fmt.Println(string(bytes))
33+
})
34+
}
35+
36+
}
37+
1938
func TestParseAliases(t *testing.T) {
2039

2140
testTable := loadTestFile("testdata/ansitags_test_aliases.yaml")
@@ -167,6 +186,91 @@ func loadRawFile(filename string) string {
167186
return string(yfile)
168187
}
169188

189+
func TestSetAliasValidDefaultGroup(t *testing.T) {
190+
alias := "testAlias256"
191+
value := 123
192+
// Ensure clean state
193+
delete(colorMap256, alias)
194+
// Set alias in default (color256) group
195+
if err := SetAlias(alias, value); err != nil {
196+
t.Fatalf("expected no error, got %v", err)
197+
}
198+
if got := colorMap256[alias]; got != value {
199+
t.Errorf("colorMap256[%q] = %d; want %d", alias, got, value)
200+
}
201+
}
202+
203+
func TestSetAliasValidColor8Group(t *testing.T) {
204+
alias := "testAlias8"
205+
value := 45
206+
delete(colorMap8, alias)
207+
// Set alias in color8 group
208+
if err := SetAlias(alias, value, "color8"); err != nil {
209+
t.Fatalf("expected no error, got %v", err)
210+
}
211+
if got := colorMap8[alias]; got != value {
212+
t.Errorf("colorMap8[%q] = %d; want %d", alias, got, value)
213+
}
214+
}
215+
216+
func TestSetAliasInvalidValue(t *testing.T) {
217+
alias := "invalidAlias"
218+
invalidValue := -5
219+
// Expect error for out-of-range value
220+
if err := SetAlias(alias, invalidValue); err == nil {
221+
t.Errorf("expected error for invalid value %d, got none", invalidValue)
222+
}
223+
}
224+
225+
func TestSetAliasesValidDefaultGroup(t *testing.T) {
226+
aliases := map[string]int{
227+
"multi256_1": 200,
228+
"multi256_2": 201,
229+
}
230+
delete(colorMap256, "multi256_1")
231+
delete(colorMap256, "multi256_2")
232+
// Bulk set in default (color256) group
233+
if err := SetAliases(aliases); err != nil {
234+
t.Fatalf("expected no error, got %v", err)
235+
}
236+
for alias, expected := range aliases {
237+
if got := colorMap256[alias]; got != expected {
238+
t.Errorf("colorMap256[%q] = %d; want %d", alias, got, expected)
239+
}
240+
}
241+
}
242+
243+
func TestSetAliasesValidColor8Group(t *testing.T) {
244+
aliases := map[string]int{
245+
"multi8_1": 10,
246+
"multi8_2": 20,
247+
}
248+
delete(colorMap8, "multi8_1")
249+
delete(colorMap8, "multi8_2")
250+
// Bulk set in color8 group
251+
if err := SetAliases(aliases, "color8"); err != nil {
252+
t.Fatalf("expected no error, got %v", err)
253+
}
254+
for alias, expected := range aliases {
255+
if got := colorMap8[alias]; got != expected {
256+
t.Errorf("colorMap8[%q] = %d; want %d", alias, got, expected)
257+
}
258+
}
259+
}
260+
261+
func TestSetAliasesInvalidValue(t *testing.T) {
262+
aliases := map[string]int{
263+
"badAlias1": -1,
264+
"badAlias2": 300,
265+
}
266+
delete(colorMap256, "badAlias1")
267+
delete(colorMap256, "badAlias2")
268+
// Expect error and no partial application
269+
if err := SetAliases(aliases); err == nil {
270+
t.Fatalf("expected error for invalid alias value, got none")
271+
}
272+
}
273+
170274
//
171275
// Benchmarks
172276
// cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
No Tag:
2+
input: "This string has no ansi tags"
3+
expected: "This string has no ansi tags"
4+
Non Tags:
5+
input: "<this is just> some <text"
6+
expected: "<this is just> some <text"
7+
Tags with Non Tags:
8+
input: "<ansi fg='blue'>This is in<side of ansi tags</ansi>"
9+
expected: "\x1b[34;49mThis is in<side of ansi tags\x1b[0m"

0 commit comments

Comments
 (0)