GO Templates: Principles and Usage

April 9, 2020 Templates Html Text Sources


GO Templates: Principles and Usage

Packages text/template and html/template are part of the Go standard library. Go templates are used in many Go-programmed software — Docker, Kubernetes, Helm. Many third-party libraries are integrated with Go templates, for example Echo. Knowing Go template syntax is very useful.

This article consists of text/template package documentation and a couple of author’s solutions. After describing Go template syntax, we’ll dive into text/template and html/template sources.

Go templates are active, which means flow-control instructions such as if, else, and range cycles are available.

Go is a strictly typed language, but templates work with all data types, thanks to the reflect package developers.

Let’s take a brief look at Go template syntax basics.

Template Calls

All template instructions are set between {{ and }} symbols. Any other text is plain text that is simply printed to output without any changes.

Template Context

We have Execute and ExecuteTemplate Go functions to run templates. Both of them have a data interface{} parameter.

Execute(wr io.Writer, data interface{}) error
ExecuteTemplate(wr io.Writer, name string, data interface{}) error

The data parameter here is the default template data. It can be accessed from templates as .. The following code prints the default data:

{{ . }}

Let’s call the default data the current template context. Some template instructions can change the template context.

Comments

{{/* comment */}}

Condition Operator

{{if condition}} T1 {{end}}

If condition is 0, "", nil, or an empty array/slice, the condition is processed as false, and T1 won’t execute. Otherwise, T1 will execute.

Variations with else, else if:

{{if condition}} T1 {{else}} T0 {{end}}
{{if condition1}} T1 {{else if condition2}} T0 {{end}}

Range Cycles

One can iterate arrays, slices, maps, or channels.

In the following code, the T1 instruction will be executed on each iteration:

{{range pipeline}} T1 {{end}}
{{range pipeline}} T1 {{else}} T2 {{end}}

Key/value variables for each iteration can also be obtained:

{{range $key, $value := pipeline}}
{{ $key }}: {{ $value }}
{{end}}

With

{{with pipeline}} T1 {{end}}

If pipeline is equivalent to true (as described in the if explanation), T1 is executed, and the current template context is set to pipeline.

Templates

One can create a template using one of the following instructions:

{{block "name" pipeline}} T1 {{end}}
{{define "name" pipeline}} T1 {{end}}

To run a template:

{{template "name" pipeline}}

Map Elements, Struct Fields, Method Calls

Go templates can print data. For example, one can print a struct field or map value. Struct fields to be used in templates should be exported (started with a capital letter). Map keys can start with a lowercase letter.

All of them can be chained:

.Field1.Field2.key1
.key1.key2

One can use method calls in templates. Template methods should return one value or two values, and the last value should be an error. There is such a method check in the sources.

Go code:

type myType struct{}

func(m *myType) Method() string {
    return "123"
}

Template code:

.Method()

Standard Functions

There are two types of functions in Go templates — built-in and user-defined.

Function call syntax for every function is as follows:

funcname arg1 arg2 arg3

Standard functions list:

  • call funcLocation arg1 arg2 — function to call a function with arguments;
  • index x 1 2 3 — obtaining a slice/array/map element;
  • slice x 1 2 — slicing slice/array — s[1:2];
  • len x — obtaining length of slice/array/map;
  • print, printf, println — explicit printing of data;

Boolean operators also work as functions:

  • eq arg1 arg2arg1 == arg2
  • ne arg1 arg2arg1 != arg2
  • lt arg1 arg2arg1 < arg2
  • le arg1 arg2arg1 <= arg2
  • gt arg1 arg2arg1 > arg2
  • ge arg1 arg2arg1 >= arg2

Values can be chained to a function with the | operator. Such values become the last function argument:

{{"output" | printf "%q"}}

User-Defined Functions

One can define any function in Go to use it later in templates.

Go code of a last function which helps to check if the iterated element is the last in a list:

tempTemplate := template.New("main").Funcs(
    template.FuncMap{
        "last": func(x int, a interface{}) bool {
            return x == reflect.ValueOf(a).Len()-1
        },
    },
)

Using last in a template:

{{ $allKeywords := .Data.Keywords }}
{{ range $k,$v := .Data.Keywords}}
{{ $v }}{{ if ne (last $i $allKeywords) }},{{ end }}
{{ end }}

Internals

Go templates use the reflect package to work with any data type. For example, range sources in text/template:

func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
    // ...
    switch val.Kind() {
    case reflect.Array, reflect.Slice:
        // ...
    case reflect.Map:
        // ...
    case reflect.Chan:
        // ...
    }
}

There is a logical branch for every iterated type. The same reflect approach is used in the evalField instruction sources.

html/template uses text/template. html/template is specifically designed to identify exactly which type of HTML content is processed by the template (HTML tag’s name, attribute, tag’s content, CSS content, URL). Based on this content identification, there are different escape solutions.

Tags:

Related Articles

4 Apr 2020

Principles of slice type in GO

Principles of slice type in GO

The Go blog describes how to use slices. Let’s take a look at slice internals.

Read More → Slice Allocation Sources
2 Apr 2020

Data handling in concurrent programs

Data handling in concurrent programs

In Go, we have goroutines functionality out of the box. We can run code in parallel. However, in our parallel running code we can work with shared variables, and it is not clear how exactly Go handles such situations.

Read More → Map Sources
2 Apr 2020

Principles of map type in GO

Principles of map type in GO

The map programming interface in Go is described in the Go blog. We just need to recall that a map is a key-value storage and it should retrieve values by key as fast as possible.

Read More → Map Sources
30 Mar 2020

Golang regexp: matching newline

Golang regexp: matching newline

Why regular expressions with dot (".") work differently in Go compared to PHP and JavaScript.

Read More → Regular Expressions Sources