【Golang】模板包:text/template

在阅读gin的context之前,我觉得还是需要先了解下模板包template。因为context上下文之前也看过了,有response和request结构体,而响应html的话肯定是会做前后端分离的模板的。

最初我并不想看模板包,因为个人认为都用上go了,前端应该和go没啥关系了,如果需要后端输出的话为什么不用开发更快的php呢?不过既然到了相关的地方了,还是需要先了解下 template 模板包的。

本篇内容来自标准库文档的阅读:https://studygolang.com/static/pkgdoc/pkg/text_template.htm

package template

import "text/template"

template包实现了数据驱动的用于生成文本输出的模板。

如果要生成HTML格式的输出,参见html/template包,该包提供了和本包相同的接口,但会自动将输出转化为安全的HTML格式输出,可以抵抗一些网络攻击。

通过将模板应用于一个数据结构(即该数据结构作为模板的参数)来执行,来获得输出。模板中的注释引用数据接口的元素(一般如结构体的字段或者字典的键)来控制执行过程和获取需要呈现的值。模板执行时会遍历结构并将指针表示为'.'(称之为"dot")指向运行过程中数据结构的当前位置的值。

用作模板的输入文本必须是utf-8编码的文本。"Action"—数据运算和控制单位—由"{{"和"}}"界定;在Action之外的所有文本都不做修改的拷贝到输出中。Action内部不能有换行,但注释可以有换行。

经解析生成模板后,一个模板可以安全的并发执行。

  1. 渲染使用 text/template 包和 html/template
  2. 其他模板引擎一样,也是传入一段数据,根据数据渲染模板
  3. 模板中的数据是引用传递
  4. 值用 . 表示,这代表一个指针
  5. 用作模板的输入文本必须是utf-8编码的文本
  6. 模板边界为 {{}} 回忆一下smarty引擎,边界似乎差不多,是 {}
  7. 模板可以安全的并发执行,也就是说底层实现了协程处理模板了

下面是一个简单的例子,可以打印"17 of wool"。

type Inventory struct {
	Material string
	Count    uint
}
sweaters := Inventory{"wool", 17}
tmpl, err := template.New("test").Parse("{{.Count}} of {{.Material}}")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, sweaters)
if err != nil { panic(err) }

更复杂的例子在下面。

  1. 官方文档给了个小demo
  2. 使用 template.New("test").Parse("{{.Count}} of {{.Material}}") 创建了一个模板
  3. tmpl.Execute(os.Stdout, sweaters) 向模板内传参并解析输出到标准输出

Actions

下面是一个action(动作)的列表。 "Arguments"和"pipelines"代表数据的执行结果,细节定义在后面。

