Skip to content

Commit 4af13ac

Browse files
Issue #582: Add simplified badge colors (#669)
* add named colors map that includes shieldsio specific colors, semantic alias, and common css color names. add resolve color helper. update parse bade settings to use new helper. add tests for named color resolution * update readme
1 parent 53ecf6f commit 4af13ac

File tree

3 files changed

+292
-22
lines changed

3 files changed

+292
-22
lines changed

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,20 +1084,38 @@ For `cocomo` you can also set the `avg-wage` value similar to `scc` itself. For
10841084

10851085
Note that the avg-wage value must be a positive integer otherwise it will revert back to the default value of 56286.
10861086

1087-
You can also configure the look and feel of the bad using the following parameters,
1087+
You can also configure the look and feel of the badge using the following parameters,
10881088

10891089
- ?lower=true will lower the title text, so "Total lines" would be "total lines"
1090-
he below can control the colours of shadows, fonts and badges
1090+
1091+
The below can control the colours of shadows, fonts and badges. Colors can be specified as either hex codes or named colors (similar to shields.io):
1092+
10911093
- ?font-color=fff
10921094
- ?font-shadow-color=010101
10931095
- ?top-shadow-accent-color=bbb
10941096
- ?title-bg-color=555
10951097
- ?badge-bg-color=4c1
10961098

1099+
##### Named Colors
1100+
1101+
For convenience, you can use named colors instead of hex codes. The following named colors are supported:
1102+
1103+
**Shields.io colors:** `brightgreen`, `green`, `yellowgreen`, `yellow`, `orange`, `red`, `blue`, `lightgrey`, `blueviolet`
1104+
1105+
**Semantic aliases:** `success`, `important`, `critical`, `informational`, `inactive`
1106+
1107+
**CSS colors:** `white`, `black`, `silver`, `gray`, `maroon`, `purple`, `fuchsia`, `lime`, `olive`, `navy`, `teal`, `aqua`, `cyan`, `magenta`, `pink`, `coral`, `salmon`, `gold`, `khaki`, `violet`, `indigo`, `crimson`, `turquoise`, `tan`, `brown`, and many more standard CSS color names.
1108+
1109+
For example, instead of `?badge-bg-color=007ec6` you can use `?badge-bg-color=blue`.
1110+
10971111
An example of using some of these parameters to produce an admittedly ugly result
10981112

