base code | coloring format v1 (foreground and/or background) | only _real_ issue for now, text cannot contain the character '$' due to regexp mechanism
This commit is contained in:
commit
2b9e25fa39
|
@ -0,0 +1,82 @@
|
|||
package clifmt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type terminalColor uint32
|
||||
|
||||
var colorMap = make(map[string]terminalColor)
|
||||
|
||||
func init() {
|
||||
colorMap["red"] = 0xff0000
|
||||
colorMap["green"] = 0x00ff00
|
||||
colorMap["blue"] = 0x0000ff
|
||||
}
|
||||
|
||||
// fromName returns the integer value of a color name
|
||||
// from the built-in color map ; it is case insensitive
|
||||
func fromName(s string) (terminalColor, error) {
|
||||
value, ok := colorMap[s]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unknown color name '%'", s)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// fromHex returns the integer value associated with
|
||||
// an hexadecimal string (full-sized or short version)
|
||||
// the format is 'abc' or 'abcdef'
|
||||
func fromHex(s string) (terminalColor, error) {
|
||||
if len(s) != 3 && len(s) != 6 {
|
||||
return 0, fmt.Errorf("expect a size of 3 or 6 (remove the '#' prefix)")
|
||||
}
|
||||
|
||||
// short version
|
||||
input := s
|
||||
if len(s) == 3 {
|
||||
input = fmt.Sprintf("%c%c%c%c%c%c", s[0], s[0], s[1], s[1], s[2], s[2])
|
||||
}
|
||||
|
||||
n, err := strconv.ParseUint(input, 16, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return terminalColor(n), nil
|
||||
}
|
||||
|
||||
// parseColor tries to parse a color string (can be a name or an hexa value)
|
||||
func parseColor(s string) (terminalColor, error) {
|
||||
|
||||
// (0) ...
|
||||
if len(s) < 1 {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// (1) hexa
|
||||
if s[0] == '#' {
|
||||
return fromHex(s[1:])
|
||||
}
|
||||
|
||||
// (2) name
|
||||
return fromName(s)
|
||||
|
||||
}
|
||||
|
||||
// Red component of the color
|
||||
func (c terminalColor) Red() uint8 {
|
||||
return uint8((c >> 16) & 0xff)
|
||||
}
|
||||
|
||||
// Green component of the color
|
||||
func (c terminalColor) Green() uint8 {
|
||||
return uint8((c >> 8) & 0xff)
|
||||
}
|
||||
|
||||
// Blue component of the color
|
||||
func (c terminalColor) Blue() uint8 {
|
||||
return uint8(c & 0xff)
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package clifmt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// extractor helps extract features from the coloring format defined as follows :
|
||||
//
|
||||
// - [Color] -> [a-z] # named color
|
||||
// - [Color] -> #[0-9a-f]{3} # hexa color (shortcode)
|
||||
// - [Color] -> #[0-9a-f]{6} # hexa color (full-sized)
|
||||
// - [Text] -> ANY
|
||||
// - [Format] -> ${Text}(Color:Color) # foreground, background colors
|
||||
// - [Format] -> ${Text}(Color) # foreground color only
|
||||
// - [Format] -> ${Text}(:Color) # background color only
|
||||
var extractor = regexp.MustCompile(`(?m)\${([^$]+)}\(((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6})))?(?:\:((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6}))))?\)`)
|
||||
|
||||
func Printf(format string, a ...interface{}) error {
|
||||
// 1. Pre-process format with 'fmt'
|
||||
input := fmt.Sprintf(format, a...)
|
||||
output := ""
|
||||
cursor := int(0)
|
||||
|
||||
// 2. extract color format matches
|
||||
for _, match := range extractor.FindAllStringSubmatchIndex(input, -1) {
|
||||
// (1) add gap between input start OR previous match
|
||||
output += input[cursor:match[0]]
|
||||
cursor = match[1]
|
||||
|
||||
// (2) extract features
|
||||
var (
|
||||
err = error(nil)
|
||||
text = ""
|
||||
sForeground = ""
|
||||
sBackground = ""
|
||||
foreground = terminalColor(0)
|
||||
background = terminalColor(0)
|
||||
)
|
||||
|
||||
if match[3]-match[2] > 0 {
|
||||
text = input[match[2]:match[3]]
|
||||
}
|
||||
if match[5]-match[4] > 0 {
|
||||
sForeground = input[match[4]:match[5]]
|
||||
foreground, err = parseColor(sForeground)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if match[7]-match[6] > 0 {
|
||||
sBackground = input[match[6]:match[7]]
|
||||
background, err = parseColor(sBackground)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// (3) replace text with colorized text
|
||||
if len(sForeground) > 0 {
|
||||
text = colorize(text, true, foreground)
|
||||
}
|
||||
if len(sBackground) > 0 {
|
||||
text = colorize(text, false, background)
|
||||
}
|
||||
output += text
|
||||
}
|
||||
|
||||
// 3. Add end of input
|
||||
if cursor < len(input)-1 {
|
||||
output += input[cursor:]
|
||||
}
|
||||
|
||||
// 3. print final output
|
||||
fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
func colorize(text string, foregound bool, color terminalColor) string {
|
||||
if foregound {
|
||||
return fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", color.Red(), color.Green(), color.Blue(), text)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("\033[48;2;%d;%d;%dm%s\033[0m", color.Red(), color.Green(), color.Blue(), text)
|
||||
}
|
Loading…
Reference in New Issue