SoFunction
Updated on 2025-03-05

Golang uses Template template to generate text dynamically

Go Template in Go is a simple and powerful template engine for generating text output. It provides a flexible way to generate text in various formats such as HTML, XML, JSON, etc.

The following main features of Go Template:

  • Simple and easy to use: Go Template syntax is concise and easy to understand. It uses a pair of double braces "{{}}" to mark the placeholders and control structure of the template. This simple syntax makes writing and maintaining templates very convenient.
  • Data-driven: Go Template supports data-driven template generation. You can pass the data structure to the template and use the dot number "." in the template to reference the fields and methods of the data. This data-driven approach allows templates to dynamically generate output based on different data.
  • Conditions and loops:Go Template provides conditional statements and loop statements, allowing you to control the output of templates based on conditions and iterations. You can use keywords such as "if", "else", and "range" to implement conditional judgment and loop iteration to generate flexible output.
  • Filters and functions:Go Template supports filters and functions for converting and processing data. You can use built-in filters to format data, such as date formatting, string truncation, etc. In addition, you can define your own functions and call them in the template to implement more complex logic and operations.
  • Nested templates:Go Template supports nesting of templates, allowing you to include other templates in one template. This combination and nesting mechanism of templates can help you build larger and more complex template structures, improving the reusability and maintainability of your code.

In many Go development tools, the project uses template templates in large quantities. For example: Helm, K8s, Prometheus, and some code-gen code generators, etc. Go template provides a template mechanism to flexibly customize various text by pre-declaring templates and passing in custom data.

1. Example

Let's use an example to understand the basic use of template.

First declare a template

var md = `Hello,{{ . }}`

Parses templates and execute

func main() {
    tpl := (("first").Parse(md))
    if err := (, "Jack"); err != nil {
        (err)
    }
}
// Output// Hello Jack

In the above example,{{ . }}The braces before and after belong to the delimiter, template will parse and fill the data in the delimiter. in . Representing the current object, this concept exists in many languages.

In the main function, we passCreate a template named "first" and use this template to parse the template. Then, execute:,data, template will fill the data into the parsed template and then output it to the passed in.

Let's take another example

