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