Skip to content

Commit 466f15a

Browse files
wpjuniorclaude
andcommitted
Add SetBorderColorByString helper for named and hex color support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9a0a989 commit 466f15a

4 files changed

Lines changed: 179 additions & 0 deletions

File tree

color.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2026 tsuru authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tablecli
6+
7+
import (
8+
"fmt"
9+
stdColor "image/color"
10+
"strings"
11+
12+
"github.com/fatih/color"
13+
)
14+
15+
func SetBorderColorByString(tableColor string) {
16+
var tblColor *color.Color
17+
18+
if fgColor := colorMap[tableColor]; fgColor != 0 {
19+
tblColor = color.New(fgColor)
20+
} else if strings.HasPrefix(tableColor, "#") {
21+
c, err := parseHexColor(tableColor)
22+
23+
if err == nil {
24+
tblColor = color.RGB(int(c.R), int(c.G), int(c.B))
25+
}
26+
}
27+
28+
if tblColor != nil {
29+
TableConfig.BorderColorFunc = func(s string) string {
30+
return tblColor.Sprint(s)
31+
}
32+
}
33+
}
34+
35+
func parseHexColor(s string) (c stdColor.RGBA, err error) {
36+
c.A = 0xff
37+
switch len(s) {
38+
case 7:
39+
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
40+
case 4:
41+
_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
42+
// Double the hex digits:
43+
c.R *= 17
44+
c.G *= 17
45+
c.B *= 17
46+
default:
47+
err = fmt.Errorf("invalid length, must be 7 or 4")
48+
49+
}
50+
return
51+
}
52+
53+
var colorMap = map[string]color.Attribute{
54+
"black": color.FgBlack,
55+
"red": color.FgRed,
56+
"green": color.FgGreen,
57+
"yellow": color.FgYellow,
58+
"blue": color.FgBlue,
59+
"magenta": color.FgMagenta,
60+
"cyan": color.FgCyan,
61+
"white": color.FgWhite,
62+
"hi-black": color.FgHiBlack,
63+
"hi-red": color.FgHiRed,
64+
"hi-green": color.FgHiGreen,
65+
"hi-yellow": color.FgHiYellow,
66+
"hi-blue": color.FgHiBlue,
67+
"hi-magenta": color.FgHiMagenta,
68+
"hi-cyan": color.FgHiCyan,
69+
"hi-white": color.FgHiWhite,
70+
}

color_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2026 tsuru authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tablecli
6+
7+
import (
8+
"image/color"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestParseHexColor(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
input string
18+
want color.RGBA
19+
wantErr string
20+
}{
21+
{"full hex", "#FF8800", color.RGBA{R: 0xFF, G: 0x88, B: 0x00, A: 0xFF}, ""},
22+
{"short hex", "#F80", color.RGBA{R: 0xFF, G: 0x88, B: 0x00, A: 0xFF}, ""},
23+
{"black", "#000000", color.RGBA{R: 0, G: 0, B: 0, A: 0xFF}, ""},
24+
{"white", "#FFFFFF", color.RGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}, ""},
25+
{"short white", "#FFF", color.RGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}, ""},
26+
{"short black", "#000", color.RGBA{R: 0, G: 0, B: 0, A: 0xFF}, ""},
27+
{"lowercase", "#ff8800", color.RGBA{R: 0xFF, G: 0x88, B: 0x00, A: 0xFF}, ""},
28+
{"mixed case", "#Ff8800", color.RGBA{R: 0xFF, G: 0x88, B: 0x00, A: 0xFF}, ""},
29+
{"invalid length", "#FF88", color.RGBA{}, "invalid length, must be 7 or 4"},
30+
{"too long", "#FF880000", color.RGBA{}, "invalid length, must be 7 or 4"},
31+
{"too short", "#FF", color.RGBA{}, "invalid length, must be 7 or 4"},
32+
{"empty", "", color.RGBA{}, "invalid length"},
33+
{"no hash", "FF8800", color.RGBA{}, "invalid length"},
34+
{"invalid chars", "#ZZZZZZ", color.RGBA{}, "expected integer"},
35+
}
36+
for _, tt := range tests {
37+
t.Run(tt.name, func(t *testing.T) {
38+
got, err := parseHexColor(tt.input)
39+
if tt.wantErr != "" {
40+
assert.Error(t, err)
41+
assert.Contains(t, err.Error(), tt.wantErr)
42+
} else {
43+
assert.NoError(t, err)
44+
assert.Equal(t, tt.want, got)
45+
}
46+
})
47+
}
48+
}
49+
50+
func TestSetBorderColorByString(t *testing.T) {
51+
t.Run("named colors", func(t *testing.T) {
52+
namedColors := []string{
53+
"black", "red", "green", "yellow",
54+
"blue", "magenta", "cyan", "white",
55+
"hi-black", "hi-red", "hi-green", "hi-yellow",
56+
"hi-blue", "hi-magenta", "hi-cyan", "hi-white",
57+
}
58+
for _, name := range namedColors {
59+
t.Run(name, func(t *testing.T) {
60+
TableConfig.BorderColorFunc = nil
61+
SetBorderColorByString(name)
62+
assert.NotNil(t, TableConfig.BorderColorFunc, "BorderColorFunc should be set for color %q", name)
63+
})
64+
}
65+
TableConfig.BorderColorFunc = nil
66+
})
67+
68+
tests := []struct {
69+
name string
70+
input string
71+
wantNil bool
72+
}{
73+
{"hex color", "#FF0000", false},
74+
{"short hex", "#F00", false},
75+
{"invalid name", "invalid-color", true},
76+
{"empty string", "", true},
77+
{"invalid hex", "#ZZZZZZ", true},
78+
{"bad hex length", "#FF", true},
79+
}
80+
for _, tt := range tests {
81+
t.Run(tt.name, func(t *testing.T) {
82+
TableConfig.BorderColorFunc = nil
83+
SetBorderColorByString(tt.input)
84+
if tt.wantNil {
85+
assert.Nil(t, TableConfig.BorderColorFunc)
86+
return
87+
} else {
88+
assert.NotNil(t, TableConfig.BorderColorFunc)
89+
}
90+
91+
result := TableConfig.BorderColorFunc("hello")
92+
assert.NotEmpty(t, result)
93+
assert.Contains(t, result, "hello")
94+
TableConfig.BorderColorFunc = nil
95+
})
96+
}
97+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ module github.com/tsuru/tablecli
33
go 1.25.6
44

55
require (
6+
github.com/fatih/color v1.18.0
67
github.com/stretchr/testify v1.11.1
78
golang.org/x/term v0.39.0
89
)
910

1011
require (
1112
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/mattn/go-colorable v0.1.13 // indirect
14+
github.com/mattn/go-isatty v0.0.20 // indirect
1215
github.com/pmezard/go-difflib v1.0.0 // indirect
1316
golang.org/x/sys v0.40.0 // indirect
1417
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
4+
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
5+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
6+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
7+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
8+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
9+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
310
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
411
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
512
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
613
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
14+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
15+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
716
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
817
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
918
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=

0 commit comments

Comments
 (0)