{{/* a comment */}}
    注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止,就像这里表示的一样。
{{pipeline}}
    pipeline的值的默认文本表示会被拷贝到输出里。
{{if pipeline}} T1 {{end}}
    如果pipeline的值为empty,不产生输出,否则输出T1执行结果。不改变dot的值。
    Empty值包括false、0、任意nil指针或者nil接口,任意长度为0的数组、切片、字典。
{{if pipeline}} T1 {{else}} T0 {{end}}
    如果pipeline的值为empty,输出T0执行结果,否则输出T1执行结果。不改变dot的值。
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
    用于简化if-else链条,else action可以直接包含另一个if;等价于:
        {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
{{range pipeline}} T1 {{end}}
    pipeline的值必须是数组、切片、字典或者通道。
    如果pipeline的值其长度为0,不会有任何输出;
    否则dot依次设为数组、切片、字典或者通道的每一个成员元素并执行T1;
    如果pipeline的值为字典,且键可排序的基本类型,元素也会按键的顺序排序。
{{range pipeline}} T1 {{else}} T0 {{end}}
    pipeline的值必须是数组、切片、字典或者通道。
    如果pipeline的值其长度为0,不改变dot的值并执行T0;否则会修改dot并执行T1。
{{template "name"}}
    执行名为name的模板,提供给模板的参数为nil,如模板不存在输出为""
{{template "name" pipeline}}
    执行名为name的模板,提供给模板的参数为pipeline的值。
{{with pipeline}} T1 {{end}}
    如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。
{{with pipeline}} T1 {{else}} T0 {{end}}
    如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。
  1. {{}} 会直接输出文本
  2. {{if pipeline}} T1 {{end}} 用于判断
  3. {{if pipeline}} T1 {{else}} T0 {{end}} if-else
  4. {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} if-elseif
  5. 也可以嵌套:{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
  6. {{range pipeline}} T1 {{end}} 遍历
  7. {{range pipeline}} T1 {{else}} T0 {{end}} 当遍历对象长度为0的时候会执行else
  8. {{template "name"}} 调用子模板,模板嵌套
  9. {{template "name" pipeline}} 模板嵌套并传递数据
  10. {{with pipeline}} T1 {{end}} 如果数据不为empty就执行T1,并将数据传入
  11. {{with pipeline}} T1 {{else}} T0 {{end}} 同样的,只是加了个else,如果为empty直接执行t0

Arguments

参数代表一个简单的,由下面的某一条表示的值:

- go语法的布尔值、字符串、字符、整数、浮点数、虚数、复数,视为无类型字面常数,字符串不能跨行
- 关键字nil,代表一个go的无类型的nil值
- 字符'.'(句点,用时不加单引号),代表dot的值
- 变量名,以美元符号起始加上(可为空的)字母和数字构成的字符串,如:$piOver2和$;
  执行结果为变量的值,变量参见下面的介绍
- 结构体数据的字段名,以句点起始,如:.Field;
  执行结果为字段的值,支持链式调用:.Field1.Field2;
  字段也可以在变量上使用(包括链式调用):$x.Field1.Field2;
- 字典类型数据的键名;以句点起始,如:.Key;
  执行结果是该键在字典中对应的成员元素的值;
  键也可以和字段配合做链式调用,深度不限:.Field1.Key1.Field2.Key2;
  虽然键也必须是字母和数字构成的标识字符串,但不需要以大写字母起始;
  键也可以用于变量(包括链式调用):$x.key1.key2;
- 数据的无参数方法名,以句点为起始,如:.Method;
  执行结果为dot调用该方法的返回值,dot.Method();
  该方法必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
  如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
  方法可和字段、键配合做链式调用,深度不限:.Field1.Key1.Method1.Field2.Key2.Method2;
  方法也可以在变量上使用(包括链式调用):$x.Method1.Field;
- 无参数的函数名,如:fun;
  执行结果是调用该函数的返回值fun();对返回值的要求和方法一样;函数和函数名细节参见后面。
- 上面某一条的实例加上括弧(用于分组)
  执行结果可以访问其字段或者键对应的值:
      print (.F1 arg1) (.F2 arg2)
      (.StructValuedMethod "arg").Field
  1. 如果没有理解错的话,模板内自定义变量使用 $ 开头 (PHPER狂喜)
  2. 变量访问成员或键使用 . 连接
  3. 方法或者函数必须要有 1-2 个返回值,且如果有2个,第二个返回值必须是 error 接口,如果第二个返回值为非 nilerror ,模板会中断并报错
  4. 可以使用 () 来进行分组,如 method 传入字符串返回一个结构体,访问结构体内的成员 field:(.method "str").field
  5. 虽然键也必须是字母和数字构成的标识字符串,但不需要以大写字母起始(可以访问任意包?)

Arguments可以是任何类型:如果是指针,在必要时会自动表示为指针指向的值;如果执行结果生成了一个函数类型的值,如结构体的函数类型字段,该函数不会自动调用,但可以在if等action里视为真。如果要调用它,使用call函数,参见下面。

Pipeline是一个(可能是链状的)command序列。Command可以是一个简单值(argument)或者对函数或者方法的(可以有多个参数的)调用:

Argument
    执行结果是argument的执行结果
.Method [Argument...]
    方法可以独立调用或者位于链式调用的末端,不同于链式调用中间的方法,可以使用参数;
    执行结果是使用给出的参数调用该方法的返回值:dot.Method(Argument1, etc.);
functionName [Argument...]
    执行结果是使用给定的参数调用函数名指定的函数的返回值:function(Argument1, etc.);

Pipelines

pipeline通常是将一个command序列分割开,再使用管道符'|'连接起来(但不使用管道符的command序列也可以视为一个管道)。在一个链式的pipeline里,每个command的结果都作为下一个command的最后一个参数。pipeline最后一个command的输出作为整个管道执行的结果。

command的输出可以是1到2个值,如果是2个后一个必须是error接口类型。如果error类型返回值非nil,模板执行会中止并将该错误返回给执行模板的调用者。

  1. 没理解错的话,go的模板的管道符是用来链式调用的,.m1|.m2|.m3 中,m1的返回值会传入m2作为最后一个参数,m2的返回值会传入m3作为最后一个参数,后面待验证

Variables

Action里可以初始化一个变量来捕获管道的执行结果。初始化语法如下: $variable := pipeline 其中$variable是变量的名字。声明变量的action不会产生任何输出。

如果"range" action初始化了1个变量,该变量设置为迭代器的每一个成员元素,如果初始化了逗号分隔的2个变量: range $index, $element := pipeline 这时,$index和$element分别设置为数组/切片的索引或者字典的键,以及对应的成员元素。注意这和go range从句只有一个参数时设置为索引/键不同!

一个变量的作用域只到声明它的控制结构("if"、"with"、"range")的"end"为止,如果不是在控制结构里声明会直到模板结束为止。子模板的调用不会从调用它的位置(作用域)继承变量。

模板开始执行时,$会设置为传递给Execute方法的参数,就是说,dot的初始值。

  1. 模板内设置变量用 $ 开头(PHPER再次狂喜)
  2. 使用range遍历变量和go语言原生不同,可以只用一个变量接受,只用一个变量接受的时候会只接受成员(就是说 range $, $v := map 等价于 range $v := map) 后面再验证
  3. 模板内变量作用域为流程控制体内
  4. Execute方法的参数为模板内dot的初始值(上面demo已经演示)

Examples

下面是一些单行模板,展示了pipeline和变量。所有都生成加引号的单词"output":

{{"\"output\""}}
	字符串常量
{{`"output"`}}
	原始字符串常量
{{printf "%q" "output"}}
	函数调用
{{"output" | printf "%q"}}
	函数调用,最后一个参数来自前一个command的返回值
{{printf "%q" (print "out" "put")}}
	加括号的参数
{{"put" | printf "%s%s" "out" | printf "%q"}}
	玩出花的管道的链式调用
{{"output" | printf "%s" | printf "%q"}}
	管道的链式调用
{{with "output"}}{{printf "%q" .}}{{end}}
	使用dot的with action
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
	创建并使用变量的with action
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
	将变量使用在另一个action的with action
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
	以管道形式将变量使用在另一个action的with action  
  1. 一些demo,还是比较好理解的

Functions

执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。

  1. 使用 Funcs() 方法向模板内添加函数

预定义的全局函数如下:

and
    函数返回它的第一个empty参数或者最后一个参数;
    就是说"and x y"等价于"if x then y else x";所有参数都会执行;
or
    返回第一个非empty参数或者最后一个参数;
    亦即"or x y"等价于"if x then x else y";所有参数都会执行;
not
    返回它的单个参数的布尔值的否定
len
    返回它的参数的整数类型长度
index
    执行结果为第一个参数以剩下的参数为索引/键指向的值;
    如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
print
    即fmt.Sprint
printf
    即fmt.Sprintf
println
    即fmt.Sprintln
html
    返回其参数文本表示的HTML逸码等价表示。
urlquery
    返回其参数文本表示的可嵌入URL查询的逸码等价表示。
js
    返回其参数文本表示的JavaScript逸码等价表示。
call
    执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
    如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
    其中Y是函数类型的字段或者字典的值,或者其他类似情况;
    call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
    该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
    如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
布尔函数会将任何类型的零值视为假,其余视为真。
  1. and"and x y"等价于"if x then y else x" (竟然不是&&)
  2. or"or x y"等价于"if x then x else y" (竟然不是||)
  3. index"index x 1 2 3"返回x[1][2][3]的值
  4. print = fmt.Sprint,printf = fmt.Sprintf,println = fmt.Sprintln
  5. 看到上面的example我刚才还在奇怪,命名是print而不是sprint,竟然可以使用,原来是简写了sprint。。
  6. html:返回其参数文本表示的HTML逸码等价表示。
  7. urlquery:返回其参数文本表示的可嵌入URL查询的逸码等价表示。
  8. js:返回其参数文本表示的JavaScript逸码等价表示。
  9. call:用来调用函数
  10. 如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
  11. 结合上面的call的文档:执行结果生成了一个函数类型的值,如结构体的函数类型字段,该函数不会自动调用,但可以在if等action里视为真。如果要调用它,使用call函数,参见下面。
  12. 也就是说一个方法返回一个函数类型的变量可以使用call来调用

下面是定义为函数的二元比较运算的集合:

eq      如果arg1 == arg2则返回真
ne      如果arg1 != arg2则返回真
lt      如果arg1 < arg2则返回真
le      如果arg1 <= arg2则返回真
gt      如果arg1 > arg2则返回真
ge      如果arg1 >= arg2则返回真

为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:

arg1==arg2 || arg1==arg3 || arg1==arg4 ...

(和go的||不一样,不做惰性运算,所有参数都会执行)

比较函数只适用于基本类型(或重定义的基本类型,如"type Celsius float32")。它们实现了go语言规则的值的比较,但具体的类型和大小会忽略掉,因此任意类型有符号整数值都可以互相比较;任意类型无符号整数值都可以互相比较;等等。但是,整数和浮点数不能互相比较。

  1. 没有说到 && 能用吗,测试的时候试一试

Associated templates

每一个模板在创建时都要用一个字符串命名。同时,每一个模板都会和0或多个模板关联,并可以使用它们的名字调用这些模板;这种关联可以传递,并形成一个模板的名字空间。

一个模板可以通过模板调用实例化另一个模板;参见上面的"template" action。name必须是包含模板调用的模板相关联的模板的名字。

  1. 模板别名和嵌套

Nested template definitions

当解析模板时,可以定义另一个模板,该模板会和当前解析的模板相关联。模板必须定义在当前模板的最顶层,就像go程序里的全局变量一样。

这种定义模板的语法是将每一个子模板的声明放在"define"和"end" action内部。

define action使用给出的字符串常数给模板命名,举例如下:

`{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}`

它定义了两个模板T1和T2,第三个模板T3在执行时调用这两个模板;最后该模板调用了T3。输出结果是:ONE TWO

采用这种方法,一个模板只能从属于一个关联。如果需要让一个模板可以被多个关联查找到;模板定义必须多次解析以创建不同的*Template 值,或者必须使用Clone或AddParseTree方法进行拷贝。

可能需要多次调用Parse函数以集合多个相关的模板;参见ParseFiles和ParseGlob函数和方法,它们提供了简便的途径去解析保存在文件中的存在关联的模板。

一个模板可以直接调用或者通过ExecuteTemplate方法调用指定名字的相关联的模板;我们可以这样调用模板:

err := tmpl.Execute(os.Stdout, "no data needed")
if err != nil {
	log.Fatalf("execution failed: %s", err)
}

或显式的指定模板的名字来调用:

err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
if err != nil {
	log.Fatalf("execution failed: %s", err)
}
  1. {{define "T1"}} 定义模板别名
  2. {{template "T3"}} 引用指定模板
  3. tmpl.ExecuteTemplate 通过别名执行模板

type FuncMap

type FuncMap map[string]interface{}

FuncMap类型定义了函数名字符串到函数的映射,每个函数都必须有1到2个返回值,如果有2个则后一个必须是error接口类型;如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用者该错误。

  1. 使用 func 方法传入的 funcmap结构体为一个切片

type Template

type Template struct {
    *parse.Tree
    // 内含隐藏或非导出字段
}

代表一个解析好的模板,*parse.Tree字段仅仅是暴露给html/template包使用的,因此其他人应该视字段未导出。

demo请前往文档查看,强烈建议查看文档的demo

  1. func Must(t *Template, err error) *Template Must函数用于包装返回(*Template, error)的函数/方法调用,它会在err非nil时panic,一般用于变量初始化: var t = template.Must(template.New("name").Parse("text"))
  1. func New(name string) *Template 创建一个名为name的模板。
  1. func ParseFiles(filenames ...string) (*Template, error) ParseFiles函数创建一个模板并解析filenames指定的文件里的模板定义。返回的模板的名字是第一个文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要提供一个文件。如果发生错误,会停止解析并返回nil。
  1. func ParseGlob(pattern string) (*Template, error) ParseGlob创建一个模板并解析匹配pattern的文件(参见glob规则)里的模板定义。返回的模板的名字是第一个匹配的文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要存在一个匹配的文件。如果发生错误,会停止解析并返回nil。ParseGlob等价于使用匹配pattern的文件的列表为参数调用ParseFiles。
  1. func (t *Template) Name() string 返回模板t的名字。
  1. func (t *Template) Delims(left, right string) *Template Delims方法用于设置action的分界字符串,应用于之后的Parse、ParseFiles、ParseGlob方法。嵌套模板定义会继承这种分界符设置。空字符串分界符表示相应的默认分界符:{{或}}。返回值就是t,以便进行链式调用。
  1. func (t *Template) Funcs(funcMap FuncMap) *Template Funcs方法向模板t的函数字典里加入参数funcMap内的键值对。如果funcMap某个键值对的值不是函数类型或者返回值不符合要求会panic。但是,可以对t函数列表的成员进行重写。方法返回t以便进行链式调用。
  1. func (t *Template) Clone() (*Template, error) Clone方法返回模板的一个副本,包括所有相关联的模板。模板的底层表示树并未拷贝,而是拷贝了命名空间,因此拷贝调用Parse方法不会修改原模板的命名空间。Clone方法用于准备模板的公用部分,向拷贝中加入其他关联模板后再进行使用。
  1. func (t *Template) Lookup(name string) *Template Lookup方法返回与t关联的名为name的模板,如果没有这个模板返回nil。
  1. func (t *Template) Templates() []*Template Templates方法返回与t相关联的模板的切片,包括t自己。
  1. func (t *Template) New(name string) *Template New方法创建一个和t关联的名字为name的模板并返回它。这种可以传递的关联允许一个模板使用template action调用另一个模板。
  1. func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) AddParseTree方法使用name和tree创建一个模板并使它和t相关联。
  1. func (t *Template) Parse(text string) (*Template, error) Parse方法将字符串text解析为模板。嵌套定义的模板会关联到最顶层的t。Parse可以多次调用,但只有第一次调用可以包含空格、注释和模板定义之外的文本。如果后面的调用在解析后仍剩余文本会引发错误、返回nil且丢弃剩余文本;如果解析得到的模板已有相关联的同名模板,会覆盖掉原模板。
  1. func (t *Template) ParseFiles(filenames ...string) (*Template, error) ParseGlob方法解析filenames指定的文件里的模板定义并将解析结果与t关联。如果发生错误,会停止解析并返回nil,否则返回(t, nil)。至少要提供一个文件。
  1. func (t *Template) ParseGlob(pattern string) (*Template, error) ParseFiles方法解析匹配pattern的文件里的模板定义并将解析结果与t关联。如果发生错误,会停止解析并返回nil,否则返回(t, nil)。至少要存在一个匹配的文件。
  1. func (t *Template) Execute(wr io.Writer, data interface{}) (err error) Execute方法将解析好的模板应用到data上,并将输出写入wr。如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。模板可以安全的并发执行。
  1. func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error ExecuteTemplate方法类似Execute,但是使用名为name的t关联的模板产生输出。

Example

顺便把官网的demo搬过来好了

Example1

官网给出的最简单的使用

// 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
`
// Prepare some data to insert into the template.
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)
    }
}
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

Example (Func)

官网给出了向模板内注入函数的demo

// First we create a FuncMap with which to register the function.
funcMap := template.FuncMap{
    // The name "title" is what the function will be called in the template text.
    "title": strings.Title,
}
// A simple template definition to test our function.
// We print the input text several ways:
// - the original
// - title-cased
// - title-cased and then printed with %q
// - printed with %q and then title-cased.
const templateText = `
Input: {{printf "%q" .}}
Output 0: {{title .}}
Output 1: {{title . | printf "%q"}}
Output 2: {{printf "%q" . | title}}
`
// Create a template, add the function map, and parse the text.
tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText)
if err != nil {
    log.Fatalf("parsing: %s", err)
}
// Run the template to verify the output.
err = tmpl.Execute(os.Stdout, "the go programming language")
if err != nil {
    log.Fatalf("execution: %s", err)
}
Input: "the go programming language"
Output 0: The Go Programming Language
Output 1: "The Go Programming Language"
Output 2: "The Go Programming Language"

Example (Glob)

ParseGlob的使用方法

// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
    // T0.tmpl is a plain template file that just invokes T1.
    {"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
    // T1.tmpl defines a template, T1 that invokes T2.
    {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
    // T2.tmpl defines a template T2.
    {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// T0.tmpl is the first name matched, so it becomes the starting template,
// the value returned by ParseGlob.
tmpl := template.Must(template.ParseGlob(pattern))
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
    log.Fatalf("template execution: %s", err)
}
T0 invokes T1: (T1 invokes T2: (This is T2))

Example (Helpers)

似乎是用来说明模板的嵌套的

// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
    // T1.tmpl defines a template, T1 that invokes T2.
    {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
    // T2.tmpl defines a template T2.
    {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// Load the helpers.
templates := template.Must(template.ParseGlob(pattern))
// Add one driver template to the bunch; we do this with an explicit template definition.
_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
if err != nil {
    log.Fatal("parsing driver1: ", err)
}
// Add another driver template.
_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
if err != nil {
    log.Fatal("parsing driver2: ", err)
}
// We load all the templates before execution. This package does not require
// that behavior but html/template's escaping does, so it's a good habit.
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)
}
Driver 1 calls T1: (T1 invokes T2: (This is T2))
Driver 2 calls T2: (This is T2)

Example (Share)

用来演示克隆的

// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
    // T0.tmpl is a plain template file that just invokes T1.
    {"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
    // T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
    {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// Load the drivers.
drivers := template.Must(template.ParseGlob(pattern))
// We must define an implementation of the T2 template. First we clone
// the drivers, then add a definition of T2 to the template name space.
// 1. Clone the helper set to create a new name space from which to run them.
first, err := drivers.Clone()
if err != nil {
    log.Fatal("cloning helpers: ", err)
}
// 2. Define T2, version A, and parse it.
_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
if err != nil {
    log.Fatal("parsing T2: ", err)
}
// Now repeat the whole thing, using a different version of T2.
// 1. Clone the drivers.
second, err := drivers.Clone()
if err != nil {
    log.Fatal("cloning drivers: ", err)
}
// 2. Define T2, version B, and parse it.
_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
if err != nil {
    log.Fatal("parsing T2: ", err)
}
// Execute the templates in the reverse order to verify the
// first is unaffected by the second.
err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
if err != nil {
    log.Fatalf("second execution: %s", err)
}
err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
if err != nil {
    log.Fatalf("first: execution: %s", err)
}
T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))

这一篇为阅读手册,下一篇开始使用测试template包

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论