Add extended and true colors (#3)

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/beaver/pulls/3
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
pull/4/head v1.1.0
John Olheiser 2021-02-17 05:44:17 +08:00
parent 5e1722bd43
commit c628fd1987
18 changed files with 922 additions and 280 deletions

View File

@ -1,23 +0,0 @@
linters:
enable:
- deadcode
- dogsled
- dupl
- errcheck
- funlen
- gocognit
- goconst
- gocritic
- gocyclo
- gofmt
- golint
- gosimple
- govet
- misspell
- prealloc
- staticcheck
- structcheck
- typecheck
- unparam
- unused
- varcheck

7
LICENSE 100644
View File

@ -0,0 +1,7 @@
Copyright 2020 John Olheiser
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,13 +1,5 @@
GO ?= go
.PHONY: lint
lint:
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
export BINARY="golangci-lint"; \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.1; \
fi
golangci-lint run --timeout 5m
.PHONY: fmt
fmt:
$(GO) fmt ./...

View File

@ -50,4 +50,8 @@ Beaver allows you to customize the colors of various parts of the message
color.Trace = color.New(color.FgMagenta)
```
[More info for the `color` package](color/README.md)
[More info for the `color` package](color/README.md)
## LICENSE
[MIT](LICENSE)

View File

@ -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"))
}

View File

@ -2,6 +2,9 @@
Beaver comes with the `color` sub-package that can be used even without Beaver calls.
`Color` is an interface that simply needs `Format(text string) string` to fulfill it.
Any logger in beaver can be set to a `Color`.
## Formatting a string with a single attribute
```go
text := color.FgRed.Format("red")
@ -12,34 +15,10 @@ text := color.FgRed.Format("red")
text := color.New(color.BgGreen, color.FgRed, color.Bold).Format("green background, red text, and bold")
```
## Formatting strings with multiple colors
The following are different ways to print `This is a color test!`
where the word `color` is red while everything else is `green`.
## Extended colors
### setup
Assuming each example is preceded by
```go
green := color.New(color.FgGreen)
red := color.New(color.FgRed)
```
Extended colors are 256-color extensions. They can be referred to by name, available in [extended_colors.go](extended_colors.go).
#### pure `fmt.Println`
```go
fmt.Println(green.Format("This is a"), red.Format("color"), green.Format("test!"))
```
## True colors
#### `color.Wrap`
```go
fmt.Println(green.Wrap("This is a #{color} test!", red))
```
#### string formatting with `fmt.Printf` and `color.Wrap`
```go
fmt.Printf(green.Wrap("This is a #{%s} test!\n", red), "color")
```
#### using a different directive prefix
```go
color.DirectivePrefix('$')
fmt.Printf(green.Wrap("This is a ${%s} test!\n", red), "color")
```
True colors are full RBG colors, they can be either created with RGB values or parsed from hex.

View File

@ -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...)
}

90
color/basic.go 100644
View File

@ -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
}

View File

@ -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
)

View File

@ -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)
}
})
}
}

View File

@ -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

View File

@ -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},

48
color/extended.go 100644
View File

@ -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)
}

View File

@ -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
)

66
color/rgb.go 100644
View File

@ -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)
}

35
color/rgb_test.go 100644
View File

@ -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()
}
})
}
}

45
color/true.go 100644
View File

@ -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)
}

View File

@ -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