// {{ .xxoo -}} Delete the blank on the rightvar md = `personal information:
Name: {{ .Name }}
age: {{ .Age }}
Hobby: {{ .Hobby -}}
`
type People struct {
    Name string
    Age  int
}
func (p People) Hobby() string {
    return "Sing, dance, rap, basketball"
}
func main() {
    tpl := (("first").Parse(md))
    p := People{
        Name: "Jackson",
        Age:  20,
    }
    if err := (, p); err != nil {
        (err)
    }
}
// Output//personal information://Name: Jackson//Age: 20//Hobby: Sing,Jump,rap,basketball

Hobby belongs to People's method, so it can also be passed in the template..Make a call. Note: Whether it is a field or a method, since the package actually parsed by template is different from the current package, both the field or method must beExportof.

When parsing in template, itRemoved {{and}}The content inside, but the blanks left remained completely the same. So when parsing it, we need to control the blanks. YAML believes that blanks make sense, so managing them becomes important. We can pass-Control blanks.

{{- (including added bars and spaces) means to delete the blank to the left, and -}} Indicates that the space on the right should be removed.

Be sure-There is a space between it and other commands.

{{- 10 }}: "Indicates that the space is removed to the left and print 10"

{{ -10 }}: "Indicates printing-10"

2. Process control

Conditional judgment IF ELSE

In template,if/elseprocess judgment.

Let's take a look at the definition of doc:

{{if pipeline}} T1 {{end}}
If the value of pipeline is empty, no output is generated;
Otherwise, execute T1. The null value is false, 0, any
nil pointer or interface value, and
Any array, slice, map or string of length zero.
Points are not affected.
{{if pipeline}} T1 {{else}} T0 {{end}}
If the value of pipeline is empty, execute T0;
Otherwise, execute T1. Points are not affected.
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
To simplify the appearance of the if-else chain,
The else operation of if can directly include another if

inpipelineThe command is aSimple value (parameter) or a function or method call. Our first example hobby is a method call.

Continue with the above case, we added an IF/ELSE to judge age, in IF we used a built-in functiongtJudge age.

In template, call the function and pass the parameters are followed by the function:function arg1 agr2

Or it can be passed through pipe characters:arg | function

Each function must have 1 to 2 return values, and if there are 2, the latter must be of the error interface type.

var md = `personal information:
Name: {{ .Name }}
age: {{ .Age }}
Hobby: {{ .Hobby -}}
{{ if gt .Age 18 }}
Adults
{{ .Age | print }}
{{ else }}
未Adults
{{ end }}
`
// Output//personal information://Name: Jackson//Age: 20//Hosts: Sing, dance, rap, basketball//Adults//20

Loop control range

template also provides loop control function. Let's take a look at the doc first

{range pipeline}} T1 {{end}} The value of pipeline must be an array, slice, map, or channel.
If the value length of the pipeline is zero, no content is output;
Otherwise, set the points to continuous elements of the array,
Slice or map and execute T1. If the value is a map and the key is a primitive type with the definition order, the order of sort keys will be accessed
    
{{range pipeline}} T1 {{else}} T0 {{end}} 
The value of pipeline must be an array, slice, map, or channel.
If the pipe's value length is zero, then . is not affected and
Execute T0; otherwise, set . as a continuous element of an array, slice, or map and execute T1.
    
{{break}}
The innermost {{range pipeline}} loop ends early, stops the current iteration and bypasses all remaining iterations.
    
{{continue}}
The innermost {{range pipeline}} loop skips the current iteration

Integrate the above IF/ELSE and we will make a comprehensive case

var md = `
Start iteration:
{{- range . }}
{{- if gt . 3 }}
Exceed3
{{- else }}
{{ . }}
{{- end }}
{{ end }}
`
func main() {
    tpl := (("first").Parse(md))
    p := []int{1, 2, 3, 4, 5, 6}
    if err := (, p); err != nil {
        (err)
    }
}
// Output//1       
//2        
//3       
//More than 3//More than 3//Exceed3

We passed{{ range . }}Iterate through the incoming object and pass it inside the loop{{ if }}/{{ else }}Determine the size of each element.

Scope control with

There is a concept of scope in languages. template also provides the use of with to modify scope.

Let's take a look at a case

var md = `
people name(out scope): {{ .Name }}
dog name(out scope): {{ . }}
{{- with .MyDog }}
dog name(in scope): {{ .Name }} 
people name(in scope): {{ $.Name }}
{{ end }}
`
type People struct {
    Name  string
    Age   int
    MyDog Dog
}
type Dog struct {
    Name string
}
func main() {
    tpl := (("first").Parse(md))
    p := People{Name: "Lucy", MyDog: Dog{Name: "Tom"}}
    if err := (, p); err != nil {
        (err)
    }
}
// Output//people name(out scope): Lucy
//dog name(out scope): Tom    
//dog name(in scope): Tom     
//people name(in scope): Lucy

In the top-level scope, we can directly use.Go to get the object's information. In the declaredwithIn, we pass the MyDog of the top-level object in, then in the with scope, through.The object obtained is Dog. So inwithWe can pass directly.Get the name of the Dog.

Sometimes, in the subscope we may also hope to get the top-level object, so we can use$Get the top-level object. The above examples$.Get People.

3. Function

In the second section, we usedprint,gtFunctions, these functions are predefined in template. We can view the predefined functions by looking up the source code:

func builtins() FuncMap {
    return FuncMap{
        "and":      and,
        "call":     call,
        "html":     HTMLEscaper,
        "index":    index,
        "slice":    slice,
        "js":       JSEscaper,
        "len":      length,
        "not":      not,
        "or":       or,
        "print":    ,
        "printf":   ,
        "println":  ,
        "urlquery": URLQueryEscaper,
        // Comparisons
        "eq": eq, // ==
        "ge": ge, // >=
        "gt": gt, // >
        "le": le, // <=
        "lt": lt, // <
        "ne": ne, // !=
    }
}

In actual development, it is difficult for these functions to meet our needs alone. At this point, we hope to be able to pass inCustom functions, when we write templates, you can use custom functions.

We introduce a requirement: I hope that the incoming str can be converted to lowercase.

var md = `
result: {{ . | lower }}
`
func Lower(str string) string {
    return (str)
}
func main() {
    tpl := (("demo").Funcs(map[string]any{
        "lower": Lower,
    }).Parse(md))
    (, "HELLO FOSHAN")
}
// Output// result: hello foshan

Since template supports chain calls, we usually put Parse at the end

We callFuncs, incomingfunctionName : functionmap.

When executing a template, the function looks for two function maps: first the template function map, and then the global function map. Generally, functions are not defined in the template, but functions are added to the template using the Funcs method.

The method must have one or two return values. If it is two, then the second must be an error interface type.

Notice:FuncsMust be called before parse parse. If the template has been parsed and then passed in funcs, template does not know how the function should be mapped.

4. Variables

Functions, pipeline characters, objects, and control structures can all be controlled, and we turn to one of the more basic ideas in many programming languages: variables. In templates, they are rarely used. But we can use variables to simplify the code and use it betterwithandrange

We passed{{ $var := .Obj }}Declare variables, inwith/rangeWe use it more frequently

var md = `
{{- $count := len . -}}
There are a total of{{ $count }}Elements
{{- range $k,$v := . }}
{{ $k }} =&gt; {{ $v }}
{{- end }}
`
func main() {
    tpl := (("demo").Parse(md))
    (, map[string]string{
        "p1": "Jack",
        "p2": "Tom",
        "p3": "Lucy",
    })
}
// Output// There are 3 elements in total// p1 =&gt; Jack 
// p2 =&gt; Tom  
// p3 =&gt; Lucy

The variable declared by {{ var }} also has the concept of scope. If var is declared in the top-level scope, then the internal scope can be directly obtained by obtaining the variable.

We passed{{- range $k,$v := . }}Iterate through every KV in the map, this writing method is similar to Golang'sfor-range

5. Name the template

In the Go template engine, naming a template refers to storing the template in a template set by giving it a unique name so that the template can be referenced and executed later by that name.

By using named templates, you can organize a set of relevant template logic together and easily call and reuse them when needed. This is very useful for building complex template structures and improving the maintainability of templates.

When writing complex templates, we always hope to abstract common templates, so we need to use named templates for reuse.

This section will learn how to use named templates for abstract multiplexing based on the case of K8sPod templates.

Let's take a look at the doc

{{template "name"}}
Template with the specified name is executed with no data.
{{template "name" pipeline}}
A template with a specified name is executed as pipeline result.

passdefineDefine the template name

{{ define "container" }}
Template
{{ end }}

passtemplateUsing templates

{{ template "container" }}

We are using the incoming name, which actually defines the name of the template

Case: We hope to abstract the Pod container, pass in data to generate containers through code, and avoid repeated writing of yaml.

var pod = `
apiVersion: v1
kind: Pod
metadata:
  name: "test"