10991113
[![Scc Count Badge](https://sloc.xyz/github/boyter/scc?font-color=ff0000&badge-bg-color=0000ff&lower=true)](https://github.com/boyter/scc/)
11001114

1115+
An example using named colors for as a slightly nicer result
1116+
1117+
[![Scc Count Badge](https://sloc.xyz/github/boyter/scc?title-bg-color=navy&badge-bg-color=blue&font-color=white)](https://github.com/boyter/scc/)
1118+
11011119
*NB* it may not work for VERY large repositories (has been tested on Apache hadoop/spark without issue).
11021120

11031121
You can find the source code for badges in the repository at <https://github.com/boyter/scc/blob/master/cmd/badges/main.go>

cmd/badges/main.go

Lines changed: 167 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,157 @@ type badgeSettings struct {
241241
BadgeBackgroundColor string
242242
}
243243

244+
// namedColors maps color names to their hex values (without #).
245+
// Includes shields.io colors and common CSS color names for compatibility.
246+
var namedColors = map[string]string{
247+
// shields.io specific colors
248+
"brightgreen": "44cc11",
249+
"green": "97ca00",
250+
"yellowgreen": "a4a61d",
251+
"yellow": "dfb317",
252+
"orange": "fe7d37",
253+
"red": "e05d44",
254+
"blue": "007ec6",
255+
"lightgrey": "9f9f9f",
256+
"lightgray": "9f9f9f",
257+
"grey": "555555",
258+
"gray": "555555",
259+
"blueviolet": "8a2be2",
260+
// shields.io semantic aliases
261+
"success": "44cc11",
262+
"important": "fe7d37",
263+
"critical": "e05d44",
264+
"informational": "007ec6",
265+
"inactive": "9f9f9f",
266+
// Common CSS color names
267+
"black": "000000",
268+
"white": "ffffff",
269+
"silver": "c0c0c0",
270+
"maroon": "800000",
271+
"purple": "800080",
272+
"fuchsia": "ff00ff",
273+
"lime": "00ff00",
274+
"olive": "808000",
275+
"navy": "000080",
276+
"teal": "008080",
277+
"aqua": "00ffff",
278+
"cyan": "00ffff",
279+
"magenta": "ff00ff",
280+
"pink": "ffc0cb",
281+
"coral": "ff7f50",
282+
"salmon": "fa8072",
283+
"gold": "ffd700",
284+
"khaki": "f0e68c",
285+
"violet": "ee82ee",
286+
"indigo": "4b0082",
287+
"crimson": "dc143c",
288+
"turquoise": "40e0d0",
289+
"tan": "d2b48c",
290+
"brown": "a52a2a",
291+
"chocolate": "d2691e",
292+
"tomato": "ff6347",
293+
"orchid": "da70d6",
294+
"plum": "dda0dd",
295+
"peru": "cd853f",
296+
"sienna": "a0522d",
297+
"beige": "f5f5dc",
298+
"ivory": "fffff0",
299+
"linen": "faf0e6",
300+
"azure": "f0ffff",
301+
"lavender": "e6e6fa",
302+
"wheat": "f5deb3",
303+
"snow": "fffafa",
304+
"seashell": "fff5ee",
305+
"honeydew": "f0fff0",
306+
"mintcream": "f5fffa",
307+
"aliceblue": "f0f8ff",
308+
"ghostwhite": "f8f8ff",
309+
"oldlace": "fdf5e6",
310+
"papayawhip": "ffefd5",
311+
"moccasin": "ffe4b5",
312+
"bisque": "ffe4c4",
313+
"mistyrose": "ffe4e1",
314+
"lemonchiffon": "fffacd",
315+
"cornsilk": "fff8dc",
316+
"antiquewhite": "faebd7",
317+
"floralwhite": "fffaf0",
318+
"steelblue": "4682b4",
319+
"royalblue": "4169e1",
320+
"skyblue": "87ceeb",
321+
"dodgerblue": "1e90ff",
322+
"deepskyblue": "00bfff",
323+
"cadetblue": "5f9ea0",
324+
"cornflowerblue": "6495ed",
325+
"mediumblue": "0000cd",
326+
"darkblue": "00008b",
327+
"midnightblue": "191970",
328+
"slateblue": "6a5acd",
329+
"darkslateblue": "483d8b",
330+
"mediumslateblue": "7b68ee",
331+
"seagreen": "2e8b57",
332+
"mediumseagreen": "3cb371",
333+
"lightgreen": "90ee90",
334+
"darkgreen": "006400",
335+
"forestgreen": "228b22",
336+
"limegreen": "32cd32",
337+
"springgreen": "00ff7f",
338+
"palegreen": "98fb98",
339+
"darkseagreen": "8fbc8f",
340+
"olivedrab": "6b8e23",
341+
"darkolivegreen": "556b2f",
342+
"darkred": "8b0000",
343+
"firebrick": "b22222",
344+
"indianred": "cd5c5c",
345+
"lightsalmon": "ffa07a",
346+
"darksalmon": "e9967a",
347+
"lightcoral": "f08080",
348+
"rosybrown": "bc8f8f",
349+
"sandybrown": "f4a460",
350+
"goldenrod": "daa520",
351+
"darkgoldenrod": "b8860b",
352+
"darkorange": "ff8c00",
353+
"orangered": "ff4500",
354+
"hotpink": "ff69b4",
355+
"deeppink": "ff1493",
356+
"palevioletred": "db7093",
357+
"mediumvioletred": "c71585",
358+
"mediumpurple": "9370db",
359+
"darkorchid": "9932cc",
360+
"darkviolet": "9400d3",
361+
"darkmagenta": "8b008b",
362+
"slategray": "708090",
363+
"slategrey": "708090",
364+
"lightslategray": "778899",
365+
"lightslategrey": "778899",
366+
"darkslategray": "2f4f4f",
367+
"darkslategrey": "2f4f4f",
368+
"dimgray": "696969",
369+
"dimgrey": "696969",
370+
"darkgray": "a9a9a9",
371+
"darkgrey": "a9a9a9",
372+
"gainsboro": "dcdcdc",
373+
"whitesmoke": "f5f5f5",
374+
}
375+
376+
// resolveColor converts a color input (name or hex) to a hex value without #.
377+
// Returns the hex value if valid, or empty string if invalid.
378+
func resolveColor(color string) string {
379+
color = strings.ToLower(color)
380+
381+
// Check if it's a named color
382+
if hex, ok := namedColors[color]; ok {
383+
return hex
384+
}
385+
386+
// Check if it's a valid hex color (3, 4, 6, or 8 digits)
387+
hexRegex := regexp.MustCompile(`^(?:(?:[\da-f]{3}){1,2}|(?:[\da-f]{4}){1,2})$`)
388+
if hexRegex.MatchString(color) {
389+
return color
390+
}
391+
392+
return ""
393+
}
394+
244395
// Parses badge settings from url query params
245396
// if error, ignore and return default badge settings
246397
func parseBadgeSettings(values url.Values) *badgeSettings {
@@ -252,31 +403,27 @@ func parseBadgeSettings(values url.Values) *badgeSettings {
252403
BadgeBackgroundColor: "4c1",
253404
}
254405

255-
fontColor := strings.ToLower(values.Get("font-color"))
256-
textShadowColor := strings.ToLower(values.Get("font-shadow-color"))
257-
topShadowAccentColor := strings.ToLower(values.Get("top-shadow-accent-color"))
258-
titleBackgroundColor := strings.ToLower(values.Get("title-bg-color"))
259-
badgeBackgroundColor := strings.ToLower(values.Get("badge-bg-color"))
406+
fontColor := values.Get("font-color")
407+
textShadowColor := values.Get("font-shadow-color")
408+
topShadowAccentColor := values.Get("top-shadow-accent-color")
409+
titleBackgroundColor := values.Get("title-bg-color")
410+
badgeBackgroundColor := values.Get("badge-bg-color")
260411

261-
// Ensure valid colors
262-
r, err := regexp.Compile(`^(?:(?:[\da-f]{3}){1,2}|(?:[\da-f]{4}){1,2})$`)
263-
if err != nil {
264-
return &bs
265-
}
266-
if r.MatchString(fontColor) {
267-
bs.FontColor = fontColor
412+
// Resolve colors (supports both named colors and hex codes)
413+
if resolved := resolveColor(fontColor); resolved != "" {
414+
bs.FontColor = resolved
268415
}
269-
if r.MatchString(textShadowColor) {
270-
bs.TextShadowColor = textShadowColor
416+
if resolved := resolveColor(textShadowColor); resolved != "" {
417+
bs.TextShadowColor = resolved
271418
}
272-
if r.MatchString(topShadowAccentColor) {
273-
bs.TopShadowAccentColor = topShadowAccentColor
419+
if resolved := resolveColor(topShadowAccentColor); resolved != "" {
420+
bs.TopShadowAccentColor = resolved
274421
}
275-
if r.MatchString(titleBackgroundColor) {
276-
bs.TitleBackgroundColor = titleBackgroundColor
422+
if resolved := resolveColor(titleBackgroundColor); resolved != "" {
423+
bs.TitleBackgroundColor = resolved
277424
}
278-
if r.MatchString(badgeBackgroundColor) {
279-
bs.BadgeBackgroundColor = badgeBackgroundColor
425+
if resolved := resolveColor(badgeBackgroundColor); resolved != "" {
426+
bs.BadgeBackgroundColor = resolved
280427
}
281428

282429
return &bs

cmd/badges/main_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,62 @@ import (
66
"testing"
77
)
88

9+
func Test_resolveColor(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
color string
13+
want string
14+
}{
15+
// Named colors (shields.io)
16+
{name: "blue", color: "blue", want: "007ec6"},
17+
{name: "red", color: "red", want: "e05d44"},
18+
{name: "green", color: "green", want: "97ca00"},
19+
{name: "brightgreen", color: "brightgreen", want: "44cc11"},
20+
{name: "yellowgreen", color: "yellowgreen", want: "a4a61d"},
21+
{name: "orange", color: "orange", want: "fe7d37"},
22+
{name: "yellow", color: "yellow", want: "dfb317"},
23+
{name: "lightgrey", color: "lightgrey", want: "9f9f9f"},
24+
{name: "lightgray", color: "lightgray", want: "9f9f9f"},
25+
{name: "blueviolet", color: "blueviolet", want: "8a2be2"},
26+
// Semantic aliases
27+
{name: "success", color: "success", want: "44cc11"},
28+
{name: "critical", color: "critical", want: "e05d44"},
29+
{name: "informational", color: "informational", want: "007ec6"},
30+
// CSS colors
31+
{name: "white", color: "white", want: "ffffff"},
32+
{name: "black", color: "black", want: "000000"},
33+
{name: "navy", color: "navy", want: "000080"},
34+
{name: "teal", color: "teal", want: "008080"},
35+
// Case insensitivity
36+
{name: "BLUE uppercase", color: "BLUE", want: "007ec6"},
37+
{name: "Blue mixed case", color: "Blue", want: "007ec6"},
38+
// Hex codes (3 digit)
39+
{name: "hex 3 digit", color: "fff", want: "fff"},
40+
{name: "hex 3 digit mixed", color: "a1b", want: "a1b"},
41+
// Hex codes (6 digit)
42+
{name: "hex 6 digit", color: "abcdef", want: "abcdef"},
43+
{name: "hex 6 digit uppercase", color: "ABCDEF", want: "abcdef"},
44+
// Hex codes (4 digit with alpha)
45+
{name: "hex 4 digit", color: "fffa", want: "fffa"},
46+
// Hex codes (8 digit with alpha)
47+
{name: "hex 8 digit", color: "abcdef12", want: "abcdef12"},
48+
// Invalid inputs
49+
{name: "invalid name", color: "notacolor", want: ""},
50+
{name: "invalid hex too short", color: "ab", want: ""},
51+
{name: "invalid hex too long", color: "abcdefghi", want: ""},
52+
{name: "invalid hex with special chars", color: "abc#ef", want: ""},
53+
{name: "empty string", color: "", want: ""},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
if got := resolveColor(tt.color); got != tt.want {
59+
t.Errorf("resolveColor(%q) = %q, want %q", tt.color, got, tt.want)
60+
}
61+
})
62+
}
63+
}
64+
965
func Test_formatCount(t *testing.T) {
1066
type args struct {
1167
count float64
@@ -187,6 +243,55 @@ func Test_parseBadgeSettings(t *testing.T) {
187243
},
188244
want: defaultSettings,
189245
},
246+
{
247+
name: "named colors - shields.io style",
248+
values: url.Values{
249+
"font-color": []string{"white"},
250+
"font-shadow-color": []string{"black"},
251+
"top-shadow-accent-color": []string{"lightgrey"},
252+
"title-bg-color": []string{"blue"},
253+
"badge-bg-color": []string{"brightgreen"},
254+
},
255+
want: &badgeSettings{
256+
FontColor: "ffffff",
257+
TextShadowColor: "000000",
258+
TopShadowAccentColor: "9f9f9f",
259+
TitleBackgroundColor: "007ec6",
260+
BadgeBackgroundColor: "44cc11",
261+
},
262+
},
263+
{
264+
name: "mixed named colors and hex codes",
265+
values: url.Values{
266+
"font-color": []string{"fff"},
267+
"font-shadow-color": []string{"navy"},
268+
"top-shadow-accent-color": []string{"bbb"},
269+
"title-bg-color": []string{"blueviolet"},
270+
"badge-bg-color": []string{"success"},
271+
},
272+
want: &badgeSettings{
273+
FontColor: "fff",
274+
TextShadowColor: "000080",
275+
TopShadowAccentColor: "bbb",
276+
TitleBackgroundColor: "8a2be2",
277+
BadgeBackgroundColor: "44cc11",
278+
},
279+
},
280+
{
281+
name: "case insensitive named colors",
282+
values: url.Values{
283+
"font-color": []string{"WHITE"},
284+
"title-bg-color": []string{"Blue"},
285+
"badge-bg-color": []string{"BrightGreen"},
286+
},
287+
want: &badgeSettings{
288+
FontColor: "ffffff",
289+
TextShadowColor: defaultSettings.TextShadowColor,
290+
TopShadowAccentColor: defaultSettings.TopShadowAccentColor,
291+
TitleBackgroundColor: "007ec6",
292+
BadgeBackgroundColor: "44cc11",
293+
},
294+
},
190295
}
191296

192297
for _, tt := range tests {

0 commit comments

Comments
 (0)