From a99fb9facd50c33c0e24d5e626c52ff05447871f Mon Sep 17 00:00:00 2001 From: jolheiser Date: Sun, 18 Oct 2020 16:47:07 -0500 Subject: [PATCH] Extended and true colors Signed-off-by: jolheiser --- _examples/examples.go | 8 + color/attribute.go | 87 ------- color/basic.go | 90 +++++++ color/basic_colors.go | 49 ++++ color/basic_test.go | 38 +++ color/color.go | 101 +------- color/color_test.go | 35 +-- color/extended.go | 48 ++++ color/extended_colors.go | 519 +++++++++++++++++++++++++++++++++++++++ color/rgb.go | 66 +++++ color/rgb_test.go | 35 +++ color/true.go | 45 ++++ level.go | 2 +- 13 files changed, 903 insertions(+), 220 deletions(-) delete mode 100644 color/attribute.go create mode 100644 color/basic.go create mode 100644 color/basic_colors.go create mode 100644 color/basic_test.go create mode 100644 color/extended.go create mode 100644 color/extended_colors.go create mode 100644 color/rgb.go create mode 100644 color/rgb_test.go create mode 100644 color/true.go diff --git a/_examples/examples.go b/_examples/examples.go index 55af5e2..14df6ac 100644 --- a/_examples/examples.go +++ b/_examples/examples.go @@ -104,4 +104,12 @@ func main() { color.Time = color.New(color.FgCyan) color.Stack = color.New(color.FgGreen) b.Info("Full options emulating Gitea") + + // Extended example + ex := color.NewExtended(color.Gold1, color.DarkGreen) + fmt.Println(ex.Format("Gold on Dark Green")) + + // True color example + birb := color.NewTrue(color.MustParseHex("#007D96"), &color.RGB{}) + fmt.Println(birb.Format("Birb blue on black")) } diff --git a/color/attribute.go b/color/attribute.go deleted file mode 100644 index 31f3b58..0000000 --- a/color/attribute.go +++ /dev/null @@ -1,87 +0,0 @@ -package color - -// Attribute defines a single SGR Code -type Attribute int - -// Logger attributes -const ( - Reset Attribute = iota - Bold - Faint - Italic - Underline - BlinkSlow - BlinkRapid - ReverseVideo - Concealed - CrossedOut -) - -// Foreground text colors -const ( - FgBlack Attribute = iota + 30 - FgRed - FgGreen - FgYellow - FgBlue - FgMagenta - FgCyan - FgWhite -) - -// Foreground Hi-Intensity text colors -const ( - FgHiBlack Attribute = iota + 90 - FgHiRed - FgHiGreen - FgHiYellow - FgHiBlue - FgHiMagenta - FgHiCyan - FgHiWhite -) - -// Background text colors -const ( - BgBlack Attribute = iota + 40 - BgRed - BgGreen - BgYellow - BgBlue - BgMagenta - BgCyan - BgWhite -) - -// Background Hi-Intensity text colors -const ( - BgHiBlack Attribute = iota + 100 - BgHiRed - BgHiGreen - BgHiYellow - BgHiBlue - BgHiMagenta - BgHiCyan - BgHiWhite -) - -var attrCache = make(map[string]*Color) - -func attr(a Attribute) *Color { - if c, ok := attrCache[string(a)]; ok { - return c - } - c := New(a) - attrCache[string(a)] = c - return c -} - -// Format is a quick way to format a string using a single attribute -func (a Attribute) Format(text string) string { - return attr(a).Format(text) -} - -// Format is a quick way to format a formatted string using a single attribute -func (a Attribute) Formatf(text string, v ...interface{}) string { - return attr(a).Formatf(text, v...) -} diff --git a/color/basic.go b/color/basic.go new file mode 100644 index 0000000..99247a1 --- /dev/null +++ b/color/basic.go @@ -0,0 +1,90 @@ +package color + +import ( + "fmt" + "strconv" + "strings" +) + +// Interface guard +var _ Color = &Basic{} + +// Basic defines a custom color object which is defined by SGR parameters. +type Basic struct { + Attrs []BasicAttribute +} + +// BasicAttribute defines a single SGR Code +type BasicAttribute int + +// Logger attributes +const ( + Reset BasicAttribute = iota + Bold + Faint + Italic + Underline + BlinkSlow + BlinkRapid + ReverseVideo + Concealed + CrossedOut +) + +// New returns a new Color with attrs Attributes +func New(attrs ...BasicAttribute) *Basic { + return &Basic{Attrs: attrs} +} + +// Add adds new Attributes to a Color +func (b *Basic) Add(attrs ...BasicAttribute) { + for _, attr := range attrs { + has := false + for _, att := range b.Attrs { + if attr == att { + has = true + break + } + } + if !has { + b.Attrs = append(b.Attrs, attr) + } + } +} + +// String returns a string representation of the sum of a Color's Attributes +func (b *Basic) String() string { + attrs := 0 + for _, attr := range b.Attrs { + attrs += int(attr) + } + return fmt.Sprintf("%d", attrs) +} + +func (b *Basic) sequence() string { + format := make([]string, len(b.Attrs)) + for i, v := range b.Attrs { + format[i] = strconv.Itoa(int(v)) + } + + return strings.Join(format, ";") +} + +func (b *Basic) wrap(s string) string { + return b.format() + s + b.unformat() +} + +func (b *Basic) format() string { + return fmt.Sprintf("%s[%sm", escape, b.sequence()) +} + +func (b *Basic) unformat() string { + return fmt.Sprintf("%s[%dm", escape, Reset) +} + +func (b *Basic) Format(text string) string { + if len(b.Attrs) > 0 { + return b.wrap(text) + } + return text +} diff --git a/color/basic_colors.go b/color/basic_colors.go new file mode 100644 index 0000000..0a9ca24 --- /dev/null +++ b/color/basic_colors.go @@ -0,0 +1,49 @@ +package color + +// Foreground text colors +const ( + FgBlack BasicAttribute = iota + 30 + FgRed + FgGreen + FgYellow + FgBlue + FgMagenta + FgCyan + FgWhite +) + +// Foreground Hi-Intensity text colors +const ( + FgHiBlack BasicAttribute = iota + 90 + FgHiRed + FgHiGreen + FgHiYellow + FgHiBlue + FgHiMagenta + FgHiCyan + FgHiWhite +) + +// Background text colors +const ( + BgBlack BasicAttribute = iota + 40 + BgRed + BgGreen + BgYellow + BgBlue + BgMagenta + BgCyan + BgWhite +) + +// Background Hi-Intensity text colors +const ( + BgHiBlack BasicAttribute = iota + 100 + BgHiRed + BgHiGreen + BgHiYellow + BgHiBlue + BgHiMagenta + BgHiCyan + BgHiWhite +) diff --git a/color/basic_test.go b/color/basic_test.go new file mode 100644 index 0000000..24c3f38 --- /dev/null +++ b/color/basic_test.go @@ -0,0 +1,38 @@ +package color + +import ( + "testing" +) + +var color = New() + +func TestColor(t *testing.T) { + tt := []struct { + Name string + SetAttrs []BasicAttribute + AddAttrs []BasicAttribute + Size int + }{ + {"Init", nil, nil, 0}, + {"Set 2", []BasicAttribute{Bold, FgBlack}, nil, 2}, + {"Add 2", nil, []BasicAttribute{Italic, BgWhite}, 4}, + {"Set 3", []BasicAttribute{Bold, FgBlack, BgWhite}, nil, 3}, + {"Add 3", nil, []BasicAttribute{Italic, FgWhite, BgBlack}, 6}, + {"Add Same", nil, []BasicAttribute{Italic, FgWhite, BgBlack}, 6}, + {"Add 2 Same", nil, []BasicAttribute{Italic, FgWhite, BgGreen}, 7}, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + if tc.SetAttrs != nil { + color.Attrs = tc.SetAttrs + } + if tc.AddAttrs != nil { + color.Add(tc.AddAttrs...) + } + if len(color.Attrs) != tc.Size { + t.Logf("color has `%d` attributes, but should have `%d`", len(color.Attrs), tc.Size) + } + }) + } +} diff --git a/color/color.go b/color/color.go index 04483fb..2cd4e02 100644 --- a/color/color.go +++ b/color/color.go @@ -1,112 +1,17 @@ package color import ( - "fmt" - "regexp" - "strconv" "strings" ) const escape = "\x1b" -var ( - directive = `{[^}]*}` - directiveRe = regexp.MustCompile("[#]" + directive) -) - -// DirectivePrefix changes the directive prefix for parsing color wrap directives -func DirectivePrefix(p rune) { - directiveRe = regexp.MustCompile("[" + string(p) + "]" + directive) -} - -// Color defines a custom color object which is defined by SGR parameters. -type Color struct { - Attrs []Attribute -} - -// New returns a new Color with attrs Attributes -func New(attrs ...Attribute) *Color { - return &Color{Attrs: attrs} -} - -// Add adds new Attributes to a Color -func (c *Color) Add(attrs ...Attribute) { - for _, attr := range attrs { - has := false - for _, att := range c.Attrs { - if attr == att { - has = true - break - } - } - if !has { - c.Attrs = append(c.Attrs, attr) - } - } -} - -// String returns a string representation of the sum of a Color's Attributes -func (c *Color) String() string { - attrs := 0 - for _, attr := range c.Attrs { - attrs += int(attr) - } - return fmt.Sprintf("%d", attrs) -} - -func (c *Color) sequence() string { - format := make([]string, len(c.Attrs)) - for i, v := range c.Attrs { - format[i] = strconv.Itoa(int(v)) - } - - return strings.Join(format, ";") -} - -func (c *Color) wrap(s string) string { - return c.format() + s + unformat() -} - -func (c *Color) format() string { - return fmt.Sprintf("%s[%sm", escape, c.sequence()) -} - -func unformat() string { - return fmt.Sprintf("%s[%dm", escape, Reset) -} - -// Format returns a string wrapped in the color -func (c *Color) Format(text string) string { - if len(c.Attrs) > 0 { - return c.wrap(text) - } - return text -} - -// Formatf returns a formatted string wrapped in the color -func (c *Color) Formatf(format string, v ...interface{}) string { - return c.Format(fmt.Sprintf(format, v...)) -} - -func parse(text string, primary, secondary *Color) string { - for { - loc := directiveRe.FindStringIndex(text) - if loc == nil { - break - } - text = text[:loc[0]] + unformat() + secondary.Format(text[loc[0]+2:loc[1]-1]) + primary.format() + text[loc[1]:] - } - return text -} - -// Wrap returns a string wrapped in a primary color, with #{} directives wrapped in a secondary color -func (c *Color) Wrap(text string, secondary *Color) string { - text = parse(text, c, secondary) - return c.Format(text) +type Color interface { + Format(text string) string } // ParseLevel parses a string and returns a Beaver Level's Color, defaulting to Info -func ParseLevel(level string) *Color { +func ParseLevel(level string) Color { switch strings.ToUpper(level) { case "T", "TRACE": return Trace diff --git a/color/color_test.go b/color/color_test.go index 8a03220..f5059aa 100644 --- a/color/color_test.go +++ b/color/color_test.go @@ -5,47 +5,14 @@ import ( "testing" ) -var color = New() - func TestMain(m *testing.M) { os.Exit(m.Run()) } -func TestColor(t *testing.T) { - tt := []struct { - Name string - SetAttrs []Attribute - AddAttrs []Attribute - Size int - }{ - {"Init", nil, nil, 0}, - {"Set 2", []Attribute{Bold, FgBlack}, nil, 2}, - {"Add 2", nil, []Attribute{Italic, BgWhite}, 4}, - {"Set 3", []Attribute{Bold, FgBlack, BgWhite}, nil, 3}, - {"Add 3", nil, []Attribute{Italic, FgWhite, BgBlack}, 6}, - {"Add Same", nil, []Attribute{Italic, FgWhite, BgBlack}, 6}, - {"Add 2 Same", nil, []Attribute{Italic, FgWhite, BgGreen}, 7}, - } - - for _, tc := range tt { - t.Run(tc.Name, func(t *testing.T) { - if tc.SetAttrs != nil { - color.Attrs = tc.SetAttrs - } - if tc.AddAttrs != nil { - color.Add(tc.AddAttrs...) - } - if len(color.Attrs) != tc.Size { - t.Logf("color has `%d` attributes, but should have `%d`", len(color.Attrs), tc.Size) - } - }) - } -} - func TestParseLevel(t *testing.T) { tt := []struct { Parse string - Expected *Color + Expected Color }{ {"T", Trace}, {"Trace", Trace}, diff --git a/color/extended.go b/color/extended.go new file mode 100644 index 0000000..0a08b84 --- /dev/null +++ b/color/extended.go @@ -0,0 +1,48 @@ +package color + +import ( + "fmt" +) + +// Interface guard +var _ Color = &Extended{} + +const ( + extendedFG = "38;5;" + extendedBG = "48;5;" +) + +// Extended defines a custom color object which is defined by SGR parameters. +type Extended struct { + FG ExtendedAttribute + BG ExtendedAttribute +} + +// NewExtended returns a new Color with an ExtendedAttribute FG and BG +func NewExtended(fg, bg ExtendedAttribute) *Extended { + return &Extended{ + FG: fg, + BG: bg, + } +} + +// String returns a string representation of the sum of a Color's Attributes +func (e *Extended) String() string { + return fmt.Sprintf("%d", e.FG+e.BG) +} + +func (e *Extended) wrap(s string) string { + return e.format() + s + e.unformat() +} + +func (e *Extended) format() string { + return fmt.Sprintf("%s[%s%dm%s[%s%dm", escape, extendedFG, e.FG, escape, extendedBG, e.BG) +} + +func (e *Extended) unformat() string { + return fmt.Sprintf("%s[%dm", escape, Reset) +} + +func (e *Extended) Format(text string) string { + return e.wrap(text) +} diff --git a/color/extended_colors.go b/color/extended_colors.go new file mode 100644 index 0000000..a264b48 --- /dev/null +++ b/color/extended_colors.go @@ -0,0 +1,519 @@ +package color + +// ExtendedAttribute defines an extended color +type ExtendedAttribute int + +const ( + // #000000 + Black ExtendedAttribute = iota + // #800000 + Maroon + // #008000 + Green + // #808000 + Olive + // #000080 + Navy + // #800080 + Purple + // #008080 + Teal + // #c0c0c0 + Silver + // #808080 + Grey + // #ff0000 + Red + // #00ff00 + Lime + // #ffff00 + Yellow + // #0000ff + Blue + // #ff00ff + Fuchsia + // #00ffff + Aqua + // #ffffff + White + // #000000 + Grey0 + // #00005f + NavyBlue + // #000087 + DarkBlue + // #0000af + Blue3 + // #0000d7 + Blue3_1 + // #0000ff + Blue1 + // #005f00 + DarkGreen + // #005f5f + DeepSkyBlue4 + // #005f87 + DeepSkyBlue4_1 + // #005faf + DeepSkyBlue4_2 + // #005fd7 + DodgerBlue3 + // #005fff + DodgerBlue2 + // #008700 + Green4 + // #00875f + SpringGreen4 + // #008787 + Turquoise4 + // #0087af + DeepSkyBlue3 + // #0087d7 + DeepSkyBlue3_1 + // #0087ff + DodgerBlue1 + // #00af00 + Green3 + // #00af5f + SpringGreen3 + // #00af87 + DarkCyan + // #00afaf + LightSeaGreen + // #00afd7 + DeepSkyBlue2 + // #00afff + DeepSkyBlue1 + // #00d700 + Green3_1 + // #00d75f + SpringGreen3_1 + // #00d787 + SpringGreen2 + // #00d7af + Cyan3 + // #00d7d7 + DarkTurquoise + // #00d7ff + Turquoise2 + // #00ff00 + Green1 + // #00ff5f + SpringGreen2_1 + // #00ff87 + SpringGreen1 + // #00ffaf + MediumSpringGreen + // #00ffd7 + Cyan2 + // #00ffff + Cyan1 + // #5f0000 + DarkRed + // #5f005f + DeepPink4 + // #5f0087 + Purple4 + // #5f00af + Purple4_1 + // #5f00d7 + Purple3 + // #5f00ff + BlueViolet + // #5f5f00 + Orange4 + // #5f5f5f + Grey37 + // #5f5f87 + MediumPurple4 + // #5f5faf + SlateBlue3 + // #5f5fd7 + SlateBlue3_1 + // #5f5fff + RoyalBlue1 + // #5f8700 + Chartreuse4 + // #5f875f + DarkSeaGreen4 + // #5f8787 + PaleTurquoise4 + // #5f87af + SteelBlue + // #5f87d7 + SteelBlue3 + // #5f87ff + CornflowerBlue + // #5faf00 + Chartreuse3 + // #5faf5f + DarkSeaGreen4_1 + // #5faf87 + CadetBlue + // #5fafaf + CadetBlue_1 + // #5fafd7 + SkyBlue3 + // #5fafff + SteelBlue1 + // #5fd700 + Chartreuse3_1 + // #5fd75f + PaleGreen3 + // #5fd787 + SeaGreen3 + // #5fd7af + Aquamarine3 + // #5fd7d7 + MediumTurquoise + // #5fd7ff + SteelBlue1_1 + // #5fff00 + Chartreuse2 + // #5fff5f + SeaGreen2 + // #5fff87 + SeaGreen1 + // #5fffaf + SeaGreen1_1 + // #5fffd7 + Aquamarine1 + // #5fffff + DarkSlateGray2 + // #870000 + DarkRed_1 + // #87005f + DeepPink4_1 + // #870087 + DarkMagenta + // #8700af + DarkMagenta_1 + // #8700d7 + DarkViolet + // #8700ff + Purple_1 + // #875f00 + Orange4_1 + // #875f5f + LightPink4 + // #875f87 + Plum4 + // #875faf + MediumPurple3 + // #875fd7 + MediumPurple3_1 + // #875fff + SlateBlue1 + // #878700 + Yellow4 + // #87875f + Wheat4 + // #878787 + Grey53 + // #8787af + LightSlateGrey + // #8787d7 + MediumPurple + // #8787ff + LightSlateBlue + // #87af00 + Yellow4_1 + // #87af5f + DarkOliveGreen3 + // #87af87 + DarkSeaGreen + // #87afaf + LightSkyBlue3 + // #87afd7 + LightSkyBlue3_1 + // #87afff + SkyBlue2 + // #87d700 + Chartreuse2_1 + // #87d75f + DarkOliveGreen3_1 + // #87d787 + PaleGreen3_1 + // #87d7af + DarkSeaGreen3 + // #87d7d7 + DarkSlateGray3 + // #87d7ff + SkyBlue1 + // #87ff00 + Chartreuse1 + // #87ff5f + LightGreen + // #87ff87 + LightGreen_1 + // #87ffaf + PaleGreen1 + // #87ffd7 + Aquamarine1_1 + // #87ffff + DarkSlateGray1 + // #af0000 + Red3 + // #af005f + DeepPink4_2 + // #af0087 + MediumVioletRed + // #af00af + Magenta3 + // #af00d7 + DarkViolet_1 + // #af00ff + Purple_2 + // #af5f00 + DarkOrange3 + // #af5f5f + IndianRed + // #af5f87 + HotPink3 + // #af5faf + MediumOrchid3 + // #af5fd7 + MediumOrchid + // #af5fff + MediumPurple2 + // #af8700 + DarkGoldenrod + // #af875f + LightSalmon3 + // #af8787 + RosyBrown + // #af87af + Grey63 + // #af87d7 + MediumPurple2_1 + // #af87ff + MediumPurple1 + // #afaf00 + Gold3 + // #afaf5f + DarkKhaki + // #afaf87 + NavajoWhite3 + // #afafaf + Grey69 + // #afafd7 + LightSteelBlue3 + // #afafff + LightSteelBlue + // #afd700 + Yellow3 + // #afd75f + DarkOliveGreen3_2 + // #afd787 + DarkSeaGreen3_1 + // #afd7af + DarkSeaGreen2 + // #afd7d7 + LightCyan3 + // #afd7ff + LightSkyBlue1 + // #afff00 + GreenYellow + // #afff5f + DarkOliveGreen2 + // #afff87 + PaleGreen1_1 + // #afffaf + DarkSeaGreen2_1 + // #afffd7 + DarkSeaGreen1 + // #afffff + PaleTurquoise1 + // #d70000 + Red3_1 + // #d7005f + DeepPink3 + // #d70087 + DeepPink3_1 + // #d700af + Magenta3_1 + // #d700d7 + Magenta3_2 + // #d700ff + Magenta2 + // #d75f00 + DarkOrange3_1 + // #d75f5f + IndianRed_1 + // #d75f87 + HotPink3_1 + // #d75faf + HotPink2 + // #d75fd7 + Orchid + // #d75fff + MediumOrchid1 + // #d78700 + Orange3 + // #d7875f + LightSalmon3_1 + // #d78787 + LightPink3 + // #d787af + Pink3 + // #d787d7 + Plum3 + // #d787ff + Violet + // #d7af00 + Gold3_1 + // #d7af5f + LightGoldenrod3 + // #d7af87 + Tan + // #d7afaf + MistyRose3 + // #d7afd7 + Thistle3 + // #d7afff + Plum2 + // #d7d700 + Yellow3_1 + // #d7d75f + Khaki3 + // #d7d787 + LightGoldenrod2 + // #d7d7af + LightYellow3 + // #d7d7d7 + Grey84 + // #d7d7ff + LightSteelBlue1 + // #d7ff00 + Yellow2 + // #d7ff5f + DarkOliveGreen1 + // #d7ff87 + DarkOliveGreen1_1 + // #d7ffaf + DarkSeaGreen1_1 + // #d7ffd7 + Honeydew2 + // #d7ffff + LightCyan1 + // #ff0000 + Red1 + // #ff005f + DeepPink2 + // #ff0087 + DeepPink1 + // #ff00af + DeepPink1_1 + // #ff00d7 + Magenta2_1 + // #ff00ff + Magenta1 + // #ff5f00 + OrangeRed1 + // #ff5f5f + IndianRed1 + // #ff5f87 + IndianRed1_1 + // #ff5faf + HotPink + // #ff5fd7 + HotPink_1 + // #ff5fff + MediumOrchid1_1 + // #ff8700 + DarkOrange + // #ff875f + Salmon1 + // #ff8787 + LightCoral + // #ff87af + PaleVioletRed1 + // #ff87d7 + Orchid2 + // #ff87ff + Orchid1 + // #ffaf00 + Orange1 + // #ffaf5f + SandyBrown + // #ffaf87 + LightSalmon1 + // #ffafaf + LightPink1 + // #ffafd7 + Pink1 + // #ffafff + Plum1 + // #ffd700 + Gold1 + // #ffd75f + LightGoldenrod2_1 + // #ffd787 + LightGoldenrod2_2 + // #ffd7af + NavajoWhite1 + // #ffd7d7 + MistyRose1 + // #ffd7ff + Thistle1 + // #ffff00 + Yellow1 + // #ffff5f + LightGoldenrod1 + // #ffff87 + Khaki1 + // #ffffaf + Wheat1 + // #ffffd7 + Cornsilk1 + // #ffffff + Grey100 + // #080808 + Grey3 + // #121212 + Grey7 + // #1c1c1c + Grey11 + // #262626 + Grey15 + // #303030 + Grey19 + // #3a3a3a + Grey23 + // #444444 + Grey27 + // #4e4e4e + Grey30 + // #585858 + Grey35 + // #626262 + Grey39 + // #6c6c6c + Grey42 + // #767676 + Grey46 + // #808080 + Grey50 + // #8a8a8a + Grey54 + // #949494 + Grey58 + // #9e9e9e + Grey62 + // #a8a8a8 + Grey66 + // #b2b2b2 + Grey70 + // #bcbcbc + Grey74 + // #c6c6c6 + Grey78 + // #d0d0d0 + Grey82 + // #dadada + Grey85 + // #e4e4e4 + Grey89 + // #eeeeee + Grey93 +) diff --git a/color/rgb.go b/color/rgb.go new file mode 100644 index 0000000..90546bd --- /dev/null +++ b/color/rgb.go @@ -0,0 +1,66 @@ +package color + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" +) + +type RGB struct { + Red int + Green int + Blue int +} + +func MustParseHex(hexColor string) *RGB { + c, err := ParseHex(hexColor) + if err != nil { + panic(err) + } + return c +} + +func ParseHex(hexColor string) (*RGB, error) { + hexColor = strings.TrimPrefix(hexColor, "#") + + // Convert to individual parts + var rh, gh, bh string + switch len(hexColor) { + case 3: + rh, gh, bh = hexColor[:1]+hexColor[:1], hexColor[1:2]+hexColor[1:2], hexColor[2:3]+hexColor[2:3] + case 6: + rh, gh, bh = hexColor[0:2], hexColor[2:4], hexColor[4:6] + default: + return nil, errors.New("invalid hex string") + } + + // Convert to bytes + rb, err := hex.DecodeString(rh) + if err != nil { + return nil, err + } + gb, err := hex.DecodeString(gh) + if err != nil { + return nil, err + } + bb, err := hex.DecodeString(bh) + if err != nil { + return nil, err + } + + return &RGB{ + Red: int(rb[0]), + Green: int(gb[0]), + Blue: int(bb[0]), + }, nil +} + +// String returns an r;g;b representation of an RGB +func (r *RGB) String() string { + return r.format() +} + +func (r *RGB) format() string { + return fmt.Sprintf("%d;%d;%d", r.Red, r.Green, r.Blue) +} diff --git a/color/rgb_test.go b/color/rgb_test.go new file mode 100644 index 0000000..0290bfc --- /dev/null +++ b/color/rgb_test.go @@ -0,0 +1,35 @@ +package color + +import "testing" + +func TestParseHex(t *testing.T) { + tt := []struct { + Name string + Hex string + RGB *RGB + Error bool + }{ + {Name: "WhiteShort", Hex: "FFF", RGB: &RGB{255, 255, 255}, Error: false}, + {Name: "WhiteLong", Hex: "FFFFFF", RGB: &RGB{255, 255, 255}, Error: false}, + {Name: "WhiteInvalid", Hex: "F", RGB: nil, Error: true}, + {Name: "InvalidHex", Hex: "ZZZ", RGB: nil, Error: true}, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + rgb, err := ParseHex(tc.Hex) + if err != nil { + if tc.Error { + return // Pass + } + t.Log(err) + t.Fail() + } + + if rgb.Red != tc.RGB.Red || rgb.Green != tc.RGB.Green || rgb.Blue != tc.RGB.Blue { + t.Logf("hex was parsed incorrectly: parsed %#v vs expected %#v", rgb, tc.RGB) + t.Fail() + } + }) + } +} diff --git a/color/true.go b/color/true.go new file mode 100644 index 0000000..3d6aa5e --- /dev/null +++ b/color/true.go @@ -0,0 +1,45 @@ +package color + +import "fmt" + +// Interface guard +var _ Color = &True{} + +const ( + trueFG = "38;2;" + trueBG = "48;2;" +) + +type True struct { + FG *RGB + BG *RGB +} + +// NewTrue returns a new Color with RGB FG and BG +func NewTrue(fg, bg *RGB) *True { + return &True{ + FG: fg, + BG: bg, + } +} + +// String returns a string representation of the sum of a Color's Attributes +func (t *True) String() string { + return fmt.Sprintf("%d", t.FG.Red+t.FG.Green+t.FG.Blue+t.BG.Red+t.BG.Green+t.BG.Blue) +} + +func (t *True) wrap(s string) string { + return t.format() + s + t.unformat() +} + +func (t *True) format() string { + return fmt.Sprintf("%s[%s%sm%s[%s%sm", escape, trueFG, t.FG.format(), escape, trueBG, t.BG.format()) +} + +func (t *True) unformat() string { + return fmt.Sprintf("%s[%dm", escape, Reset) +} + +func (t *True) Format(text string) string { + return t.wrap(text) +} diff --git a/level.go b/level.go index 314121a..baf8887 100644 --- a/level.go +++ b/level.go @@ -63,7 +63,7 @@ func (l Level) Prefix() string { } // Color returns a Level's color, defaulting to bold -func (l Level) Color() *color.Color { +func (l Level) Color() color.Color { switch l { case TRACE: return color.Trace