文章目录
笔记内容按照 中文文案排版指北 进行排版,以保证内容的可读性。
自动化排版工具 ,clone 下来之后,点击 index.html 就可以使用了
text/template 库学习
话不多说,直接上流程。。我习惯直接从帮助文档里学习的,当然你也可以选择从其他地方学习,那种最有效就用那种。
库学习流程
- 打开 text/template 库的帮助文档 ,粗略看一下库的功能是啥。
- 打开 text/template 库源代码所在目录。
![/text/template库源代码目录截图](https://i-blog.csdnimg.cn/blog_migrate/85661394ede60df2abed7cb7ba24a52f.png#pic_center)
寻找含有 example 和_test 的示例文件,发现有三个文件,分别是 example_test.go、examplefiles_test.go、examplefunc_test.go
3.结合文档,测试三个文件的功能,基本上就够用了。
库基本功能
粗略看了一下文档,大致意思就是将一组文字按照特定格式动态嵌入另一组文字中,有点抽象,直接运行一个简单案例看看。。
tmpl1, _ := template.New("test1").Parse("hello {{.}}")
tmpl1.Execute(os.Stdout, "world")
fmt.Println()
data := []string{"a", "b", "c"}
tmpl2, _ := template.New("test2").Parse("{{.}}")
tmpl2.Execute(os.Stdout, data)
fmt.Println()
type Inventory struct {
Material string
Count uint
}
sweaters := Inventory{"wool", 17}
tmpl3, _ := template.New("test3").Parse("{{.Count}} items are made of {{.Material}}")
tmpl3.Execute(os.Stdout, sweaters)
fmt.Println()
tmpl4, _ := template.New("test4").Parse("{{.key1}} + {{.key2}}")
tmpl4.Execute(os.Stdout, map[string]int{"key1": 111, "key2": 222})
output:
hello world
[a b c]
17 items are made of wool
111 + 222
看上去很简单,总结下来就是
- 结构体一般用.属性名,map 一般用.key,其他类型一般用.
- {{}} 里面要进行更改或功能变换,{{}} 外面的原封不动保留下来
有一定概念之后,试试三个 example*_test*.go 文件。
example_test.go
ExampleTemplate 函数
func ExampleTemplate() {
// Define a template.
const letter = `
Dear {{.Name}},
{{if .Attended}}
It was a pleasure to see you at the wedding.
{{- else}}
It is a shame you couldn't make it to the wedding.
{{- end}}
{{with .Gift -}}
Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
`
type Recipient struct {
Name, Gift string
Attended bool
}
var recipients = []Recipient{
{"Aunt Mildred", "bone china tea set", true},
{"Uncle John", "moleskin pants", false},
{"Cousin Rodney", "", false},
}
// Create a new template and parse the letter into it.
t := template.Must(template.New("letter").Parse(letter))
// Execute the template for each recipient.
for _, r := range recipients {
err := t.Execute(os.Stdout, r)
if err != nil {
log.Println("executing template:", err)
}
}
}
分析:
ExampleTemplate 函数有以下几个内容 if,else,with,还有就是 {{}} 里面的-号是啥,大致疑惑就这些,然后看看文档,actions 里面有 if,else,with 的介绍。
if else 好理解,if 传进来的变量=true,这里就选择 It was a pleasure to see you at the wedding.这一行,反之就选择 It is a shame you couldn’t make it to the wedding.这一行。
with pipeline,这里的管道是不是空,就看有没有传进来内容,有就建立了管道,然后将传进来的内容传给后面 {{.}} 里;没有内容就为空,跳过后面的语句。
{{}} 里面的-号,看了一下文档,就是一种格式,原文大致内容:
为了帮助格式化模板源代码,如果操作的左分隔符(默认情况下为“{{”)后面紧跟着减号和空格,则所有尾随空白字符将从紧邻的前面文本中修剪掉。同样,如果右分隔符(“}}”)前面有空格和减号,则紧随其后的文本中的所有前导空白字符都会被修剪掉。
对于此修剪,空白字符的定义与 Go 中相同:空格、水平制表符、回车符和换行符。
即
[空白字符 ]{{- .}}--->{{.}}
{{. -}}[空白字符 ]--->{{.}}
------------------------处理前----------------------
直接分析列子 (\n 显示加上)
\n
Dear {{.Name}},\n
{{if .Attended}}\n
It was a pleasure to see you at the wedding.\n
{{- else}}\n
It is a shame you couldn't make it to the wedding.\n
{{- end}}\n
{{with .Gift -}}\n
Thank you for the lovely {{.}}.\n
{{end}}\n
Best wishes,\n
Josie\n
------------------------处理后----------------------
\n
Dear {{.Name}},\n
{{if .Attended}}\n
It was a pleasure to see you at the wedding.{{else}}\n
It is a shame you couldn't make it to the wedding.{{end}}\n
{{with .Gift}}Thank you for the lovely {{.}}.\n
{{end}}\n
Best wishes,\n
Josie\n
伪代码形式:
t 模板:template.Must(template.New("letter").Parse(letter))
==> 等价于以下函数
func (letter){
print("\nDear ")
print(letter.Name+",\n")
if letter.Attended{
print("\nIt was a pleasure to see you at the wedding.")
}else{
print("\nIt is a shame you couldn't make it to the wedding.")
}
print("\n")
if letter.Gift{
print("Thank you for the lovely "+letter.Gift+".\n")
}==>{{with .Gift -}} 等价于这个 if 判断
print("\nBest wishes,\n")
print("Josie\n")
}
--------output:---------
Dear Aunt Mildred,
It was a pleasure to see you at the wedding.
Thank you for the lovely bone china tea set.
Best wishes,
Josie
Dear Uncle John,
It is a shame you couldn't make it to the wedding.
Thank you for the lovely moleskin pants.
Best wishes,
Josie
Dear Cousin Rodney,
It is a shame you couldn't make it to the wedding.
Best wishes,
Josie
可以看到输出完全符合伪代码描述。。
ExampleTemplate_block 函数
func ExampleTemplate_block() {
const (
master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
overlay = `{{define "list"}} {{join . ", "}}{{end}} `
)
var (
funcs = template.FuncMap{"join": strings.Join}
guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
)
masterTmpl, _ := template.New("master").Funcs(funcs).Parse(master)
if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
log.Fatal(err)
}
overlayTmpl, _ := template.Must(masterTmpl.Clone()).Parse(overlay)
if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
log.Fatal(err)
}
}
分析:
block 块,相当于 {},名字叫 list,.是指上下文 (即传进来的变量),block “list” .是指将上下文传到 block list 中,不传的话,block 块中访问不到。
define 定义块或者模板。
join 是 strings.Join 函数,template.New(“master”).Funcs(funcs).Parse(master),这里先注册了 join 函数,在解析的,不注册,join 无法识别,会报错。
伪代码形式:
masterTmpl 模板==> 等价于以下函数
func (guardians){
print("Names:")
{
print(""\n")
for v:=guardians
{
println("-",v)
}
}==> 这对 {} 名叫 list
}
--------output:---------
Names:
- Gamora
- Groot
- Nebula
- Rocket
- Star-Lord
template.Must(masterTmpl.Clone()) --> 克隆一份模板,在检查是否模板是否正确,正确,返回模板,失败,panic。。 说实话,用处不大,直接用 masterTmpl 功能也一样。
即
overlayTmpl, _ := masterTmpl.Parse(overlay)
overlayTmpl2, _ := template.Must(masterTmpl.Clone()).Parse(overlay)
overlayTmpl,overlayTmpl2 等价
故
overlayTmpl=template.New(“master”).Funcs(funcs).Parse(master).Parse(overlay)
这里重新定义了 list 块,换掉得到 overlayTmpl,故
overlayTmpl 模板==> 等价于以下函数
func (guardians){
print("Names:")
{
print([空格 ]) ==> 注意 {{define "list"}} {{join . ", "}} 括号中间有个空格
strings.Join(guardians,", ")
}--> 这对 {} 名叫 list
}
--------output:---------
Names: Gamora, Groot, Nebula, Rocket, Star-Lord
examplefunc_test.go
ExampleTemplate_func 函数
这里改了一点点,功能没变
func ExampleTemplate_func() {
funcMap := template.FuncMap{
"func1": strings.Title,
}
const templateText = `
Input: {{printf "%q" .}}
Output 0: {{func1 .}}
Output 1: {{func1 . | printf "%q"}}
Output 2: {{printf "%q" . | func1}}
`
tmpl, _ := template.New("titleTest").Funcs(funcMap).Parse(templateText)
tmpl.Execute(os.Stdout, "the go programming language")
}
这里就是自定义函数的用法,需要注意在 parse 前 Funcs 注册一下就行了
tmpl 模板==> 等价于以下函数
这里用 s 表示传进来的字符串"the go programming language"
func (s){
print("\n Input: ")
printf("%q",s) -->%q 会加""
print("\nOutput 0: ")
print(strings.Title(s))
print("\nOutput 1: ")
printf("%q",strings.Title(s))
print("\nOutput 2: ")
print(strings.Title(printf("%q",s)))
print("\n")
}
--------output:---------
Input: "the go programming language"
Output 0: The Go Programming Language
Output 1: "The Go Programming Language"
Output 2: "The Go Programming Language"
可以自己多注册几个函数试试,功能还是很好用的。。
examplefiles_test.go
ExampleTemplate_glob 函数
createTestDir(filename,content)==> 创建文件,文件内容为第二个参数
func ExampleTemplate_glob() {
dir := createTestDir([]templateFile{
{"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
defer os.RemoveAll(dir)
pattern := filepath.Join(dir, "*.tmpl")
tmpl := template.Must(template.ParseGlob(pattern))
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
log.Fatalf("template execution: %s", err)
}
}
一个模板就相当于一个函数
func T0(){
print("T0 invokes T1: ")
T1()
}
func T1(){
print("T1 invokes T2: ")
T2()
}
func T2(){
print("This is T2")
}
--------output:---------
T0 invokes T1: (T1 invokes T2: (This is T2))
ExampleTemplate_helpers 函数
func ExampleTemplate_helpers() {
dir := createTestDir([]templateFile{
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
defer os.RemoveAll(dir)
pattern := filepath.Join(dir, "*.tmpl")
templates := template.Must(template.ParseGlob(pattern))
_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
if err != nil {
log.Fatal("parsing driver1: ", err)
}
_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
if err != nil {
log.Fatal("parsing driver2: ", err)
}
err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
if err != nil {
log.Fatalf("driver1 execution: %s", err)
}
err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
if err != nil {
log.Fatalf("driver2 execution: %s", err)
}
}
分析
func T1(){
print("T1 invokes T2: ")
T1()
}
func T2(){
print("This is T2")
}
templates ==> 定义了两个函数
func drive1(){
print("Driver 1 calls T1:")
T1()
print("\n")
}
func driver2(){
print("Driver 2 calls T2:")
T2()
print("\n")
}
--------output:---------
Driver 1 calls T1: (T1 invokes T2: (This is T2))
Driver 2 calls T2: (This is T2)
ExampleTemplate_share 函数
精简了一下,不然太长了
func ExampleTemplate_share() {
dir := createTestDir([]templateFile{
{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
})
defer os.RemoveAll(dir)
pattern := filepath.Join(dir, "*.tmpl")
drivers := template.Must(template.ParseGlob(pattern))
first, _ := drivers.Clone()
_, _ = first.Parse("{{define `T2`}}T2, version A{{end}}")
second, _ := drivers.Clone()
_, _ = second.Parse("{{define `T2`}}T2, version B{{end}}")
second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
}
分析:
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
ExecuteTemplate 方法使用名为 name 的 t 关联的模板产生输出,
即使用 name 模板产生输出,但是 name 模板中有一个相关联的外部调用,用 t
func T0(arg1){
print("T0 (")
print(arg1)
print(" version) invokes T1: (")
T1()
print(")\n")
}
func T1(){
print("T1 invokes T2: ")
T2()
}
func first(){
define T2(){
print("T2, version A")
}
}
func second(){
define T2(){
print("T2, version B")
}
}
--------output:---------
T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
对于 HTML 格式的输出,参见 html/template 包,功能和 text/template 包差不多。
总结
总体而言,text/template 库可以借助伪代码形式帮助我们理解。而且 text/template 库功能还是很强大的,可以用来自动化生成文本,网页以及 markdown 模板。下篇博客讲讲 template 库的一些用法,打造一个属于自己的 github 首页👋👋。