使用go模板
原文链接:[Gopher Academy Blog][https://blog.gopheracademy.com/advent-2017/using-go-templates/] Marko Mudrinić
go模板是自定义输出的强大方法,无论你是想创建页面,发送邮件,还是与Buffalo, Go-Hugo,集成,或者仅仅是使用CLI,如kubectl.
golang中有两个包用于模板操作:text/template
和html/templates
.两个包提供了相同的接口,但html/templates
用于生成HTML输出,代码代码注入安全。
本文将速快浏览如何使用模板以及如何在你的应用中集成模板。
Actions
在学习如何实现之前,先看看template的语法。一些合适的函数以字符串或”原生字符串”的形式提供给模板。Actions代表data evaluations, functions或controll loops。
Data evaluations
通常在使用模板的时候,需要将模板绑定到其获取数据的go的数据结构上(如struct)。如果从结构体struct中获取数据,你需要使用action,这个位置会在解析的时候使用给定结构体的`FieldName`的值替换。结构体struct需要传给`Execute`函数。
还有一种
action, 使用它你可以引用一个非结构体类型的值。
Conditions
你可以在模板中使用if
条件,比如i想检查FieldName
是否非空。如果不为空,就打印它的值: Value of FieldName is .
else
和else if
还支持如下模式:
// action // action 2 .
Loops
使用range
action可以遍历一个slice, range
action格式如下:
...
如果slice的类型不是结构体,你可以使用 action来引用这个值。如果slice的类型是结构体,你可以使用 action来引用其值。
Functions, Pipelines and Variables
一些Action有一些内置函数可以结合管道来对输出进一步的解析。管道使用|
符号,默认行为是把左侧的数据发送给管道右侧的函数。
函数用于转义action的结果。有一些默认的函数如html
用于返回转义HTML后的输出,防止代码注入;js
函数返回JavaScript转义后的输出。
使用with
action,你可以定定义变量,变量的作用范围在with
语句块内有效:
.
本文通篇会包含一些复杂的action,比如从array中而不是struct中读取数据。
Parsing Templates
这里有三个最重要,使用最频繁的函数:
- New: allocates new, undefined template
- Parse: parses given template string and return parsed template
- Execute: applies parsed template to the data structure and write result to the given writer
下面的代码展示了上面提到的三种函数在action中的用法:
package main
import (
"os"
"text/template"
)
type Todo struct {
Name string
Description string
}
func main() {
td := Todo{"Test templates", "Let's test a template to see the magic."}
t, err := template.New("todos").Parse("You have a task named \"\" with description: \"\"")
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, td)
if err != nil {
panic(err)
}
}
执行上面的代码,会在终端输出如下内容:
You have a task named "Test templates" with description: "Let's test a template to see the magic."
你可以复用模板,而不需要重新创建和解析,只需要提供Execute
函数所需要的结构体数据。
// code omitted beacuse of brevity
...
tdNew := Todo{"Go", "Contribute to any Go project"}
err = t.Execute(os.Stdout, tdNew)
}
如你所见,模板提供了自定义文本输出的强大功能。除了控制文本输出,还可以通过html/templates
控制html的输出。
Verifying Templates
由于模板通常是在编译期间就固定下来的,如果模板无法解析,这将是程序一个严重的bug。template
包提供了Must
函数,用于验证在模板解析过程中是否有效。Must
函数与上面手动检查错误的结果相同。这可以节省代码输入量。当有错误是,应用会发生panic。如果需要进一步处理error,使用上面手动的方式比使用Must
函数更加容易。
Must
函数需要接受一个template和error 作为参数,通常是将New
函数作为其参数:
t := template.Must(template.New("todos").Parse("You have task named \"\" with description: \"\""))
本文会采用这个函数,而不使用显示的错误检查的方法。
Implementing Templates
这里将展示如何使用template魔法特性。首先创建一个简单的包含to-do列表的页面。
Creating Web Pages using Templates
html/tempaltes
包允许提供模板文件,如html格式的文件,这样使得前后端的实现更加容易。下面的数据结构代表一个To-Do列表,这个列表是一个stuct类型的slice,包含任务的名称和状态。
type entry struct {
Name string
Done bool
}
type ToDo struct {
User string
List []entry
}
一个简单的html页面用来展示用户的名称和它的To-Do列表。本例中将使用range
action来loop through tasks slice, 使用with
从slice中取数据,还包含一个条件检查,如果任务已经完成的话。任务完成的情况下,Yes
会被写入结构体成员,否则no
写入。
<!DOCTYPE html>
<html>
<head>
<title>Go To-Do list</title>
</head>
<body>
<p>
To-Do list for user:
</p>
<table>
<tr>
<td>Task</td>
<td>Done</td>
</tr>
<tr>
<td></td>
<td>YesNo</td>
</tr>
</table>
</body>
</html>
和前面一样,我们会解析模板并将其应用于struct的数据,这里不使用Parse
函数,而是使用ParseFile
函数。为了代码简短,我们将解析后的数据打到标准输出,而不是HTTP 的Writer接口。
package main
import (
"html/template"
"os"
)
type entry struct {
Name string
Done bool
}
type ToDo struct {
User string
List []entry
}
func main() {
// Parse data -- omitted for brevity
// Files are provided as a slice of strings.
paths := []string{
"todo.tmpl",
}
t := template.Must(template.New("html-tmpl").ParseFiles(paths...))
err = t.Execute(os.Stdout, todos)
if err != nil {
panic(err)
}
}
代码执行结果如下:
<!DOCTYPE html>
<html>
<head>
<title>Go To-Do list</title>
</head>
<body>
<p>
To-Do list for user: gopher
</p>
<table>
<tr>
<td>Task</td>
<td>Done</td>
</tr>
<tr>
<td>GopherAcademy Article</td>
<td>Yes</td>
</tr>
<tr>
<td>Merge PRs</td>
<td>No</td>
</tr>
</table>
</body>
</html>
Parsing Multiple Files
有时你有多个模板文件,或者你想动态的增减模板文件。这种场景下可以使用ParseGlob
函数,此接受glob作为参数,并解析所有匹配glob的文件:
// ...
t := template.Must(template.New("html-tmpl").ParseGlob("*.tmpl"))
err = t.Execute(os.Stdout, todos)
if err != nil {
panic(err)
}
// ...
Customizing Command’s Output
你也可以在CLI中使用模板,这样用户就可以自定义命令的输出。下面的代码片段通过两个flag:template
和template-file
来解析指定的模板。
package main
import (
"flag"
"os"
)
func main() {
// data parsing...
var template, templateFile string
flag.StringVar(&template, "template", "", "a template")
flag.StringVar(&templateFile, "template-file", "", "a template file path")
flag.Parse()
if templateFile != "" {
path := []string{templateFile}
t := template.Must(template.New("html-tmpl").ParseFiles(path...))
err = t.Execute(os.Stdout, todos)
if err != nil {
panic(err)
}
} else if template != "" {
path := []string{templateFile}
t := template.Must(template.New("html-tmpl").Parse(template))
err = t.Execute(os.Stdout, todos)
if err != nil {
panic(err)
}
} else {
// non-template data logic...
}
}
相似功能也可以通过spf13/cobra
包实现,处于篇幅的考虑,这里的代码省略的数据解析的逻辑。通过这种方式可以自定义更加直观的输出,而不需要使用sed,awk或grep等工具。