前言

本文是参考Go: Building a cli command with Go: cowsay所实现的一个 toy-demo ,主要用于本人的一个留档记录。

开始实践

获取输入

我们通过管道获取其他命令程序的输出作为我们的输入,与上篇 lollcat 相似的代码,如下所示:

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
package main

import (
"bufio"
"fmt"
"io"
"os"
)

func main() {
info, _ := os.Stdin.Stat()

if info.Mode()&os.ModeCharDevice != 0 {
fmt.Println("The command is intended to work with pipes.")
fmt.Println("Usage: fortune | gocowsay")
return
}

reader := bufio.NewReader(os.Stdin)
var output []rune

for {
input, _, err := reader.ReadRune()
if err != nil && err == io.EOF {
break
}
output = append(output, input)
}

for j := 0; j < len(output); j++ {
fmt.Printf("%c", output[j])
}
}

打印 cow 与 ballroom

接下来为了打印 cow 与 ballroom ,我们添加如下函数:

  • tabsToSpaces:将输入内容中的制表符转换为四个空格
  • calculateMaxWidth:返回切片中最长的元素的长度
  • normalizeStringsLength:对齐每个字符串的长度,使长度都等于最长的元素的长度
  • buildBalloon:制作对话气泡

更新代码如下:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main

import (
"bufio"
"fmt"
"io"
"os"
"strings"
"unicode/utf8"
)

func main() {
info, _ := os.Stdin.Stat()

if info.Mode()&os.ModeCharDevice != 0 {
fmt.Println("The command is intended to work with pipes.")
fmt.Println("Usage: fortune | gocowsay")
return
}

var lines []string

reader := bufio.NewReader(os.Stdin)

for {
line, _, err := reader.ReadLine()
if err != nil && err == io.EOF {
break
}
lines = append(lines, string(line))
}

var cow = ` \ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
`

lines = tabsToSpaces(lines)
maxwidth := calculateMaxWidth(lines)
messages := normalizeStringsLength(lines, maxwidth)
balloon := buildBalloon(messages, maxwidth)
fmt.Println(balloon)
fmt.Println(cow)
fmt.Println()
}

// tabsToSpaces 函数将 lines 切片中
// 找到的所有字符串中的制表符转换为四个空格,以防止在计算字符时出现对齐错误。
func tabsToSpaces(lines []string) []string {
var ret []string
for _, l := range lines {
l = strings.Replace(l, "\t", " ", -1)
ret = append(ret, l)
}
return ret
}

// 返回切片中最长的一行的长度
func calculateMaxWidth(lines []string) int {
w := 0
for _, l := range lines {
len := utf8.RuneCountInString(l)
if len > w {
w = len
}
}

return w
}

// normalizeStringsLength 函数接受一个字符串切片,
// 然后为每个字符串添加一定数量的空格,以确保它们的字符数(rune)相同。
func normalizeStringsLength(lines []string, maxwidth int) []string {
var ret []string
for _, l := range lines {
s := l + strings.Repeat(" ", maxwidth-utf8.RuneCountInString(l))
ret = append(ret, s)
}
return ret
}

// buildBalloon 函数接受一个字符串切片,其中每个字符串的最大宽度为 maxwidth。
// 它在第一行和最后一行的开头和结尾以及每行的开头和结尾添加边距,并返回一个包含气球内容的字符串。
func buildBalloon(lines []string, maxwidth int) string {
var borders []string
count := len(lines)
var ret []string

borders = []string{"/", "\\", "\\", "/", "|", "<", ">"}

top := " " + strings.Repeat("_", maxwidth+2)
bottom := " " + strings.Repeat("-", maxwidth+2)

ret = append(ret, top)
if count == 1 {
s := fmt.Sprintf("%s %s %s", borders[5], lines[0], borders[6])
ret = append(ret, s)
} else {
s := fmt.Sprintf(`%s %s %s`, borders[0], lines[0], borders[1])
ret = append(ret, s)
i := 1
for ; i < count-1; i++ {
s = fmt.Sprintf(`%s %s %s`, borders[4], lines[i], borders[4])
ret = append(ret, s)
}
s = fmt.Sprintf(`%s %s %s`, borders[2], lines[i], borders[3])
ret = append(ret, s)
}

ret = append(ret, bottom)
return strings.Join(ret, "\n")
}

效果图:
QQ20240616-212625.png

实现图形可配置化

我们除了生成 cow ,也可以生成更多的动物图像,具体函数更新如下:

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
62
63
64
65
package main

import (
"bufio"
"flag"
"fmt"
"io"
"os"
"strings"
"unicode/utf8"
)

func main() {
// ...

var figure string
flag.StringVar(&figure, "f", "cow", "the figure name. Valid values are `cow` and `stegosaurus`")
flag.Parse()

// ...

printFigure(figure)
fmt.Println()
}

// ... 不变

// printFigure 函数接受一个图形名称作为输入,并打印出相应的图形。
// 目前,它支持打印 cow 和 stegosaurus 两种图形。
func printFigure(name string) {

var cow = ` \ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
`

var stegosaurus = ` \ . .
\ / ` + "`" + `. .' "
\ .---. < > < > .---.
\ | \ \ - ~ ~ - / / |
_____ ..-~ ~-..-~
| | \~~~\\.' ` + "`" + `./~~~/
--------- \__/ \__/
.' O \ / / \ "
(_____, ` + "`" + `._.' | } \/~~~/
` + "`" + `----. / } | / \__/
` + "`" + `-. | / | / ` + "`" + `. ,~~|
~-.__| /_ - ~ ^| /- _ ` + "`" + `..-'
| / | / ~-. ` + "`" + `-. _ _ _
|_____| |_____| ~ - . _ _ _ _ _>

`

switch name {
case "cow":
fmt.Println(cow)
case "stegosaurus":
fmt.Println(stegosaurus)
default:
fmt.Println("Unknown figure")
}
}

效果如下:
QQ20240616-213212.png
如果愿意的话,也可以自定义更多的动物图像。

小结

接下来我们只要编译就可以作为命令行工具使用啦,除此之外,也可以配合我们之前写的 lolcat 工具一起使用,效果如下:
QQ20240616-213455.png