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