spec:
  containers:
{{- template "container" .}}
`
var container = `
{{ define "container" }}
    - name: {{ .Name }}
      image: "{{ .Image}}"
{{ end }}
`
func main() {
    tpl := (("demo").Parse(pod))
    (container)
    (, "demo", struct {
        Name  string
        Image string
    }{
        "nginx",
        "1.14.1",
    })
}
// OutputapiVersion: v1
kind: Pod
metadata:
  name: "test"
spec:
  containers:
    - name: nginx    
      image: "1.14.1"

TPL can parse multiple templates and define the templates in different templates. Use ExecuteTemplate to pass in the template name to specify the parsing template. exist{{- template "container" .}}Object data can be passed in.

In actual development, we often do not print output. Different requirements can be selected when executing Execute.. Often we prefer to write to files.

Common functions

func Must(t *Template, err error) *Template

Must is a helper function that encapsulates calls to the function that returns (Template, error) and panics when the error is not nil. It is intended for template initialization.

// parse the specified file// Example: ParseFiles(./)func ParseFiles(filenames ...string) (*Template, error)
// parse matching files// Example: ParseGlob(/data/*.tpl)func ParseGlob(pattern string) (*Template, error)

These two functions help us parse templates in the file. In most cases, we write the templates in.tplin the ending file. The corresponding file is parsed through different parsing rules.

func (t *Template) Templates() []*Template

Returns the slice of the template related to the current t, including t itself.

func (t *Template) ExecuteTemplate(wr , name string, data any) error

Pass in the template name and execute the specified template.

If an error occurs while executing a template or writing its output, execution will stop, but some results may have been written to the output writer. Templates can be executed safely in parallel, but if parallel executions share a Writer, the output may be interleaved.

func (t *Template) Delims(left, right string) *Template

Modify the delimiter in the template, you can change the{{}}Modified to<>

func (t *Template) Clone() (*Template, error)

clone returns a copy of the template, including all associated templates. Adding templates to the clone copy will not affect the original template. So we can use it for public templates and get different copies through clone.

7. Summary

Golang's template improves code reusability: The template engine allows you to create reusable template fragments. By extracting the duplicate template logic into separate templates and calling them when needed, you can reduce code duplication and improve code maintainability and scalability.Many code-gens use template + cobra to generate multiplexed code and template code, which is conducive to freeing our hands.

The above is the detailed content of Golang using the Template template to dynamically generate text. For more information about Go Template, please follow my other related articles!