侧边栏壁纸
博主头像
张种恩的技术小栈博主等级

行动起来,活在当下

  • 累计撰写 748 篇文章
  • 累计创建 65 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录
Go

看看go template再来写helm chart吧~

zze
zze
2021-07-07 / 1 评论 / 0 点赞 / 1367 阅读 / 15170 字

不定期更新相关视频,抖音点击左上角加号后扫一扫右方侧边栏二维码关注我~正在更新《Shell其实很简单》系列

不知道你有没有写过 helm chart,市面上大部分教程讲到 helm chart 的语法基本都是硬讲,死记硬背那些语法。。其实,chart 的语法正是 go template 的语法,所以只要系统的学习一下 go template,再来看 helm chart 基本一下子就了然于心了。。所以这里就有了这篇文章对 go template 的做下小结。

Golang 提供了两个标准库用来处理模板 text/templatehtml/template,这俩库就类似于 Java 的 thymeleaf、freemarker 以及 Python 的 Jinjia2,也就是模板引擎库。

如其名,我们通常使用 text/template 来处理裸文本文档,使用 html/template 来处理 html 文档。

本篇文章后续示例我都会使用 html/template,它们的接口几乎没有差异,了解一个另一个也就会了。

入门示例

下面看一个小例子:

package main

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
)

func main() {
	tplStr := `<a href="zze.xyz">{{ . }}</a>`
	// 从字符串加载模板
	tpl, err := template.New("tpl").Parse(tplStr)
	// 从文件加载模板
	// tpl, err := template.ParseFiles("tpl.html")
	if err != nil {
		log.Fatalf("parse failed, err: %v\n", err)
	}

	buf := new(bytes.Buffer)
	err = tpl.Execute(buf, "hello zze")
	//err = tpl.Execute(os.Stdout, "hello zze")
	if err != nil {
		log.Fatalf("exec tpl failed, err: %v\n", err)
	}
	fmt.Println(buf.String())
	/*
		<a href="zze.xyz">hello zze</a>
	*/
}

在上述示例中,通过将模板(这里使用的是字符串,对应 tplStr 变量值,也可以使用一个文件)应用于一个对象(即该对象作为模板的参数)来执行,以获得输出。模板执行时会将指针指向当前操作对象,并表示为 .

用作模板的输入文本必须是 utf-8 编码的文本。在模板内容中通过 {{}} 来界定参数值,在其之外的所有文本都直接原样拷贝到输出中。

函数

预置函数

go template 预置了很多常用的函数供我们使用,如下:

  • and:同 shell 的 && 语义,第一个参数为真时,返回第二个参数,否则返回第一个参数;
  • or:同 shell 的 || 语义,第一个参数为真时,返回第一个参数,否则返回第二个参数;
  • not:同 shell 的 ! 语义,取反返回;
  • len:返回参数值的长度;
  • index:返回以第一个参数为数组或字典,以第二个参数为索引或键指向的值;
  • print: 同 fmt.Sprint
  • printf:同 fmt.Sprintf
  • println:同 fmt.Sprintln
  • html:转义 HTML 文本;
  • urlquery: 转义 URL 中的标点符号;
  • js:转义 javascript 脚本中的标点符号;
  • call:调用对象方法,改方法必须以属性的方式预定义到结构体;
  • eq:第一个参数和第二个参数相等则返回 true,否则返回 false
  • ne:第一个参数和第二个参数不相等则返回 true,否则返回 false
  • lt:第一个参数小于第二个参数则返回 true,否则返回 false
  • le:第一个参数小于等于第二个参数则返回 true,否则返回 false
  • gt:第一个参数大于第二个参数则返回 true,否则返回 false
  • ge:第一个参数大于等于第二个参数则返回 true,否则返回 false

看如下示例:

