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

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

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

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

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

==============
cowsay 許可證
==============

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

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

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

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

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

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

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

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

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

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

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’.

// 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 來享受了。