diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 6d71439..0000000 --- a/.golangci.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0f02641 --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/Makefile b/Makefile index 888096f..2f88598 100644 --- a/Makefile +++ b/Makefile @@ -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 ./... diff --git a/README.md b/README.md index daa4c77..d2d613e 100644 --- a/README.md +++ b/README.md @@ -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) \ No newline at end of file +[More info for the `color` package](color/README.md) + +## LICENSE + +[MIT](LICENSE) \ No newline at end of file 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/README.md b/color/README.md index 1557d77..d304518 100644 --- a/color/README.md +++ b/color/README.md @@ -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") -``` \ No newline at end of file +True colors are full RBG colors, they can be either created with RGB values or parsed from hex. \ No newline at end of file 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