func main() {
	tplStr := `
	and: {{ and 0 1 }} | {{ and 2 1 }}
	or: {{ or 0 1 }} | {{ or 2 1 }}
	not: {{ not 0 }} | {{ not 1 }} | {{ not false }} | {{ not true }}
	len: {{ len "zze" }}
	index: {{ index .A 0 }} | {{ index .M 1 }}
	print: {{ print .A }}
	printf: {{ printf "hello %s" "zze" }}
	println: {{ println "hello zze" }}
	html: {{ html "<a href='zze.xyz'>zze</a>" }}
	urlquery: {{ urlquery "www.zze.xyz?id=1" }}
	js: {{ js "alert('123')" }}
	call: {{ call .U.Show "test" }}
	eq: {{ eq .U.Age 24 }}
	ne: {{ ne .U.Age 24 }}
	lt: {{ lt .U.Age 30 }}
	le: {{ le .U.Age 30 }}
	gt: {{ gt .U.Age 30 }}
	ge: {{ ge .U.Age 30 }}
`
	mySlice := []string{"a", "b", "c"}
	myMap := make(map[int]string)
	myMap[0] = "zze1"
	myMap[1] = "zze2"
	myUser := User{Id: 1, Name: "zze", Age: 24}
	myUser.Show = func(remark string) string {
		return fmt.Sprintf("my name is %s, %d years old. [%s]", myUser.Name, myUser.Age, remark)
	}
	tpl, _ := template.New("tpl").Parse(tplStr)
	tpl.Execute(os.Stdout, struct {
		A []string
		M map[int]string
		U User
	}{mySlice, myMap, myUser})
	/*
	        and: 0 | 1
	        or: 1 | 2
	        not: true | false | true | false
	        len: 3
	        index: a | zze2
	        print: [a b c]
	        printf: hello zze
	        println: hello zze

	        html: &lt;a href=&#39;zze.xyz&#39;&gt;zze&lt;/a&gt;
	        urlquery: www.zze.xyz%3Fid%3D1
	        js: alert(\&#39;123\&#39;)
	        call: my name is zze, 24 years old. [test]
	        eq: true | true
	        ne: false
	        lt: true
	        le: true
	        gt: false
	        ge: false
	*/
}

自定义函数

除了上述介绍的内置函数,go template 还支持自定义函数,看如下示例:

tplStr := `
		{{ show "zze" }}
	`
show := func(name string) string {
	return fmt.Sprintf("%s 真帅!", "zze")
}
tpl, _ := template.New("tpl").Funcs(template.FuncMap{"show": show}).Parse(tplStr)
tpl.Execute(os.Stdout, "")
/*
	zze 真帅!
*/

要注意的是自定义的函数必须在执行 Parse 方法前注册哦~

常用语法

渲染结构体

可以直接通过 . 来访问结构体对象中的属性。

tplStr := `
	<ul>
		<li>编号:{{ .Id }}</li>
		<li>姓名:{{ .Name }}</li>
		<li>年龄:{{ .Age }}</li>
	</ul>
	`
tpl, _ := template.New("tpl").Parse(tplStr)

tpl.Execute(os.Stdout, struct {
	Id   int
	Name string
	Age  int
}{1, "zze", 24})

/*
   <ul>
		   <li>编号:1</li>
		   <li>姓名:zze</li>
		   <li>年龄:24</li>
   </ul>
*/

修改当前对象

可以使用 {{ with <target> }} ... {{ end }} 来将被 with 包裹的上下文的 . 的值设置为 target

tplStr := `
	.Age: {{ .Age }}
	{{ with .Age -}}
	with <target>: {{ . -}}
	{{ end }}
	`
tpl, _ := template.New("tpl").Parse(tplStr)

tpl.Execute(os.Stdout, struct {
	Id   int
	Name string
	Age  int
}{1, "zze", 24})
/*
	.Age: 24
	with <target>: 24
*/

注释

{{/* ... */}} 包裹的内容不会被渲染。

tplStr := "hello {{/* a comment */}}"
tpl, _ := template.New("tpl").Parse(tplStr)
tpl.Execute(os.Stdout, "xxx")
/*
hello
*/

变量

模板中支持变量的定义和变量的赋值,同 go 语法,:= 代表初始化变量并赋值,= 仅代表赋值。

tplStr := `
		{{- /* 声明变量并赋值 */ -}}
		{{- $strLen := (len .) }}
		长度:{{ $strLen }}
		{{/* 赋值 */}}
		{{- $strLen = 8 -}}
		长度new:{{- $strLen }}
	`

