gotemplate全面讲解
什么是go - template
你可以把它理解为Java的jsp,理解为js的ejs,vue的template,等等就是一个模版,所以就是需要了解一些模版的语法,以及渲染机制等,本文基本覆盖的很全面,模版其实核心是理解:1、基本语法,2、自定义func使用和内置func使用,3、变量的使用,4、模版复用等机制。
如果你掌握以上我说的,那么开发一个模版工具,是很轻松的,比如orm通用代码,其他工具等。
以下就是个demo: func TestTemplateString(t *testing.T) { tmpl := "my name is {{.}}" parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, "anthony") } // my name is anthony
所以需要模板渲染的部分都需要加入 {{}} , 有些部分操作需要加入{{end}} 作为标识符等。简单语法学习{{.}}
可以展示任何数据,各种类型的数据,可以理解为接收类型是 interface{} func TestTemplateString(t *testing.T) { tmpl := "my name is {{.}}" parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, []string{"hao", "dong"}) } === RUN TestTemplateString my name is [hao dong]--- PASS: TestTemplateString (0.00s) PASS{{.Field}}func TestTemplateStruct(t *testing.T) { tmpl := `my name is "{{.Name}}"` parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, struct{ Name string }{Name: "anthony"}) }{{/* a comment */}}
给go文件加入注释 func TestTemplateCommon(t *testing.T) { tmpl := `{{/* a comment */}}` parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, struct{ Name string }{Name: "anthony"}) } // 什么也不输出{{-content-}}
去除前后空格,超级方便好用 func TestTemplateTrim(t *testing.T) { tmpl := ` {{- . -}} ` parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, "hello") } //=== RUN TestTemplateTrim //hello--- PASS: TestTemplateTrim (0.00s)
如果不去会是这样子 func TestTemplateTrim(t *testing.T) { tmpl := ` {{ . }} ` parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, "hello") } //=== RUN TestTemplateTrim // // hello --- PASS: TestTemplateTrim (0.00s) //PASS条件语句{{ifcondition}}do{{else}}doelse{{end}}这个语意是如果true,则do,否则do-else,必须最后申明 {{end}} , 跟我的ifelse 一模一样的
doelse的条件为 : false、0、任意 nil 指针、接口值、数组、切片、字典和空字符串 "" (长度为 0 的字符串)。func TestTemplateIf(t *testing.T) { tmpl := `{{if .flag -}} The flag=true {{- else -}} The flag=false {{- end}} ` parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, _map{ "flag": true, }) _ = parse.Execute(os.Stdout, _map{ "flag": false, }) } === RUN TestTemplateIf The flag=true The flag=false --- PASS: TestTemplateIf (0.00s) PASS循环语句{{range content}}T1{{end}}
语意很简单,就是遍历内容 func TestRange(t *testing.T) { tmpl := `{{range .Array}} {{- . -}}, {{- end}} ` parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, struct { Array []string }{Array: []string{"a", "b", "c"}}) } //=== RUN TestRange //a,b,c, //--- PASS: TestRange (0.00s){{range content}}T1{{else}}T0{{end}}
如果数组为空,输出else的东西 func TestRangeEmpty(t *testing.T) { tmpl := `{{range .Array}} {{- . -}}, {{else -}} array null {{- end}} ` parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, struct { Array []string }{Array: []string{}}) } //=== RUN TestRangeEmpty //array null //--- PASS: TestRangeEmpty (0.00s){{range $index,$value:=pipeline}}T1{{end}}{{range $key,$value :=pipeline }}T1 {{end}} 也适用,用于遍历
关于 $key ,这个属于变量操作,后面会讲到func TestRange(t *testing.T) { tmpl := `{{range $key, $value:=.Array}} {{- $key}}:{{$value}}, {{- end}} ` parse, _ := template.New("demo").Parse(tmpl) _ = parse.Execute(os.Stdout, struct { Array map[string]string }{Array: map[string]string{"a": "1", "b": "2", "c": "3"}}) } //=== RUN TestRange //a:1,b:2,c:3, //--- PASS: TestRange (0.00s){{ range pipeline }} T1 {{ end }} // 这个 else 比较有意思,如果 pipeline 的长度为 0 则输出 else 中的内容 {{ range pipeline }} T1 {{ else }} T0 {{ end }} // 获取容器的下标 {{ range $index, $value := pipeline }} T1 {{ end }}FuncMap(很重要)1、自定义函数
type FuncMapmap[string]interface{}
它有一个比较好的功能就是,自定义函数 func TestFuncMap(t *testing.T) { tem, _ := template.New("").Funcs(map[string]interface{}{ "ReplaceAll": func(src string, old, new string) string { return strings.ReplaceAll(src, old, new) }, }).Parse(`func replace: {{ReplaceAll .content "a" "A"}}`) tem.Execute(os.Stdout, map[string]interface{}{ "content": "aBC", }) } === RUN TestFuncMap func replace: ABC--- PASS: TestFuncMap (0.00s) PASS
其中,不能写 {{ReplaceAll.content a A}} ,因为go不识别 a 是一个字符串,所以必须加引号
如果理解了这个,其实对于一些内置函数会理解很多,其中对于函数的要求是: // 写法错误:不允许有两个参数返回值,如果是两个返回值,第二个必须是error "ReplaceAll": func(src string, old, new string) (string, string) { return strings.ReplaceAll(src, old, new), "111" }, // 写法正确: 如果两个返回类型,第二个必须是error,顺序不能颠倒 "ReplaceAll": func(src string, old, new string) (string, error) { return strings.ReplaceAll(src, old, new), nil }, // 写法正确: 如果是一个返回类型,直接返回就行了 "ReplaceAll": func(src string, old, new string) string { return strings.ReplaceAll(src, old, new) }, // 写法正确:参数可以不传递,但是必须有返回值的(其实可以理解,没有返回值,你渲染啥) tem, _ := template.New("").Funcs(map[string]interface{}{ "Echo": func() string { return "hello world" }, }).Parse(`func echo : {{Echo}}`) // 写法错误:不允许没有返回参数,直接panic "ReplaceAll": func(src string, old, new string) { strings.ReplaceAll(src, old, new) },
其实理解了funcmap,你就理解了内置函数如何玩的,接下来就会说的,不用死记硬背。 2、内置函数
内置函数本质上也是 FuncMap,所以如果掌握了如何使用FuncMap,其实就会这个了。 eqfunc TestEQ(t *testing.T) { parse, _ := template.New("").Parse(`{{eq .content1 .content2}}`) parse.Execute(os.Stdout, _map{ "content1": "a", "content2": "b", }) } === RUN TestEQ false--- PASS: TestEQ (0.00s) PASScallfunc TestCall(t *testing.T) { parse, _ := template.New("").Parse(`{{call .fun .param}} `) parse.Execute(os.Stdout, _map{ "fun": func(str string) string { return strings.ToUpper(str) }, "param": "abc", }) } === RUN TestCall ABC --- PASS: TestCall (0.00s) PASS
其实就是这么简单,对于call函数来说,它必须要求返回参数格式是1或2个,其中如果是两个则必须一个是error func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) func safeCall(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) { defer func() { if r := recover(); r != nil { if e, ok := r.(error); ok { err = e } else { err = fmt.Errorf("%v", r) } } }() ret := fun.Call(args) // 结果如果两个,则必须有一个是error if len(ret) == 2 && !ret[1].IsNil() { return ret[0], ret[1].Interface().(error) } return ret[0], nil }变量
变量通常适用于定义了某个值,申明很简单,和写php一样,变量前面需要加入一个 $ ,注意的是需要渲染的部分全部需要用{{}} 包起来func TestVariable(t *testing.T) { tmpl, err := template.New("test").Parse(`{{$a := "anthony"}} hello {{$a}}`) if err != nil { panic(err) } err = tmpl.Execute(os.Stdout, nil) if err != nil { panic(err) } }
其实跟我的go预发基本一致。 模版嵌套
自定义内嵌的模板 {{define "template_name"}}template_content {{end}} ,其中就是一种模版复用的机制。 那么如何模版引用呢{{template"template_name""args"}} ,记得传入两个参数就行,一个模版的名称(记住申明模版名称需要加上"" ),一个是你的模版需要的参数。func TestTemplateInternal(t *testing.T) { parse, _ := template.New("").Parse(` {{define "print"}}my name is {{.}} {{end}} {{- template "print" .name}} `) parse.Execute(os.Stdout, _map{ "name": "hao dong", }) } === RUN TestTemplateInternal my name is hao dong --- PASS: TestTemplateInternal (0.00s) PASSformat 工具
我们知道,go是有个fmt插件,可以帮助format 文件,所以在这里go 也提供了format func TestFormat(t *testing.T) { parse, _ := template.New("").Parse(` package main import "fmt" func main(){ fmt.Println("{{.}}") } `) parse.Execute(os.Stdout, _map{"data":"1111"}) } // 结果:是不是很乱,但是别慌 === RUN TestFormat package main import "fmt" func main(){ fmt.Println("map[data:1111]") } --- PASS: TestFormat (0.00s) PASS
如何使用format呢 ? func TestFormat(t *testing.T) { parse, _ := template.New("").Parse(` package main import "fmt" func main(){ fmt.Println("{{.}}") } `) buffer := &bytes.Buffer{} parse.Execute(buffer, _map{"data":"1111"}) source, _ := format.Source(buffer.Bytes()) fmt.Printf("%s",source) } === RUN TestFormat package main import "fmt" func main() { fmt.Println("map[data:1111]") } --- PASS: TestFormat (0.00s) PASS
是不是变得很整齐。 总结
go 原生的提供了模版机制,对于开发者相当友好,尽管第三方包有很多模版工具,但是原生是最好的,因为不需要引入依赖。
模版通常用于生成一些复用的代码,比如 protobuf文件生成, 比如orm框架的model,dao等,都是需要改进的 参考
https://juejin.im/post/5c403b98f265da612d1984c9
https://juejin.im/post/5eb232026fb9a043867d50ae
邓伦生日闪现八城地标,品牌这波芯光祝福也太秀了每一剪光影彰显无与伦比的魅力踏实沉淀自我积蓄演员的能量,专注解读作品赋予角色的层次,他的品格与魅力令人伦陷。祝邓伦生日快乐,时光不会辜负静心努力的人,请继续执着勇敢地前行10月21
三十而励,细节处处不同微软为李现送上地标生日广告祝福最好的李,就是现在在演员职业里,怀炽热初心真诚对待表演,以角色体会多重人生在日常生活中,跃进人海深入生活感受万物,以自在拥抱广阔世界。微软x李现生日x地标马克户外广告灯光秀与光影交
战火再燃!户外地标广告打响街舞终极之战这!就是街舞4全球精英争霸赛倒计时10月25日,多地上演这街4灯光大秀,潮燃名场面令人震撼!地标马克点亮旗下两城地标灯光秀为巅峰之夜这!就是街舞第四季总决赛闪耀加码!这!就是街舞x
纪念二万五千里的奇迹地标马克户外广告全国9城地标集体点亮今天,纪念红军长征伟大胜利红军不怕远征难,万水千山只等闲。长征胜利85周年x地标马克户外公益广告灯光秀85年前的今天,有一支英雄的部队,纵横二万五千里,在中国地图上谱写了一部气吞山
幽会王维我穿越到唐宋走近唐宋的文人骚客与先哲大师来一场心灵约会,我觉王维大概是最能看透人生意义的,并善于享受生活珍惜人生当下的人。王维晚年退隐终南山参禅悟理,学庄信道,精通诗书画音乐。诗名盛于开元天宝
心安处天涯何处不故乡晓风星月,反思过往深圳是实现梦想的天堂,在这里,年青人至少不用担心七大姑八大姨的催婚,努力往前冲就好了,在这里没有任何人嘲笑你,思想奇特,异想天开,因为没有人怀疑一个努力的人突然成
晓风残月悟道人生跳出三界外晓风残月悟道人生跳出三界外,人生时时要悟道,跳出三界其实是道家术语,但依然有现实中的指导意义,现实中人要跳出束缚你,压抑你激情的圈子,现实中就象鸟寻觅更适合容身与觅食的圈子,当一个
鹏城蔚蓝的天缱绻了五月的时光行走在铺满阳光的小径,轻嗅风中花香的美好,心思是否会如露珠般晶莹剔透呢?最好的时光,应开出最好的花吧。时光,留不住昨天缘分,停不在初见。漫漫历史长河中,我们都是岁月中的微不足道的小
晓风残月子夜扰思醉酒的夜晚周未出差广州,夜色中的羊城让我迷醉,今天下午头条上一震憾心灵的消息让我半天缓不过神,袁隆平院士袁老于今日2点7分走了。再加上前两天的那个拥有1000亿财富帝国的左辉也走了。网上一片
精神的世外桃园晓风夜月,夜读心灵深圳住久了,总能避开很多是非,那些形式上的礼尚往来的虚情假意,和无效社交觥筹交错,简直是醉生梦死蹉跎岁月,但在深圳完全可享受自由,享受寂寞,享受孤独,是深圳一种特
激情在深圳湾的日出里沉醉每日面对深圳湾日出,就象给心灵充电,蓄存光阴里阳光与快乐。随着环境与经济状况的焕然一新,会看淡了很多人很多事,却再也不想随便捅破,包括讨厌的人和事,再也没必要去交往,不值得去认真更