You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
18 KiB
Go
604 lines
18 KiB
Go
package starlog
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"b612.me/starlog/colorable"
|
|
"b612.me/starlog/isatty"
|
|
)
|
|
|
|
var (
|
|
// NoColor defines if the output is colorized or not. It's dynamically set to
|
|
// false or true based on the stdout's file descriptor referring to a terminal
|
|
// or not. This is a global option and affects all colors. For more control
|
|
// over each color block use the methods DisableColor() individually.
|
|
NoColor = os.Getenv("TERM") == "dumb" ||
|
|
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
|
|
|
|
// Output defines the standard output of the print functions. By default
|
|
// os.Stdout is used.
|
|
Output = colorable.NewColorableStdout()
|
|
|
|
// Error defines a color supporting writer for os.Stderr.
|
|
Errors = colorable.NewColorableStderr()
|
|
|
|
// colorsCache is used to reduce the count of created Color objects and
|
|
// allows to reuse already created objects with required Attr.
|
|
colorsCache = make(map[Attr]*Color)
|
|
colorsCacheMu sync.Mutex // protects colorsCache
|
|
)
|
|
|
|
// Color defines a custom color object which is defined by SGR parameters.
|
|
type Color struct {
|
|
params []Attr
|
|
noColor *bool
|
|
}
|
|
|
|
// Attr defines a single SGR Code
|
|
type Attr int
|
|
|
|
const escape = "\x1b"
|
|
|
|
// Base attributes
|
|
const (
|
|
Reset Attr = iota
|
|
Bold
|
|
Faint
|
|
Italic
|
|
Underline
|
|
BlinkSlow
|
|
BlinkRapid
|
|
ReverseVideo
|
|
Concealed
|
|
CrossedOut
|
|
)
|
|
|
|
// Foreground text colors
|
|
const (
|
|
FgBlack Attr = iota + 30
|
|
FgRed
|
|
FgGreen
|
|
FgYellow
|
|
FgBlue
|
|
FgMagenta
|
|
FgCyan
|
|
FgWhite
|
|
)
|
|
|
|
// Foreground Hi-Intensity text colors
|
|
const (
|
|
FgHiBlack Attr = iota + 90
|
|
FgHiRed
|
|
FgHiGreen
|
|
FgHiYellow
|
|
FgHiBlue
|
|
FgHiMagenta
|
|
FgHiCyan
|
|
FgHiWhite
|
|
)
|
|
|
|
// Background text colors
|
|
const (
|
|
BgBlack Attr = iota + 40
|
|
BgRed
|
|
BgGreen
|
|
BgYellow
|
|
BgBlue
|
|
BgMagenta
|
|
BgCyan
|
|
BgWhite
|
|
)
|
|
|
|
// Background Hi-Intensity text colors
|
|
const (
|
|
BgHiBlack Attr = iota + 100
|
|
BgHiRed
|
|
BgHiGreen
|
|
BgHiYellow
|
|
BgHiBlue
|
|
BgHiMagenta
|
|
BgHiCyan
|
|
BgHiWhite
|
|
)
|
|
|
|
// New returns a newly created color object.
|
|
func NewColor(value ...Attr) *Color {
|
|
c := &Color{params: make([]Attr, 0)}
|
|
c.Add(value...)
|
|
return c
|
|
}
|
|
|
|
// Set sets the given parameters immediately. It will change the color of
|
|
// output with the given SGR parameters until color.Unset() is called.
|
|
func Set(p ...Attr) *Color {
|
|
c := NewColor(p...)
|
|
c.Set()
|
|
return c
|
|
}
|
|
|
|
// Unset resets all escape attributes and clears the output. Usually should
|
|
// be called after Set().
|
|
func Unset() {
|
|
if NoColor {
|
|
return
|
|
}
|
|
|
|
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
|
}
|
|
|
|
// Set sets the SGR sequence.
|
|
func (c *Color) Set() *Color {
|
|
if c.isNoColorSet() {
|
|
return c
|
|
}
|
|
|
|
fmt.Fprintf(Output, c.format())
|
|
return c
|
|
}
|
|
|
|
func (c *Color) unset() {
|
|
if c.isNoColorSet() {
|
|
return
|
|
}
|
|
|
|
Unset()
|
|
}
|
|
|
|
func (c *Color) setWriter(w io.Writer) *Color {
|
|
if c.isNoColorSet() {
|
|
return c
|
|
}
|
|
|
|
fmt.Fprintf(w, c.format())
|
|
return c
|
|
}
|
|
|
|
func (c *Color) unsetWriter(w io.Writer) {
|
|
if c.isNoColorSet() {
|
|
return
|
|
}
|
|
|
|
if NoColor {
|
|
return
|
|
}
|
|
|
|
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
|
}
|
|
|
|
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
|
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
|
func (c *Color) Add(value ...Attr) *Color {
|
|
c.params = append(c.params, value...)
|
|
return c
|
|
}
|
|
|
|
func (c *Color) prepend(value Attr) {
|
|
c.params = append(c.params, 0)
|
|
copy(c.params[1:], c.params[0:])
|
|
c.params[0] = value
|
|
}
|
|
|
|
// Fprint formats using the default formats for its operands and writes to w.
|
|
// Spaces are added between operands when neither is a string.
|
|
// It returns the number of bytes written and any write error encountered.
|
|
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
|
// type *os.File.
|
|
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
c.setWriter(w)
|
|
defer c.unsetWriter(w)
|
|
|
|
return fmt.Fprint(w, a...)
|
|
}
|
|
|
|
// Print formats using the default formats for its operands and writes to
|
|
// standard output. Spaces are added between operands when neither is a
|
|
// string. It returns the number of bytes written and any write error
|
|
// encountered. This is the standard fmt.Print() method wrapped with the given
|
|
// color.
|
|
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
|
c.Set()
|
|
defer c.unset()
|
|
|
|
return fmt.Fprint(Output, a...)
|
|
}
|
|
|
|
// Fprintf formats according to a format specifier and writes to w.
|
|
// It returns the number of bytes written and any write error encountered.
|
|
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
|
// type *os.File.
|
|
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
c.setWriter(w)
|
|
defer c.unsetWriter(w)
|
|
|
|
return fmt.Fprintf(w, format, a...)
|
|
}
|
|
|
|
// Printf formats according to a format specifier and writes to standard output.
|
|
// It returns the number of bytes written and any write error encountered.
|
|
// This is the standard fmt.Printf() method wrapped with the given color.
|
|
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
|
c.Set()
|
|
defer c.unset()
|
|
|
|
return fmt.Fprintf(Output, format, a...)
|
|
}
|
|
|
|
// Fprintln formats using the default formats for its operands and writes to w.
|
|
// Spaces are always added between operands and a newline is appended.
|
|
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
|
// type *os.File.
|
|
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
c.setWriter(w)
|
|
defer c.unsetWriter(w)
|
|
|
|
return fmt.Fprintln(w, a...)
|
|
}
|
|
|
|
// Println formats using the default formats for its operands and writes to
|
|
// standard output. Spaces are always added between operands and a newline is
|
|
// appended. It returns the number of bytes written and any write error
|
|
// encountered. This is the standard fmt.Print() method wrapped with the given
|
|
// color.
|
|
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
|
c.Set()
|
|
defer c.unset()
|
|
|
|
return fmt.Fprintln(Output, a...)
|
|
}
|
|
|
|
// Sprint is just like Print, but returns a string instead of printing it.
|
|
func (c *Color) Sprint(a ...interface{}) string {
|
|
return c.wrap(fmt.Sprint(a...))
|
|
}
|
|
|
|
// Sprintln is just like Println, but returns a string instead of printing it.
|
|
func (c *Color) Sprintln(a ...interface{}) string {
|
|
return c.wrap(fmt.Sprintln(a...))
|
|
}
|
|
|
|
// Sprintf is just like Printf, but returns a string instead of printing it.
|
|
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
|
return c.wrap(fmt.Sprintf(format, a...))
|
|
}
|
|
|
|
// FprintFunc returns a new function that prints the passed arguments as
|
|
// colorized with color.Fprint().
|
|
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
|
return func(w io.Writer, a ...interface{}) {
|
|
c.Fprint(w, a...)
|
|
}
|
|
}
|
|
|
|
// PrintFunc returns a new function that prints the passed arguments as
|
|
// colorized with color.Print().
|
|
func (c *Color) PrintFunc() func(a ...interface{}) {
|
|
return func(a ...interface{}) {
|
|
c.Print(a...)
|
|
}
|
|
}
|
|
|
|
// FprintfFunc returns a new function that prints the passed arguments as
|
|
// colorized with color.Fprintf().
|
|
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
|
return func(w io.Writer, format string, a ...interface{}) {
|
|
c.Fprintf(w, format, a...)
|
|
}
|
|
}
|
|
|
|
// PrintfFunc returns a new function that prints the passed arguments as
|
|
// colorized with color.Printf().
|
|
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
|
return func(format string, a ...interface{}) {
|
|
c.Printf(format, a...)
|
|
}
|
|
}
|
|
|
|
// FprintlnFunc returns a new function that prints the passed arguments as
|
|
// colorized with color.Fprintln().
|
|
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
|
return func(w io.Writer, a ...interface{}) {
|
|
c.Fprintln(w, a...)
|
|
}
|
|
}
|
|
|
|
// PrintlnFunc returns a new function that prints the passed arguments as
|
|
// colorized with color.Println().
|
|
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
|
return func(a ...interface{}) {
|
|
c.Println(a...)
|
|
}
|
|
}
|
|
|
|
// SprintFunc returns a new function that returns colorized strings for the
|
|
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
|
// string. Windows users should use this in conjunction with color.Output, example:
|
|
//
|
|
// put := New(FgYellow).SprintFunc()
|
|
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
|
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
|
return func(a ...interface{}) string {
|
|
return c.wrap(fmt.Sprint(a...))
|
|
}
|
|
}
|
|
|
|
// SprintfFunc returns a new function that returns colorized strings for the
|
|
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
|
// string. Windows users should use this in conjunction with color.Output.
|
|
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
|
return func(format string, a ...interface{}) string {
|
|
return c.wrap(fmt.Sprintf(format, a...))
|
|
}
|
|
}
|
|
|
|
// SprintlnFunc returns a new function that returns colorized strings for the
|
|
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
|
// string. Windows users should use this in conjunction with color.Output.
|
|
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
|
return func(a ...interface{}) string {
|
|
return c.wrap(fmt.Sprintln(a...))
|
|
}
|
|
}
|
|
|
|
// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
|
|
// an example output might be: "1;36" -> bold cyan
|
|
func (c *Color) sequence() string {
|
|
format := make([]string, len(c.params))
|
|
for i, v := range c.params {
|
|
format[i] = strconv.Itoa(int(v))
|
|
}
|
|
|
|
return strings.Join(format, ";")
|
|
}
|
|
|
|
// wrap wraps the s string with the colors attributes. The string is ready to
|
|
// be printed.
|
|
func (c *Color) wrap(s string) string {
|
|
if c.isNoColorSet() {
|
|
return s
|
|
}
|
|
|
|
return c.format() + s + c.unformat()
|
|
}
|
|
|
|
func (c *Color) format() string {
|
|
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
|
}
|
|
|
|
func (c *Color) unformat() string {
|
|
return fmt.Sprintf("%s[%dm", escape, Reset)
|
|
}
|
|
|
|
// DisableColor disables the color output. Useful to not change any existing
|
|
// code and still being able to output. Can be used for flags like
|
|
// "--no-color". To enable back use EnableColor() method.
|
|
func (c *Color) DisableColor() {
|
|
c.noColor = boolPtr(true)
|
|
}
|
|
|
|
// EnableColor enables the color output. Use it in conjunction with
|
|
// DisableColor(). Otherwise this method has no side effects.
|
|
func (c *Color) EnableColor() {
|
|
c.noColor = boolPtr(false)
|
|
}
|
|
|
|
func (c *Color) isNoColorSet() bool {
|
|
// check first if we have user setted action
|
|
if c.noColor != nil {
|
|
return *c.noColor
|
|
}
|
|
|
|
// if not return the global option, which is disabled by default
|
|
return NoColor
|
|
}
|
|
|
|
// Equals returns a boolean value indicating whether two colors are equal.
|
|
func (c *Color) Equals(c2 *Color) bool {
|
|
if len(c.params) != len(c2.params) {
|
|
return false
|
|
}
|
|
|
|
for _, attr := range c.params {
|
|
if !c2.attrExists(attr) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (c *Color) attrExists(a Attr) bool {
|
|
for _, attr := range c.params {
|
|
if attr == a {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func boolPtr(v bool) *bool {
|
|
return &v
|
|
}
|
|
|
|
func getCachedColor(p Attr) *Color {
|
|
colorsCacheMu.Lock()
|
|
defer colorsCacheMu.Unlock()
|
|
|
|
c, ok := colorsCache[p]
|
|
if !ok {
|
|
c = NewColor(p)
|
|
colorsCache[p] = c
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
func colorPrint(format string, p Attr, a ...interface{}) {
|
|
c := getCachedColor(p)
|
|
|
|
if !strings.HasSuffix(format, "\n") {
|
|
format += "\n"
|
|
}
|
|
|
|
if len(a) == 0 {
|
|
c.Print(format)
|
|
} else {
|
|
c.Printf(format, a...)
|
|
}
|
|
}
|
|
|
|
func colorString(format string, p Attr, a ...interface{}) string {
|
|
c := getCachedColor(p)
|
|
|
|
if len(a) == 0 {
|
|
return c.SprintFunc()(format)
|
|
}
|
|
|
|
return c.SprintfFunc()(format, a...)
|
|
}
|
|
|
|
// Black is a convenient helper function to print with black foreground. A
|
|
// newline is appended to format by default.
|
|
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
|
|
|
// Red is a convenient helper function to print with red foreground. A
|
|
// newline is appended to format by default.
|
|
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
|
|
|
// Green is a convenient helper function to print with green foreground. A
|
|
// newline is appended to format by default.
|
|
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
|
|
|
// Yellow is a convenient helper function to print with yellow foreground.
|
|
// A newline is appended to format by default.
|
|
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
|
|
|
// Blue is a convenient helper function to print with blue foreground. A
|
|
// newline is appended to format by default.
|
|
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
|
|
|
// Magenta is a convenient helper function to print with magenta foreground.
|
|
// A newline is appended to format by default.
|
|
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
|
|
|
// Cyan is a convenient helper function to print with cyan foreground. A
|
|
// newline is appended to format by default.
|
|
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
|
|
|
// White is a convenient helper function to print with white foreground. A
|
|
// newline is appended to format by default.
|
|
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
|
|
|
// BlackString is a convenient helper function to return a string with black
|
|
// foreground.
|
|
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
|
|
|
// RedString is a convenient helper function to return a string with red
|
|
// foreground.
|
|
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
|
|
|
// GreenString is a convenient helper function to return a string with green
|
|
// foreground.
|
|
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
|
|
|
// YellowString is a convenient helper function to return a string with yellow
|
|
// foreground.
|
|
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
|
|
|
// BlueString is a convenient helper function to return a string with blue
|
|
// foreground.
|
|
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
|
|
|
// MagentaString is a convenient helper function to return a string with magenta
|
|
// foreground.
|
|
func MagentaString(format string, a ...interface{}) string {
|
|
return colorString(format, FgMagenta, a...)
|
|
}
|
|
|
|
// CyanString is a convenient helper function to return a string with cyan
|
|
// foreground.
|
|
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
|
|
|
// WhiteString is a convenient helper function to return a string with white
|
|
// foreground.
|
|
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
|
|
|
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
|
|
// newline is appended to format by default.
|
|
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
|
|
|
|
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
|
|
// newline is appended to format by default.
|
|
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
|
|
|
|
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
|
|
// newline is appended to format by default.
|
|
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
|
|
|
|
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
|
|
// A newline is appended to format by default.
|
|
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
|
|
|
|
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
|
|
// newline is appended to format by default.
|
|
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
|
|
|
|
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
|
|
// A newline is appended to format by default.
|
|
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
|
|
|
|
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
|
|
// newline is appended to format by default.
|
|
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
|
|
|
|
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
|
|
// newline is appended to format by default.
|
|
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
|
|
|
|
// HiBlackString is a convenient helper function to return a string with hi-intensity black
|
|
// foreground.
|
|
func HiBlackString(format string, a ...interface{}) string {
|
|
return colorString(format, FgHiBlack, a...)
|
|
}
|
|
|
|
// HiRedString is a convenient helper function to return a string with hi-intensity red
|
|
// foreground.
|
|
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
|
|
|
|
// HiGreenString is a convenient helper function to return a string with hi-intensity green
|
|
// foreground.
|
|
func HiGreenString(format string, a ...interface{}) string {
|
|
return colorString(format, FgHiGreen, a...)
|
|
}
|
|
|
|
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
|
|
// foreground.
|
|
func HiYellowString(format string, a ...interface{}) string {
|
|
return colorString(format, FgHiYellow, a...)
|
|
}
|
|
|
|
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
|
|
// foreground.
|
|
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
|
|
|
|
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
|
|
// foreground.
|
|
func HiMagentaString(format string, a ...interface{}) string {
|
|
return colorString(format, FgHiMagenta, a...)
|
|
}
|
|
|
|
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
|
|
// foreground.
|
|
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
|
|
|
|
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
|
|
// foreground.
|
|
func HiWhiteString(format string, a ...interface{}) string {
|
|
return colorString(format, FgHiWhite, a...)
|
|
}
|