Colorful Printing

平时在使用一些命令行工具时,是否经常注意到彩色的 log 打出来呢,然而自己写的命令行工具永远都是清一色白底黑字(或者黑底白字)的输出,是不是会怀疑别人施了黑魔法才让输出变得五彩斑斓,其实要让命令行输出彩色字体并没有那么难,关键就在对其规则的把握。

为了让字符变色,我们需要规定其伴随的属性,一般来说属性有以下几类。

  • 基本属性,例如字体粗细,是否为斜体等等
  • 前景字体颜色
  • 前景字体颜色(强烈版)
  • 背景字体颜色
  • 背景字体颜色(强烈版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Base 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
)

让字符变色的关键在于转义字符的使用,转义字符对每个程序员来说肯定都不陌生,它的作用就是让紧随其后的字符变成其他含义,所谓转义转义就是这个意思,我们这里要使用的转义字符是 \x1b ,或者写作 \033 ,二者都是可以的,因为十六进制的 1b 和八进制的 33 相等。

下面我们定义一个 Color 结构体,里面包含一个属性数组,规定了我们的文字会变化成什么样。

1
2
3
4
5
6
7
8
9
10
11
12
13
const escape = "\x1b"

type Attribute int

type Color struct {
params []Attribute
}

func New(value ...Attribute) *Color {
c := &Color{params: make([]Attribute, 0)}
c.params = append(c.params, value...)
return c
}

例如一段文字 how are you? ,我们想要把把中间的 are 变为绿色,我们应该怎么办呢,其实就是分别在 are 的前后加上一些修饰符。

are 的前面加上 \x1b[32m 表示从这之后将后面的字变为绿色。
而在 are 的后面加上 \x1b[0m 则表示结束属性变化。

所以我们在开始变化的时候加上 fmt.Sprintf("%s[%sm", escape, c.sequence()) ,这里的 escape 就是我们之前提过的转义字符,而 c.sequence() 就是我们规定的属性。

在变化结束的时候加上 fmt.Sprintf("%s[%dm", escape, Reset) ,这里 Reset 就表示重置变化,其值为0。

注意如果有多个属性,我们使用 ; 连接就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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, ";")
}

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

func (c *Color) wrap(s string) string {
return c.format() + s + c.unformat()
}

下面我们对常见的几个函数作一层封装。

1
2
3
4
5
6
7
8
9
10
11
func (c *Color) Sprint(a ...interface{}) string {
return c.wrap(fmt.Sprint(a...))
}

func (c *Color) Sprintln(a ...interface{}) string {
return c.wrap(fmt.Sprintln(a...))
}

func (c *Color) Sprintf(format string, a ...interface{}) string {
return c.wrap(fmt.Sprintf(format, a...))
}

使用

理清了基本思路,使用它就变的非常简单了。

1
2
3
4
func main() {
c := New(Bold, FgGreen)
fmt.Println(c.Sprintln("hello world"))
}
Pieces of Valuable Programming Knowledges