使用 Go 內(nèi)置模板構(gòu)建豐富的 CLI 應(yīng)用程序
今天介紹如何通過 text/template 構(gòu)建富文本 CLI 程序。
01 概述
模板包 text/template[1] 實(shí)現(xiàn)了用于生成文本輸出的數(shù)據(jù)驅(qū)動(dòng)模板。雖然我們不會(huì)從多次執(zhí)行模板輸出中受益,但我們發(fā)現(xiàn)它易于使用且有助于輸出帶顏色的文本、編碼數(shù)據(jù)和呈現(xiàn)表格信息。
通過按名稱映射附加函數(shù),可以使用更多功能擴(kuò)展模板引擎。函數(shù)可以接受來自模板引擎的輸入作為參數(shù)并返回將呈現(xiàn)到輸出中的值。
映射應(yīng)該在我們調(diào)用模板Parse 函數(shù)功能之前完成 。
為了說明如何做到這一點(diǎn),讓我們看一下以下示例:
rand.Seed(time.Now().UnixNano())
tmpl?:=?template.Must(template.
?New("").
?Funcs(map[string]interface{}{
?????"rand":?func()?int?{
?????????return?rand.Intn(100)
?????},
?}).
?Parse(`Hi?{{.}},?you?are?number?{{rand}}.`))
_?=?tmpl.Execute(os.Stdout,?"User")
輸出:
Hi User, you are number 6.
在這種情況下,我們將rand 函數(shù)映射到一個(gè)返回 0 到 100(不包括)之間隨機(jī)數(shù)的 rand.Intn函數(shù)。模板引擎將接受返回值并將其字符串化為輸出。
模板可以接受的函數(shù)應(yīng)該有一個(gè)有效的名稱,可以用作模板的一部分(由字母和數(shù)字或下劃線組成,不應(yīng)以數(shù)字開頭)并返回值類型或帶 error 的值。
現(xiàn)在讓我們看一下 CLI 中的一些用例。
02 彩色輸出
為了輸出彩色文本,我們使用 go-pretty[2] 包。
使用此包的好處之一是它提供的彩色文本功能以及完全禁用/啟用顏色支持的選項(xiàng)。
讓我們將一些顏色映射到我們的模板
data?:=?struct?{
?Passed?int
?Failed?int
}{
?Passed:?1,
?Failed:?5,
}
tmpl?:=?template.Must(template.
?New("").
?Funcs(map[string]interface{}{
?????"red":?func(v?interface{})?string?{
?????????return?text.FgHiRed.Sprint(v)
?????},
?????"green":?func(v?interface{})?string?{
?????????return?text.FgHiGreen.Sprint(v)
?????},
?????"yellow":?func(v?interface{})?string?{
?????????return?text.FgHiYellow.Sprint(v)
?????},
?}).
?Parse(`{{?"Results"?|?yellow?}}
Passed:?{{?.Passed?|?green?}}
Failed:?{{?.Failed?|?red?}}
`))
_?=?tmpl.Execute(os.Stdout,?data)
使用 term[3] 包,我們可以檢測(cè)我們的輸出是否進(jìn)入終端。雖然用戶希望在終端上看到帶有顏色的輸出,但將輸出重定向或通過另一個(gè)命令進(jìn)行管道處理,會(huì)不喜歡處理產(chǎn)生的顏色轉(zhuǎn)義碼。
通過檢查終端,我們可以禁用包上的顏色go-pretty,運(yùn)行上面的代碼將產(chǎn)生相同的但只有文本輸出:
if?!term.IsTerminal(int(os.Stdout.Fd()))?{
???text.DisableColors()
}
03 數(shù)據(jù)為 JSON
一個(gè)常見的用例是需要打印出數(shù)據(jù)模型——配置、服務(wù)器響應(yīng)或其他復(fù)雜的結(jié)構(gòu)。JSON 通常用于此目的。
我們可以通過在映射中添加一個(gè) json 函數(shù)來輕松呈現(xiàn)我們的輸出:
data?:=?struct?{
?ID?int?`json:"id"`
?UpdateTime?time.Time?`json:"update_time"`
?Path?string?`json:"path,omitempty"`
}{
?ID:?1,
?UpdateTime:?time.Now(),
?Path:?"path/to/data",
}
tmpl?:=?template.Must(template.
?New("").
?Funcs(map[string]interface{}{
?????"json":?func(v?interface{})?(string,?error)?{
?????????b,?err?:=?json.MarshalIndent(v,?"",?"??")
?????????if?err?!=?nil?{return?"",?err}
?????????return?string(b),?nil
?????},
?}).
?Parse(`Record?information?{{?.?|?json?}}`))
_?=?tmpl.Execute(os.Stdout,?data)
Record?information?{
????"id":?1,
????"update_time":?"2021-10-18T21:18:25.973140953+03:00",
????"path":?"path/to/data"
}
請(qǐng)注意,這種情況下的映射函數(shù)也會(huì)返回錯(cuò)誤。如果我們的調(diào)用失敗,Execute 方法將返回錯(cuò)誤MarshalIndent 。
04 表
以表格格式處理和顯示數(shù)據(jù)很常見。我們將提供給模板的函數(shù)使用了一個(gè)通用的數(shù)據(jù)結(jié)構(gòu)來呈現(xiàn)表格。
//?type?table.Row?interface{}?-?holds?any?value
type?Table?struct?{
?Headers?table.Row
?Rows?[]table.Row
}
該 Table 結(jié)構(gòu)由標(biāo)題和行組成,用于對(duì)任何表格信息進(jìn)行建模。
標(biāo)題的數(shù)量反映了每行中的單元格數(shù)量(我們假設(shè)它是對(duì)齊的)。
就像我們對(duì)彩色輸出所做的那樣,將表格渲染到終端與將其渲染到文件不同。
根據(jù) isTerminal 的值(假設(shè)是通過 term.IsTerminal 設(shè)置的),我們會(huì)渲染出對(duì)齊的表格或者 CSV 格式,以便于處理。
tmpl?:=?template.Must(template.
?New("").
?Funcs(map[string]interface{}{
?????"table":?func(tab?*Table)?string?{
?????????w?:=?table.NewWriter()
?????????w.AppendHeader(tab.Headers)
?????????w.AppendRows(tab.Rows)
?????????if?isTerminal?{
?????????????return?w.Render()
?????????}
?????????return?w.RenderCSV()
?????},
?}).
?Parse(`{{?.?|?table?}}`))
tbl?:=?&Table{
?Headers:?table.Row{"id",?"path"},
?Rows:?[]table.Row{{1,?"file1"},?{2,?"file2"},?{3,?"file3"}},
}
_?=?tmpl.Execute(os.Stdout,?tbl)
終端輸出:
+----+-------+
|?ID?|?PATH??|
+----+-------+
|??1?|?file1?|
|??2?|?file2?|
|??3?|?file3?|
+----+-------+
非終端輸出:
id,path
1,file1
2,file2
3,file3
05 其他想法
review 代碼總會(huì)提出如何改進(jìn)代碼或使其更易于使用的想法。例如:
使用 reflect 對(duì)表格信息建模——標(biāo)簽或附加結(jié)構(gòu)來描述模型。如果不需要,將阻止轉(zhuǎn)換 Table。使用連續(xù)數(shù)據(jù)源呈現(xiàn)表格——提取信息的 API 通常依賴于分頁(yè)。使用反射來渲染數(shù)據(jù)并捕獲我們拉取數(shù)據(jù)的方式,我們可以有一個(gè)通用的方式來連續(xù)獲取和顯示數(shù)據(jù)。
原文鏈接:https://lakefs.io/building-rich-cli-applications-with-gos-built-in-templating/
參考資料
text/template: https://pkg.go.dev/text/template
[2]go-pretty: https://github.com/jedib0t/go-pretty
[3]term: https://pkg.go.dev/golang.org/x/term
我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語(yǔ)言并創(chuàng)建了 Go 語(yǔ)言中文網(wǎng)!著有《Go語(yǔ)言編程之旅》、開源圖書《Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)》等。
堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場(chǎng)心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長(zhǎng)!也歡迎加我微信好友交流:gopherstudio
