From 1050a04a88b15b6941ac10a379d58a9d92ad7ea4 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Tue, 25 Feb 2020 21:57:44 -0600 Subject: [PATCH 1/8] Add wrapping Signed-off-by: jolheiser --- color/color.go | 38 ++++++++++++++++++++++++++++++-------- level.go | 25 ++++++++++++++++++++++++- logger.go | 11 +++++------ 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/color/color.go b/color/color.go index 27b06ea..cc83b7c 100644 --- a/color/color.go +++ b/color/color.go @@ -118,14 +118,14 @@ func (c *Color) sequence() string { } func (c *Color) wrap(s string) string { - return c.format() + s + c.unformat() + return c.format() + s + unformat() } func (c *Color) format() string { return fmt.Sprintf("%s[%sm", escape, c.sequence()) } -func (c *Color) unformat() string { +func unformat() string { return fmt.Sprintf("%s[%dm", escape, Reset) } @@ -136,6 +136,22 @@ func (c *Color) Format(text string) string { return text } +func (c *Color) Formatf(format string, v ...interface{}) string { + return c.Format(fmt.Sprintf(format, v...)) +} + +func (c *Color) Wrap(color *Color, text string) string { + return unformat() + c.Format(text) + color.format() +} + +func (c *Color) Wrapf(color *Color, format string, v ...interface{}) string { + args := make([]interface{}, len(v)) + for idx, arg := range v { + args[idx] = color.Wrap(c, fmt.Sprintf("%v", arg)) + } + return unformat() + c.Formatf(format, args...) + color.format() +} + // ParseLevel parses a string and returns a Beaver Level's Color, defaulting to Info func ParseLevel(level string) *Color { switch strings.ToUpper(level) { @@ -156,12 +172,18 @@ func ParseLevel(level string) *Color { } var ( - Trace = New(Bold, FgCyan) - Debug = New(Bold, FgBlue) - Info = New(Bold, FgGreen) - Warn = New(Bold, FgYellow) - Error = New(Bold, FgRed) - Fatal = New(Bold, BgRed) + Trace = New(FgCyan) + Tracef = New(Bold, FgCyan) + Debug = New(FgBlue) + Debugf = New(Bold, FgBlue) + Info = New(FgGreen) + Infof = New(Bold, FgGreen) + Warn = New(FgYellow) + Warnf = New(Bold, FgYellow) + Error = New(FgRed) + Errorf = New(Bold, FgRed) + Fatal = New(BgRed) + Fatalf = New(Bold, BgRed) Default = New() Time = Default Stack = Default diff --git a/level.go b/level.go index 314121a..c8a16a4 100644 --- a/level.go +++ b/level.go @@ -8,7 +8,10 @@ import ( "go.jolheiser.com/beaver/color" ) -var bold = color.New(color.Bold) +var ( + bold = color.New(color.Bold) + italic = color.New(color.Italic) +) // Level defines a Beaver logging level type Level int @@ -82,6 +85,26 @@ func (l Level) Color() *color.Color { } } +// Color returns a Level's colorf, defaulting to italic +func (l Level) Colorf() *color.Color { + switch l { + case TRACE: + return color.Tracef + case DEBUG: + return color.Debugf + case INFO: + return color.Infof + case WARN: + return color.Warnf + case ERROR: + return color.Errorf + case FATAL: + return color.Fatalf + default: + return italic + } +} + // Levels returns a list of Beaver logging levels func Levels() []Level { return []Level{TRACE, DEBUG, INFO, WARN, ERROR, FATAL} diff --git a/logger.go b/logger.go index 4e8b3e5..75fb93c 100644 --- a/logger.go +++ b/logger.go @@ -5,7 +5,6 @@ import ( "io" "os" "runtime" - "strings" "time" "go.jolheiser.com/beaver/color" @@ -27,6 +26,7 @@ type FormatOptions struct { LevelPrefix bool LevelColor bool MessageColor bool + StyleArgs bool } // New returns a new Beaver logger @@ -75,16 +75,15 @@ func (l *Logger) write(level Level, text string) { } func (l *Logger) log(level Level, v ...interface{}) { - var args = make([]string, len(v)) - for i := 0; i < len(v); i++ { - args[i] = fmt.Sprintf("%v", v[i]) - } - text := strings.Join(args, " ") + text := fmt.Sprint(v...) l.write(level, text) } func (l *Logger) logf(level Level, format string, v ...interface{}) { text := fmt.Sprintf(format, v...) + if l.Format.StyleArgs { + + } l.write(level, text) } -- 2.41.0 From 11378d6bc27360ad54823ed60a3aa937a1433c29 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 26 Feb 2020 13:54:39 -0600 Subject: [PATCH 2/8] Update colors Signed-off-by: jolheiser --- README.md | 4 +- color/README.md | 46 +++++++++++++++++ color/attribute.go | 87 ++++++++++++++++++++++++++++++++ color/color.go | 121 +++++++++++++-------------------------------- level.go | 25 +--------- logger.go | 6 +-- 6 files changed, 172 insertions(+), 117 deletions(-) create mode 100644 color/README.md create mode 100644 color/attribute.go diff --git a/README.md b/README.md index 9d495e3..daa4c77 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,6 @@ Beaver allows you to customize the colors of various parts of the message ```go // Set Trace logging to Magenta instead of Bold-Cyan color.Trace = color.New(color.FgMagenta) -``` \ No newline at end of file +``` + +[More info for the `color` package](color/README.md) \ No newline at end of file diff --git a/color/README.md b/color/README.md new file mode 100644 index 0000000..9656efd --- /dev/null +++ b/color/README.md @@ -0,0 +1,46 @@ +# Color + +Beaver comes with the `color` sub-package that can be used even without Beaver calls. + +## Formatting a string with a single attribute +```go +text := color.FgRed.Format("red") +``` + +## Formatting a string with a full color +```go +c := color.New(color.BgGreen, color.FgRed, color.Bold) +text := c.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`. + +### setup +Assuming each example is preceded by +```go +green := color.New(color.FgGreen) +red := color.New(color.FgRed) +``` + +#### pure `fmt.Println` +```go +fmt.Println(green.Format("This is a"), red.Format("color"), green.Format("test!")) +``` + +#### `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 diff --git a/color/attribute.go b/color/attribute.go new file mode 100644 index 0000000..31f3b58 --- /dev/null +++ b/color/attribute.go @@ -0,0 +1,87 @@ +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/color.go b/color/color.go index cc83b7c..04483fb 100644 --- a/color/color.go +++ b/color/color.go @@ -2,82 +2,28 @@ 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 } -// Attribute defines a single SGR Code -type Attribute int - -const escape = "\x1b" - -// 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 -) - // New returns a new Color with attrs Attributes func New(attrs ...Attribute) *Color { return &Color{Attrs: attrs} @@ -129,6 +75,7 @@ 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) @@ -136,20 +83,26 @@ func (c *Color) Format(text string) string { 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 (c *Color) Wrap(color *Color, text string) string { - return unformat() + c.Format(text) + color.format() +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 } -func (c *Color) Wrapf(color *Color, format string, v ...interface{}) string { - args := make([]interface{}, len(v)) - for idx, arg := range v { - args[idx] = color.Wrap(c, fmt.Sprintf("%v", arg)) - } - return unformat() + c.Formatf(format, args...) + color.format() +// 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) } // ParseLevel parses a string and returns a Beaver Level's Color, defaulting to Info @@ -172,18 +125,12 @@ func ParseLevel(level string) *Color { } var ( - Trace = New(FgCyan) - Tracef = New(Bold, FgCyan) - Debug = New(FgBlue) - Debugf = New(Bold, FgBlue) - Info = New(FgGreen) - Infof = New(Bold, FgGreen) - Warn = New(FgYellow) - Warnf = New(Bold, FgYellow) - Error = New(FgRed) - Errorf = New(Bold, FgRed) - Fatal = New(BgRed) - Fatalf = New(Bold, BgRed) + Trace = New(Bold, FgCyan) + Debug = New(Bold, FgBlue) + Info = New(Bold, FgGreen) + Warn = New(Bold, FgYellow) + Error = New(Bold, FgRed) + Fatal = New(Bold, BgRed) Default = New() Time = Default Stack = Default diff --git a/level.go b/level.go index c8a16a4..314121a 100644 --- a/level.go +++ b/level.go @@ -8,10 +8,7 @@ import ( "go.jolheiser.com/beaver/color" ) -var ( - bold = color.New(color.Bold) - italic = color.New(color.Italic) -) +var bold = color.New(color.Bold) // Level defines a Beaver logging level type Level int @@ -85,26 +82,6 @@ func (l Level) Color() *color.Color { } } -// Color returns a Level's colorf, defaulting to italic -func (l Level) Colorf() *color.Color { - switch l { - case TRACE: - return color.Tracef - case DEBUG: - return color.Debugf - case INFO: - return color.Infof - case WARN: - return color.Warnf - case ERROR: - return color.Errorf - case FATAL: - return color.Fatalf - default: - return italic - } -} - // Levels returns a list of Beaver logging levels func Levels() []Level { return []Level{TRACE, DEBUG, INFO, WARN, ERROR, FATAL} diff --git a/logger.go b/logger.go index 75fb93c..d586d9b 100644 --- a/logger.go +++ b/logger.go @@ -26,7 +26,6 @@ type FormatOptions struct { LevelPrefix bool LevelColor bool MessageColor bool - StyleArgs bool } // New returns a new Beaver logger @@ -41,12 +40,12 @@ func New(writer io.Writer, level Level, opts FormatOptions) *Logger { func (l *Logger) write(level Level, text string) { if l.Level <= level { - _, file, line, _ := runtime.Caller(l.skip) var message string if l.Format.TimePrefix { message += color.Time.Format(timePrefix(time.Now())) + " " } if l.Format.StackPrefix { + _, file, line, _ := runtime.Caller(l.skip) idx := len(file) - l.Format.StackLimit if l.Format.StackLimit <= 0 { idx = 0 @@ -81,9 +80,6 @@ func (l *Logger) log(level Level, v ...interface{}) { func (l *Logger) logf(level Level, format string, v ...interface{}) { text := fmt.Sprintf(format, v...) - if l.Format.StyleArgs { - - } l.write(level, text) } -- 2.41.0 From f5fdbd1d2e98b53f4ef3a555bb4cd9110254e906 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 26 Feb 2020 13:57:13 -0600 Subject: [PATCH 3/8] Simplify example Signed-off-by: jolheiser --- color/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/color/README.md b/color/README.md index 9656efd..1557d77 100644 --- a/color/README.md +++ b/color/README.md @@ -9,8 +9,7 @@ text := color.FgRed.Format("red") ## Formatting a string with a full color ```go -c := color.New(color.BgGreen, color.FgRed, color.Bold) -text := c.Format("green background, red text, and bold") +text := color.New(color.BgGreen, color.FgRed, color.Bold).Format("green background, red text, and bold") ``` ## Formatting strings with multiple colors -- 2.41.0 From 5e1722bd4357aba706501845203f1d1fb9b5d373 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Wed, 26 Feb 2020 20:01:20 +0000 Subject: [PATCH 4/8] Update colors (#2) Simplify example Update colors Add wrapping Signed-off-by: jolheiser Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/beaver/pulls/2 --- README.md | 4 +- color/README.md | 45 +++++++++++++++++++ color/attribute.go | 87 ++++++++++++++++++++++++++++++++++++ color/color.go | 107 ++++++++++++++++----------------------------- logger.go | 9 +--- 5 files changed, 175 insertions(+), 77 deletions(-) create mode 100644 color/README.md create mode 100644 color/attribute.go diff --git a/README.md b/README.md index 9d495e3..daa4c77 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,6 @@ Beaver allows you to customize the colors of various parts of the message ```go // Set Trace logging to Magenta instead of Bold-Cyan color.Trace = color.New(color.FgMagenta) -``` \ No newline at end of file +``` + +[More info for the `color` package](color/README.md) \ No newline at end of file diff --git a/color/README.md b/color/README.md new file mode 100644 index 0000000..1557d77 --- /dev/null +++ b/color/README.md @@ -0,0 +1,45 @@ +# Color + +Beaver comes with the `color` sub-package that can be used even without Beaver calls. + +## Formatting a string with a single attribute +```go +text := color.FgRed.Format("red") +``` + +## Formatting a string with a full color +```go +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`. + +### setup +Assuming each example is preceded by +```go +green := color.New(color.FgGreen) +red := color.New(color.FgRed) +``` + +#### pure `fmt.Println` +```go +fmt.Println(green.Format("This is a"), red.Format("color"), green.Format("test!")) +``` + +#### `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 diff --git a/color/attribute.go b/color/attribute.go new file mode 100644 index 0000000..31f3b58 --- /dev/null +++ b/color/attribute.go @@ -0,0 +1,87 @@ +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/color.go b/color/color.go index 27b06ea..04483fb 100644 --- a/color/color.go +++ b/color/color.go @@ -2,82 +2,28 @@ 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 } -// Attribute defines a single SGR Code -type Attribute int - -const escape = "\x1b" - -// 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 -) - // New returns a new Color with attrs Attributes func New(attrs ...Attribute) *Color { return &Color{Attrs: attrs} @@ -118,17 +64,18 @@ func (c *Color) sequence() string { } func (c *Color) wrap(s string) string { - return c.format() + s + c.unformat() + return c.format() + s + unformat() } func (c *Color) format() string { return fmt.Sprintf("%s[%sm", escape, c.sequence()) } -func (c *Color) unformat() string { +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) @@ -136,6 +83,28 @@ func (c *Color) Format(text string) string { 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) +} + // ParseLevel parses a string and returns a Beaver Level's Color, defaulting to Info func ParseLevel(level string) *Color { switch strings.ToUpper(level) { diff --git a/logger.go b/logger.go index 4e8b3e5..d586d9b 100644 --- a/logger.go +++ b/logger.go @@ -5,7 +5,6 @@ import ( "io" "os" "runtime" - "strings" "time" "go.jolheiser.com/beaver/color" @@ -41,12 +40,12 @@ func New(writer io.Writer, level Level, opts FormatOptions) *Logger { func (l *Logger) write(level Level, text string) { if l.Level <= level { - _, file, line, _ := runtime.Caller(l.skip) var message string if l.Format.TimePrefix { message += color.Time.Format(timePrefix(time.Now())) + " " } if l.Format.StackPrefix { + _, file, line, _ := runtime.Caller(l.skip) idx := len(file) - l.Format.StackLimit if l.Format.StackLimit <= 0 { idx = 0 @@ -75,11 +74,7 @@ func (l *Logger) write(level Level, text string) { } func (l *Logger) log(level Level, v ...interface{}) { - var args = make([]string, len(v)) - for i := 0; i < len(v); i++ { - args[i] = fmt.Sprintf("%v", v[i]) - } - text := strings.Join(args, " ") + text := fmt.Sprint(v...) l.write(level, text) } -- 2.41.0 From c628fd1987e4654ce497db68f0378fd3fc1c0780 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Wed, 17 Feb 2021 05:44:17 +0800 Subject: [PATCH 5/8] Add extended and true colors (#3) Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/beaver/pulls/3 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- .golangci.yml | 23 -- LICENSE | 7 + Makefile | 8 - README.md | 6 +- _examples/examples.go | 8 + color/README.md | 35 +-- 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 +- 18 files changed, 922 insertions(+), 280 deletions(-) delete mode 100644 .golangci.yml create mode 100644 LICENSE 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/.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 -- 2.41.0 From 262effd971740480b2e2a47daa39c36549fbcd0b Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Wed, 17 Feb 2021 13:40:21 +0800 Subject: [PATCH 6/8] Fix legacy code (#4) Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/beaver/pulls/4 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- color/basic.go | 6 ++++++ color/basic_colors.go | 21 +++++++++++++++++++++ color/color.go | 1 + color/extended.go | 6 ++++++ color/true.go | 6 ++++++ 5 files changed, 40 insertions(+) diff --git a/color/basic.go b/color/basic.go index 99247a1..b2b3ca4 100644 --- a/color/basic.go +++ b/color/basic.go @@ -82,9 +82,15 @@ func (b *Basic) unformat() string { return fmt.Sprintf("%s[%dm", escape, Reset) } +// Format returns a string wrapped in the basic color func (b *Basic) Format(text string) string { if len(b.Attrs) > 0 { return b.wrap(text) } return text } + +// Formatf returns a formatted string wrapped in the basic color +func (b *Basic) Formatf(format string, v ...interface{}) string { + return b.Format(fmt.Sprintf(format, v...)) +} diff --git a/color/basic_colors.go b/color/basic_colors.go index 0a9ca24..f44ebcc 100644 --- a/color/basic_colors.go +++ b/color/basic_colors.go @@ -47,3 +47,24 @@ const ( BgHiCyan BgHiWhite ) + +var attrCache = make(map[BasicAttribute]*Basic) + +func attr(a BasicAttribute) *Basic { + if c, ok := attrCache[a]; ok { + return c + } + c := New(a) + attrCache[a] = c + return c +} + +// Format is a quick way to format a string using a single attribute +func (a BasicAttribute) 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 BasicAttribute) Formatf(text string, v ...interface{}) string { + return attr(a).Formatf(text, v...) +} diff --git a/color/color.go b/color/color.go index 2cd4e02..df5d90f 100644 --- a/color/color.go +++ b/color/color.go @@ -8,6 +8,7 @@ const escape = "\x1b" type Color interface { Format(text string) string + Formatf(text string, v ...interface{}) string } // ParseLevel parses a string and returns a Beaver Level's Color, defaulting to Info diff --git a/color/extended.go b/color/extended.go index 0a08b84..a8a7798 100644 --- a/color/extended.go +++ b/color/extended.go @@ -43,6 +43,12 @@ func (e *Extended) unformat() string { return fmt.Sprintf("%s[%dm", escape, Reset) } +// Format returns a string wrapped in the extended color func (e *Extended) Format(text string) string { return e.wrap(text) } + +// Formatf returns a formatted string wrapped in the extended color +func (e *Extended) Formatf(text string, v ...interface{}) string { + return e.wrap(fmt.Sprintf(text, v...)) +} diff --git a/color/true.go b/color/true.go index 3d6aa5e..5601db1 100644 --- a/color/true.go +++ b/color/true.go @@ -40,6 +40,12 @@ func (t *True) unformat() string { return fmt.Sprintf("%s[%dm", escape, Reset) } +// Format returns a string wrapped in the true color func (t *True) Format(text string) string { return t.wrap(text) } + +// Formatf returns a formatted string wrapped in the true color +func (t *True) Formatf(text string, v ...interface{}) string { + return t.wrap(fmt.Sprintf(text, v...)) +} -- 2.41.0 From 0b1a1e4bd0f8f5a149d4313e7b0d75dbe86ee0bd Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Wed, 17 Feb 2021 13:58:17 +0800 Subject: [PATCH 7/8] Time format (#5) Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/beaver/pulls/5 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- level.go | 36 ------------------------------------ logger.go | 2 +- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/level.go b/level.go index baf8887..7b1004f 100644 --- a/level.go +++ b/level.go @@ -3,7 +3,6 @@ package beaver import ( "fmt" "strings" - "time" "go.jolheiser.com/beaver/color" ) @@ -105,38 +104,3 @@ func ParseLevel(level string) Level { } return INFO } - -func timePrefix(t time.Time) string { - var buf = &[]byte{} - year, month, day := t.Date() - itoa(buf, int(month), 2) - *buf = append(*buf, '/') - itoa(buf, day, 2) - *buf = append(*buf, '/') - itoa(buf, year, 4) - *buf = append(*buf, ' ') - - hour, min, sec := t.Clock() - itoa(buf, hour, 2) - *buf = append(*buf, ':') - itoa(buf, min, 2) - *buf = append(*buf, ':') - itoa(buf, sec, 2) - - return string(*buf) -} - -func itoa(buf *[]byte, i int, wid int) { - var b [20]byte - bp := len(b) - 1 - for i >= 10 || wid > 1 { - wid-- - q := i / 10 - b[bp] = byte('0' + i - q*10) - bp-- - i = q - } - // i < 10 - b[bp] = byte('0' + i) - *buf = append(*buf, b[bp:]...) -} diff --git a/logger.go b/logger.go index d586d9b..39c096d 100644 --- a/logger.go +++ b/logger.go @@ -42,7 +42,7 @@ func (l *Logger) write(level Level, text string) { if l.Level <= level { var message string if l.Format.TimePrefix { - message += color.Time.Format(timePrefix(time.Now())) + " " + message += color.Time.Format(time.Now().Format("01/02/2006 15:04:05")) + " " } if l.Format.StackPrefix { _, file, line, _ := runtime.Caller(l.skip) -- 2.41.0 From 8af49ac71fc3c78fcf6ecb9ab06652b45b7b3549 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Mon, 21 Jun 2021 21:47:43 +0000 Subject: [PATCH 8/8] Print to stderr by default --- console.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console.go b/console.go index ccf081e..83d3be9 100644 --- a/console.go +++ b/console.go @@ -6,7 +6,7 @@ import ( var ( Console = &Logger{ - Writer: os.Stdout, + Writer: os.Stderr, Level: INFO, Format: FormatOptions{ MessageColor: true, -- 2.41.0