tpl, _ := template.New("tpl").Parse(tplStr)
tpl.Execute(os.Stdout, "hello zze")
/*
	长度:9
	长度new:8
*/

条件判断

有如下三种标准的条件判断:

  • 单分支:{{ if <expr> }} t1... {{ end }}
  • 双分支:{{ if <expr> }} t1... {{ else }} t2... {{end}}
  • 多分支:{{ if <expr1> }} t1... {{ else if <expr2> }} t2... {{ else }} t3... {{ end }}
tplStr := `
	{{ if lt .Age 18 }}
		少年
	{{ else if and (ge .Age 18) (lt .Age 35) }}
		青年
	{{ else if and (ge .Age 35) (lt .Age 50) }}
		中年
	{{ else }}
		老年
	{{ end }}
	`
tpl, _ := template.New("tpl").Parse(tplStr)
tpl.Execute(os.Stdout, struct {
	Id   int
	Name string
	Age  int
}{1, "zze", 24})
/*
青年
 */

遍历

go template 也提供了类似 for 循环的遍历操作语法。

type User struct {
	Id   int
	Name string
	Age  int
}

func main() {
	tplStr := `
	<ul>
	{{- range . -}}
		{{/* "." 遍历此段内容 */}}
	   <li>{{ .Id }}|{{ .Name }}|{{ .Age }}</li>
	{{- else }}
		{{/* "." 没有内容时才会执行此处 */}}
		null
	{{ end }}
	</ul>

	{{/* 获取遍历索引 */}}
	<ul>
	{{- range $i, $v := . }}
	   <li>{{ $i }} : {{ $v.Id }}|{{ $v.Name }}|{{ $v.Age}}</li>
	{{- end }}
	</ul>
	`
	users := make([]User, 0)
	users = append(users, User{1, "zze", 94})
	users = append(users, User{2, "zzf", 64})
	users = append(users, User{3, "zzg", 24})
	tpl, _ := template.New("tpl").Parse(tplStr)

	tpl.Execute(os.Stdout, users)
	/*
	<ul>
	   <li>1|zze|94</li>
	   <li>2|zzf|64</li>
	   <li>3|zzg|24</li>
	</ul>

	<ul>
           <li>0 : 1|zze|94</li>
           <li>1 : 2|zzf|64</li>
           <li>2 : 3|zzg|24</li>
        </ul>
	 */
}

去除空白

可以在 {{ 符号的后面加上短横线并保留一个或多个空格 - 来去除它前面的空白(包括换行符、制表符、空格等),即 {{- xxxx

}} 的前面加上一个或多个空格以及一个短横线 - 来去除它后面的空白,即 xxxx -}}

tplStr := `
	a {{ 111 }} b
	a {{- 111 }} b
	a {{ 111 -}} b
`
show := func(name string) string {
	return fmt.Sprintf("%s 真帅!", "zze")
}
tpl, _ := template.New("tpl").Funcs(template.FuncMap{"show": show}).Parse(tplStr)
tpl.Execute(os.Stdout, "")
/*
	a 111 b
	a111 b
	a 111b
*/

管道

前面介绍函数时所有函数的执行格式都是 <函数名> <参数1> <参数2>.. 这种形式,其实它们还可以通过管道的方式来接收参数,看如下示例:

tplStr := `
		{{ 18 | ge .Age }}
		{{ .Name | show }}
	`
show := func(name string) string {
	return fmt.Sprintf("%s 真帅!", "zze")
}
tpl, _ := template.New("tpl").Funcs(template.FuncMap{"show": show}).Parse(tplStr)
tpl.Execute(os.Stdout, struct {
	Id   int
	Name string
	Age  int
}{1, "zze", 24})

明确不转义

go template 默认情况下对于要传入模板渲染的入参都会进行转义以防止 XFS 攻击,比如:

tplStr := `
		{{ . }}
	`
tpl, _ := template.New("tpl").Parse(tplStr)
tpl.Execute(os.Stdout, "<script>alert('asas')</script>")
/*
&lt;script&gt;alert(&#39;asas&#39;)&lt;/script&gt;
 */

如果很明确不需要这个转义,可以使用 template.HTML 方法包裹传入的字符串:

