428 lines
10 KiB
Go
428 lines
10 KiB
Go
package color
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// output colored text like use html tag. (not support windows cmd)
|
|
const (
|
|
// MatchExpr regex to match color tags
|
|
//
|
|
// Notice: golang 不支持反向引用. 即不支持使用 \1 引用第一个匹配 ([a-z=;]+)
|
|
// MatchExpr = `<([a-z=;]+)>(.*?)<\/\1>`
|
|
// 所以调整一下 统一使用 `</>` 来结束标签,例如 "<info>some text</>"
|
|
//
|
|
// allow custom attrs, eg: "<fg=white;bg=blue;op=bold>content</>"
|
|
// (?s:...) s - 让 "." 匹配换行
|
|
MatchExpr = `<([0-9a-zA-Z_=,;]+)>(?s:(.*?))<\/>`
|
|
|
|
// AttrExpr regex to match custom color attributes
|
|
// eg: "<fg=white;bg=blue;op=bold>content</>"
|
|
AttrExpr = `(fg|bg|op)[\s]*=[\s]*([0-9a-zA-Z,]+);?`
|
|
|
|
// StripExpr regex used for removing color tags
|
|
// StripExpr = `<[\/]?[a-zA-Z=;]+>`
|
|
// 随着上面的做一些调整
|
|
StripExpr = `<[\/]?[0-9a-zA-Z_=,;]*>`
|
|
)
|
|
|
|
var (
|
|
attrRegex = regexp.MustCompile(AttrExpr)
|
|
matchRegex = regexp.MustCompile(MatchExpr)
|
|
stripRegex = regexp.MustCompile(StripExpr)
|
|
)
|
|
|
|
/*************************************************************
|
|
* internal defined color tags
|
|
*************************************************************/
|
|
|
|
// There are internal defined color tags
|
|
// Usage: <tag>content text</>
|
|
// @notice 加 0 在前面是为了防止之前的影响到现在的设置
|
|
var colorTags = map[string]string{
|
|
// basic tags
|
|
"red": "0;31",
|
|
"red1": "1;31", // with bold
|
|
"redB": "1;31",
|
|
"red_b": "1;31",
|
|
"blue": "0;34",
|
|
"blue1": "1;34", // with bold
|
|
"blueB": "1;34",
|
|
"blue_b": "1;34",
|
|
"cyan": "0;36",
|
|
"cyan1": "1;36", // with bold
|
|
"cyanB": "1;36",
|
|
"cyan_b": "1;36",
|
|
"green": "0;32",
|
|
"green1": "1;32", // with bold
|
|
"greenB": "1;32",
|
|
"green_b": "1;32",
|
|
"black": "0;30",
|
|
"white": "1;37",
|
|
"default": "0;39", // no color
|
|
"normal": "0;39", // no color
|
|
"brown": "0;33", // #A52A2A
|
|
"yellow": "0;33",
|
|
"ylw0": "0;33",
|
|
"yellowB": "1;33", // with bold
|
|
"ylw1": "1;33",
|
|
"ylwB": "1;33",
|
|
"magenta": "0;35",
|
|
"mga": "0;35", // short name
|
|
"magentaB": "1;35", // with bold
|
|
"mgb": "1;35",
|
|
"mgaB": "1;35",
|
|
|
|
// light/hi tags
|
|
|
|
"gray": "0;90",
|
|
"darkGray": "0;90",
|
|
"dark_gray": "0;90",
|
|
"lightYellow": "0;93",
|
|
"light_yellow": "0;93",
|
|
"hiYellow": "0;93",
|
|
"hi_yellow": "0;93",
|
|
"hiYellowB": "1;93", // with bold
|
|
"hi_yellow_b": "1;93",
|
|
"lightMagenta": "0;95",
|
|
"light_magenta": "0;95",
|
|
"hiMagenta": "0;95",
|
|
"hi_magenta": "0;95",
|
|
"lightMagentaB": "1;95", // with bold
|
|
"hiMagentaB": "1;95", // with bold
|
|
"hi_magenta_b": "1;95",
|
|
"lightRed": "0;91",
|
|
"light_red": "0;91",
|
|
"hiRed": "0;91",
|
|
"hi_red": "0;91",
|
|
"lightRedB": "1;91", // with bold
|
|
"light_red_b": "1;91",
|
|
"hi_red_b": "1;91",
|
|
"lightGreen": "0;92",
|
|
"light_green": "0;92",
|
|
"hiGreen": "0;92",
|
|
"hi_green": "0;92",
|
|
"lightGreenB": "1;92",
|
|
"light_green_b": "1;92",
|
|
"hi_green_b": "1;92",
|
|
"lightBlue": "0;94",
|
|
"light_blue": "0;94",
|
|
"hiBlue": "0;94",
|
|
"hi_blue": "0;94",
|
|
"lightBlueB": "1;94",
|
|
"light_blue_b": "1;94",
|
|
"hi_blue_b": "1;94",
|
|
"lightCyan": "0;96",
|
|
"light_cyan": "0;96",
|
|
"hiCyan": "0;96",
|
|
"hi_cyan": "0;96",
|
|
"lightCyanB": "1;96",
|
|
"light_cyan_b": "1;96",
|
|
"hi_cyan_b": "1;96",
|
|
"lightWhite": "0;97;40",
|
|
"light_white": "0;97;40",
|
|
|
|
// option
|
|
"bold": "1",
|
|
"b": "1",
|
|
"underscore": "4",
|
|
"us": "4", // short name for 'underscore'
|
|
"reverse": "7",
|
|
|
|
// alert tags, like bootstrap's alert
|
|
"suc": "1;32", // same "green" and "bold"
|
|
"success": "1;32",
|
|
"info": "0;32", // same "green",
|
|
"comment": "0;33", // same "brown"
|
|
"note": "36;1",
|
|
"notice": "36;4",
|
|
"warn": "0;1;33",
|
|
"warning": "0;30;43",
|
|
"primary": "0;34",
|
|
"danger": "1;31", // same "red" but add bold
|
|
"err": "97;41",
|
|
"error": "97;41", // fg light white; bg red
|
|
}
|
|
|
|
/*************************************************************
|
|
* parse color tags
|
|
*************************************************************/
|
|
|
|
var (
|
|
tagParser = TagParser{}
|
|
rxNumStr = regexp.MustCompile("^[0-9]{1,3}$")
|
|
rxHexCode = regexp.MustCompile("^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
|
|
)
|
|
|
|
// TagParser struct
|
|
type TagParser struct {
|
|
disable bool
|
|
}
|
|
|
|
// NewTagParser create
|
|
func NewTagParser() *TagParser {
|
|
return &TagParser{}
|
|
}
|
|
|
|
// func (tp *TagParser) Disable() *TagParser {
|
|
// tp.disable = true
|
|
// return tp
|
|
// }
|
|
|
|
// ParseByEnv parse given string. will check package setting.
|
|
func (tp *TagParser) ParseByEnv(str string) string {
|
|
// disable handler TAG
|
|
if !RenderTag {
|
|
return str
|
|
}
|
|
|
|
// disable OR not support color
|
|
if !Enable || !SupportColor() {
|
|
return ClearTag(str)
|
|
}
|
|
|
|
return tp.Parse(str)
|
|
}
|
|
|
|
// Parse parse given string, replace color tag and return rendered string
|
|
func (tp *TagParser) Parse(str string) string {
|
|
// not contains color tag
|
|
if !strings.Contains(str, "</>") {
|
|
return str
|
|
}
|
|
|
|
// find color tags by regex. str eg: "<fg=white;bg=blue;op=bold>content</>"
|
|
matched := matchRegex.FindAllStringSubmatch(str, -1)
|
|
|
|
// item: 0 full text 1 tag name 2 tag content
|
|
for _, item := range matched {
|
|
full, tag, content := item[0], item[1], item[2]
|
|
|
|
// use defined tag name: "<info>content</>" -> tag: "info"
|
|
if !strings.ContainsRune(tag, '=') {
|
|
code := colorTags[tag]
|
|
if len(code) > 0 {
|
|
now := RenderString(code, content)
|
|
// old := WrapTag(content, tag) is equals to var 'full'
|
|
str = strings.Replace(str, full, now, 1)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// custom color in tag
|
|
// - basic: "fg=white;bg=blue;op=bold"
|
|
if code := ParseCodeFromAttr(tag); len(code) > 0 {
|
|
now := RenderString(code, content)
|
|
str = strings.Replace(str, full, now, 1)
|
|
}
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
// func (tp *TagParser) ParseAttr(attr string) (code string) {
|
|
// return
|
|
// }
|
|
|
|
// ReplaceTag parse string, replace color tag and return rendered string
|
|
func ReplaceTag(str string) string {
|
|
return tagParser.ParseByEnv(str)
|
|
}
|
|
|
|
// ParseCodeFromAttr parse color attributes.
|
|
//
|
|
// attr format:
|
|
// // VALUE please see var: FgColors, BgColors, AllOptions
|
|
// "fg=VALUE;bg=VALUE;op=VALUE"
|
|
// 16 color:
|
|
// "fg=yellow"
|
|
// "bg=red"
|
|
// "op=bold,underscore" option is allow multi value
|
|
// "fg=white;bg=blue;op=bold"
|
|
// "fg=white;op=bold,underscore"
|
|
// 256 color:
|
|
// "fg=167"
|
|
// "fg=167;bg=23"
|
|
// "fg=167;bg=23;op=bold"
|
|
// true color:
|
|
// // hex
|
|
// "fg=fc1cac"
|
|
// "fg=fc1cac;bg=c2c3c4"
|
|
// // r,g,b
|
|
// "fg=23,45,214"
|
|
// "fg=23,45,214;bg=109,99,88"
|
|
func ParseCodeFromAttr(attr string) (code string) {
|
|
if !strings.ContainsRune(attr, '=') {
|
|
return
|
|
}
|
|
|
|
attr = strings.Trim(attr, ";=,")
|
|
if len(attr) == 0 {
|
|
return
|
|
}
|
|
|
|
var codes []string
|
|
matched := attrRegex.FindAllStringSubmatch(attr, -1)
|
|
|
|
for _, item := range matched {
|
|
pos, val := item[1], item[2]
|
|
switch pos {
|
|
case "fg":
|
|
if c, ok := FgColors[val]; ok { // basic
|
|
codes = append(codes, c.String())
|
|
} else if c, ok := ExFgColors[val]; ok { // extra
|
|
codes = append(codes, c.String())
|
|
} else if code := rgbHex256toCode(val, false); code != "" {
|
|
codes = append(codes, code)
|
|
}
|
|
case "bg":
|
|
if c, ok := BgColors[val]; ok { // basic bg
|
|
codes = append(codes, c.String())
|
|
} else if c, ok := ExBgColors[val]; ok { // extra bg
|
|
codes = append(codes, c.String())
|
|
} else if code := rgbHex256toCode(val, true); code != "" {
|
|
codes = append(codes, code)
|
|
}
|
|
case "op": // options allow multi value
|
|
if strings.Contains(val, ",") {
|
|
ns := strings.Split(val, ",")
|
|
for _, n := range ns {
|
|
if c, ok := AllOptions[n]; ok {
|
|
codes = append(codes, c.String())
|
|
}
|
|
}
|
|
} else if c, ok := AllOptions[val]; ok {
|
|
codes = append(codes, c.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
return strings.Join(codes, ";")
|
|
}
|
|
|
|
func rgbHex256toCode(val string, isBg bool) (code string) {
|
|
if len(val) == 6 && rxHexCode.MatchString(val) { // hex: "fc1cac"
|
|
code = HEX(val, isBg).String()
|
|
} else if strings.ContainsRune(val, ',') { // rgb: "231,178,161"
|
|
code = strings.Replace(val, ",", ";", -1)
|
|
if isBg {
|
|
code = BgRGBPfx + code
|
|
} else {
|
|
code = FgRGBPfx + code
|
|
}
|
|
} else if len(val) < 4 && rxNumStr.MatchString(val) { // 256 code
|
|
if isBg {
|
|
code = Bg256Pfx + val
|
|
} else {
|
|
code = Fg256Pfx + val
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// ClearTag clear all tag for a string
|
|
func ClearTag(s string) string {
|
|
if !strings.Contains(s, "</>") {
|
|
return s
|
|
}
|
|
|
|
return stripRegex.ReplaceAllString(s, "")
|
|
}
|
|
|
|
/*************************************************************
|
|
* helper methods
|
|
*************************************************************/
|
|
|
|
// GetTagCode get color code by tag name
|
|
func GetTagCode(name string) string {
|
|
return colorTags[name]
|
|
}
|
|
|
|
// ApplyTag for messages
|
|
func ApplyTag(tag string, a ...interface{}) string {
|
|
return RenderCode(GetTagCode(tag), a...)
|
|
}
|
|
|
|
// WrapTag wrap a tag for a string "<tag>content</>"
|
|
func WrapTag(s string, tag string) string {
|
|
if s == "" || tag == "" {
|
|
return s
|
|
}
|
|
|
|
return fmt.Sprintf("<%s>%s</>", tag, s)
|
|
}
|
|
|
|
// GetColorTags get all internal color tags
|
|
func GetColorTags() map[string]string {
|
|
return colorTags
|
|
}
|
|
|
|
// IsDefinedTag is defined tag name
|
|
func IsDefinedTag(name string) bool {
|
|
_, ok := colorTags[name]
|
|
return ok
|
|
}
|
|
|
|
/*************************************************************
|
|
* Tag extra
|
|
*************************************************************/
|
|
|
|
// Tag value is a defined style name
|
|
// Usage:
|
|
// Tag("info").Println("message")
|
|
type Tag string
|
|
|
|
// Print messages
|
|
func (tg Tag) Print(a ...interface{}) {
|
|
name := string(tg)
|
|
str := fmt.Sprint(a...)
|
|
|
|
if stl := GetStyle(name); !stl.IsEmpty() {
|
|
stl.Print(str)
|
|
} else {
|
|
doPrintV2(GetTagCode(name), str)
|
|
}
|
|
}
|
|
|
|
// Printf format and print messages
|
|
func (tg Tag) Printf(format string, a ...interface{}) {
|
|
name := string(tg)
|
|
str := fmt.Sprintf(format, a...)
|
|
|
|
if stl := GetStyle(name); !stl.IsEmpty() {
|
|
stl.Print(str)
|
|
} else {
|
|
doPrintV2(GetTagCode(name), str)
|
|
}
|
|
}
|
|
|
|
// Println messages line
|
|
func (tg Tag) Println(a ...interface{}) {
|
|
name := string(tg)
|
|
if stl := GetStyle(name); !stl.IsEmpty() {
|
|
stl.Println(a...)
|
|
} else {
|
|
doPrintlnV2(GetTagCode(name), a)
|
|
}
|
|
}
|
|
|
|
// Sprint render messages
|
|
func (tg Tag) Sprint(a ...interface{}) string {
|
|
name := string(tg)
|
|
// if stl := GetStyle(name); !stl.IsEmpty() {
|
|
// return stl.Render(args...)
|
|
// }
|
|
|
|
return RenderCode(GetTagCode(name), a...)
|
|
}
|
|
|
|
// Sprintf format and render messages
|
|
func (tg Tag) Sprintf(format string, a ...interface{}) string {
|
|
tag := string(tg)
|
|
str := fmt.Sprintf(format, a...)
|
|
|
|
return RenderString(GetTagCode(tag), str)
|
|
}
|