go语言text/template库学习


​ 笔记内容按照 中文文案排版指北 进行排版,以保证内容的可读性。
自动化排版工具 ,clone 下来之后,点击 index.html 就可以使用了

text/template 库学习

​ 话不多说,直接上流程。。我习惯直接从帮助文档里学习的,当然你也可以选择从其他地方学习,那种最有效就用那种。

库学习流程

  1. 打开 text/template 库的帮助文档 ,粗略看一下库的功能是啥。
  2. 打开 text/template 库源代码所在目录。
/text/template库源代码目录截图

寻找含有 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 首页👋👋。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值