tplStr := `
		{{ . }}
	`
tpl, _ := template.New("tpl").Parse(tplStr)
tpl.Execute(os.Stdout, template.HTML("<script>alert('asas')</script>"))
/*
<script>alert('asas')</script>
 */

嵌套模板

可以通过 define <模板名> 在模板文件中预定义一些子模板块供后续 template <模板名> 调用。

tplStr := `
		{{- define "T1" }} ONE {{ println . }}{{ end }}
		{{- define "T2" }} TWO {{ println . }}{{ end }}
		{{- define "T3" }}{{ template "T1" }}{{ template "T2" "haha" }}{{ end }}
		{{- template "T3" -}}
	`
tpl, _ := template.New("tpl").Parse(tplStr)
tpl.Execute(os.Stdout, "")

/*
 ONE &lt;nil&gt;
 TWO haha
*/

执行 template 时,第一个参数为 define 定义的模板名,第二个参数将会作为对应模板范围内的当前对象赋值给 .。如上,调用 T1 时没有第二个参数,所以渲染结果为 ONE nil,调用 T2 时第二个参数设置为了 haha,所以最终渲染结果为 TWO haha

define 块内也可以通过 template 来调用其它已 define 的模板,如 T3 中调用了 T1T2

template 不只能调用 define 块,还可以直接调用模板。看如下示例:

main.go 同级目录有如下两个文件:

  • 1.html
<h1> in 1.html </h1>
{{ template "2.html" }}
  • 2.html
<h1> in 2.html </h1>

main.go 内容如下:

func main() {
	tpls, _ := template.ParseFiles("1.html", "2.html")
	splitLine := strings.Repeat("-", 40)
	// 遍历每个模板执行
	for _, tpl := range tpls.Templates() {
		fmt.Printf("%s %s %s\n", splitLine, tpl.Name(), splitLine)
		tpl.Execute(os.Stdout, "")
		fmt.Println()
	}
	fmt.Printf("%s 默认:%s %s\n", splitLine, tpls.Name(), splitLine)
	// 直接调用 tpls 的 Execute 默认会渲染第一个模板,这里也就是 1.html
	tpls.Execute(os.Stdout, "")
}

执行结果如下:

$ go run main.go 
---------------------------------------- 1.html ----------------------------------------
<h1> in 1.html </h1>
<h1> in 2.html </h1>
---------------------------------------- 2.html ----------------------------------------
<h1> in 2.html </h1>
---------------------------------------- 默认:1.html ----------------------------------------
<h1> in 1.html </h1>
<h1> in 2.html </h1>

block 块

block 块可以在模板中预留一个位置并给这个位置设置一个默认值,看下面示例。

main.go 同级目录下有如下文件:

  • base.html
<title>{{block "title" .}}Default Title{{end}}</title>
<body>{{block "content" .}}This is the default body.{{end}}</body>
  • home.html
{{define "title"}}Home{{end}}
{{define "content"}}This is the Home page.{{end}}
  • about.html
{{define "title"}}About{{end}}
{{define "content"}}This is the About page.{{end}}

main.go 中来渲染 base.html

tpl, _ := template.ParseFiles("base.html")
tpl.Execute(os.Stdout, "")
/*
<title>Default Title</title>
<body>This is the default body.</body>
*/

由于仅解析了 base.htmlblock 并不能发现到 home.htmlabout.html 中通过 define 定义的块的存在,所以此时就会输出 base.htmlblock 块中定义的默认内容。

修改 main.go 同时解析 base.htmlhome.html

tpl, _ := template.ParseFiles("base.html", "home.html")
tpl.Execute(os.Stdout, "")
/*
<title>Home</title>
<body>This is the Home page.</body>
 */

可以看到由于同时解析了 home.htmlbase.html,所以 base.html 中的 block 块能够发现 home.html 中通过 define 定义的 titlecontent 块,就会使用它们替换对应的 block 块。

同理,同时解析 base.htmlabout.html 效果如下:

tpl, _ := template.ParseFiles("base.html", "about.html")
tpl.Execute(os.Stdout, "")
/*
<title>About</title>
<body>This is the About page.</body>
 */
0

评论区