/

使用Go建立CLI命令:cowsay

使用Go建立CLI命令:cowsay

Cowsay 是那些你無法生活沒有的應用之一。

它基本上是根據傳遞給它的任何訊息生成ASCII圖片的一頭牛,以上的屏幕截圖中使用 fortune 生成。

不僅限於牛類,它還可以打印企鵝、麋鹿和許多其他動物。

聽起來像是一個適合移植到Go的有用應用!

我還喜歡它附帶的簡單英文許可證:

1
2
3
4
5
6
7
8
9
10
11
12
13
==============
cowsay 許可證
==============

cowsay 根據Perl的許可證進行分發:艺术许可证或GNU通用公共许可证。如果您不想為了自己去查找和閱讀這些許可證,請使用我更喜歡的部分:

(0)我是寫它的人,你不是。

(1)如果您將代碼用於其他目的,請給予應有的應有的好處。

(2)如果您有任何錯誤修復或建議,請通知我,以便我可以加以整合。

(3)如果您試圖從cowsay牟利,您就有問題。

讓我們從定義問題開始.我們想通過一個管道接受輸入,並讓我們的牛說出來.

第一個迭代從管道讀取用戶輸入並將其打印回來.沒有太複雜.

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("該命令僅針對管道工作。")
fmt.Println("用法: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])
}
}

我們缺少了牛,而且還需要將消息包裝到一個漂亮格式的對話框內.

這是我們程序的第一次迭代:

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

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

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

// tabsToSpaces 將`lines`切片中的所有制表符轉換為4個空格,以防止計算Rune時導致對齊錯誤
func tabsToSpaces(lines []string) []string {
var ret []string
for _, l := range lines {
l = strings.Replace(l, "\t", " ", -1)
ret = append(ret, l)
}
return ret
}

// calculateMaxWidth 接收一个字符串切片,返回长度最大的字符串长度
func calculateMaxWidth(lines []string) int {
w := 0
for _, l := range lines {
length := utf8.RuneCountInString(l)
if length > w {
w = length
}
}

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
}

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

if info.Mode()&os.ModeCharDevice != 0 {
fmt.Println("該命令僅針對管道工作。")
fmt.Println("用法: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()
}

讓我們現在可配置化泡泡的圖形,通過添加一個 ‘劍龍’

把上面程序的中的 printFigure 的職能改成接收圖片的名字並打印出來。添加的支持是 ‘cow’ 和 ‘stegosaurus’.

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
// 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("未知圖片")
}
}

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

play

我想我们已經達到了一個很好的地步。我只是希望能夠在系統中使用,而不是運行 go run main.go,所以我只需要輸入 go buildgo install

現在我可以花整天時間與 gololcat 和 gocowsay 來享受了。