October 20, 2021 7 min read2011
Templates are files that define a specific pattern and give room for automation, be it a simple text file or an HTML file for a webpage. If it provides space for customization, it is a template.
You can write programming logic to parse simple templates, but as the level of customization in your template increases, the programming logic you need to customize increases, making it less feasible to write template parsing logic.
Programming languages and web development frameworks support template parsing out of the box or provide libraries for template parsing.
In this article, we will explore features in the Go programming language that support template parsing. We can use these methods to parse any file extension, but we will use only text files in this article.
This guide will also only display Go code and the least amount of template (text) needed to understand the Go code.
Before beginning our tutorial, you should have:
- A w orking knowledge of Go
- Go >1.x runtime installed on your machine
You can also c lone the guide’s repository to access the complete template files or enter:
git clone https://github.com/Bamimore-Tomi/go-templates-guide.git
In this section, we’ll explore the features of the text/template
package in Go.
To work with templates, you must parse them into your Go program.
The text/template
standard library provides the functions needed to parse our program:
package main
import (
"log"
"os"
"text/template"
)
// Prints out the template without passing any value using the text/template package
func main() {
template, err := template.ParseFiles("template-01.txt")
// Capture any error
if err != nil {
log.Fatalln(err)
}
// Print out the template to std
template.Execute(os.Stdout, nil)
}
//OUTPUT
// Hi <no value>
// You are welcome to this tutorial
The program above prints a template file called template-01.txt
. The template
variable holds the content of the file. To print out the file to Stdout
, we call the Execute
method.
For parsing multiple files at once, the ParseGlob
function is useful:
package main
import (
"log"
"os"
"text/template"
)
// Parse all the files in a certain directory
func main() {
// This function takes a pattern. It can be a folder
temp, err := template.ParseGlob("templates/*")
if err != nil {
log.Fatalln(err)
}
// Simply calling execute parses the first file in the directory
err = temp.Execute(os.Stdout, nil)
if err != nil {
log.Fatalln(err)
}
// Or you can execute a particular template in the directory
err = temp.ExecuteTemplate(os.Stdout, "template-03.txt", nil)
if err != nil {
log.Fatalln(err)
}
// Calling a template not in the directory will produce an error
err = temp.ExecuteTemplate(os.Stdout, "template-04.txt", nil)
if err != nil {
log.Fatalln(err)
}
}
With this code, we parsed all the files in the templates/
directory to our program. To execute any of the templates parsed, we call the ExecuteTemplate
method on the result of ParseGlob
.
The Execute
method is where we parse data into the template(s). Here is the template-04.txt
file:
Hello {{.}}
You are doing great. Keep learning.
Do not stop {{.}}
The {{.}}
tells the text/template
package where to place the data that is passed into the template. In this template, we want to set the data in two places: line 1 and line 4:
package main
import (
"log"
"os"
"text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-04.txt"))
}
func main() {
// Execute myName into the template and print to Stdout
myName := "Oluwatomisin"
err := temp.Execute(os.Stdout, myName)
if err != nil {
log.Fatalln(err)
}
}
// Hello Oluwatomisin
// You are doing great. Keep learning.
// Do not stop Oluwatomisin
Here, we use slightly different syntax to initialize the template. temp.Execute
takes an io.writer
and data interface{}
, which is myName
in this case.
You can also pass in more complicated data structures in your template; the only change to do this is how you access the structures inside the template file, which we will discuss in the next section.
We can also initialize variables right inside a template. Check the example in template-05.txt
:
Hello {{.Name}}
{{$aTrait := .Trait}}
You are {{$aTrait}}
Notice a change in how we use data inside the template. template.Execute
takes a data interface{}
argument, which means we can execute a struct
in a template:
package main
import (
"log"
"os"
"text/template"
)
type PersonTrait struct {
Name string
Trait string
}
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-05.txt"))
}
func main() {
// Execute person into the template and print to Stdout
person := PersonTrait{Name: "Oluwatomisin", Trait: "a great writer"}
err := temp.Execute(os.Stdout, person)
if err != nil {
log.Fatalln(err)
}
}
// Hello Oluwatomisin
// You are a great writer
In this example, we execute the PersonTrait
struct in the template. This way, you can execute any data type inside a template.
The text/template
package also allows you to run loops inside a template. In template-06.txt
, we will list some cute pets:
Animals are cute; some cute animals are:
{{range .}}
{{.}}
{{end}}
In the program, we must execute a slice of cute pets in the template:
package main
import (
"log"
"os"
"text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
// Execute cuteAnimals into the template and print to Stdout
cuteAnimals := []string{"Dogs", "Cats", "Mice", "Fish"}
err := temp.Execute(os.Stdout, cuteAnimals)
if err != nil {
log.Fatalln(err)
}
}
// Animals are cute, some cute animals are:
// Dogs
// Cats
// Mice
// Fish
We can also loop through a map
if needed:
Animals are cute, some cute animals are:
{{range $key, $val := .}}
{{$key}} , {{$val}}
{{end}}
Then, we can create and execute the map
in the program:
package main
import (
"log"
"os"
"text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the response of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
// Execute cuteAnimalsSpecies into the template and print to Stdout
cuteAnimalsSpecies := map[string]string{
"Dogs": "German Shepherd",
"Cats": "Ragdoll",
"Mice": "Deer Mouse",
"Fish": "Goldfish",
}
err := temp.Execute(os.Stdout, cuteAnimalsSpecies)
if err != nil {
log.Fatalln(err)
}
}
// Animals are cute, some cute animals are:
// Cats , Ragdoll
// Dogs , German Shepherd
// Fish , Goldfish
// Mice , Deer Mouse
To add more customization to our templates, we can use conditional statements. To use conditionals, we must call the comparison functions inside the template. In this example, we can check whether a random integer is less or greater than 200:
{{if (lt . 200)}}
Number {{.}} is less than 200
{{else}}
Number {{.}} is greater than 200
{{end}}
The (lt . 200)
syntax is how we compare the random integer value using lt
. Other operators include:
lt
for the less-than operatorgt
for the greater-than operatoreq
for the equals-o operatorne
for the not equals-to operatorle
for the less-than-or-equal-to operatorge
for the greater-than-or-equal-to operator
Now, we can generate the random value in our main program:
package main
import (
"log"
"math/rand"
"os"
"text/template"
"time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
// Generate random number between 100 and 300
rand.Seed(time.Now().UnixNano())
min := 100
max := 300
// Execute myNumber into the template and print to Stdout
myNumber := rand.Intn((max-min)+1) + min
err := temp.Execute(os.Stdout, myNumber)
if err != nil {
log.Fatalln(err)
}
}
// Number 141 is less than 200
The text/template
package also provides a way to execute custom functions inside a template. A famous example is converting timestamps into other date formats:
Hi,
Time before formatting : {{.}}
Time After formatting : {{formatDate .}}
The template displays the time before and after parsing with the formatDate
function. Observe the syntax that can call the custom function:
package main
import (
"log"
"os"
"text/template"
"time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.New("template-07.txt").Funcs(funcMap).ParseFiles("template-08.txt"))
}
// Custom function must have only 1 return value, or 1 return value and an error
func formatDate(timeStamp time.Time) string {
//Define layout for formatting timestamp to string
return timeStamp.Format("01-02-2006")
}
// Map name formatDate to formatDate function above
var funcMap = template.FuncMap{
"formatDate": formatDate,
}
func main() {
timeNow := time.Now()
err := temp.Execute(os.Stdout, timeNow)
if err != nil {
log.Fatalln(err)
}
}
// Hi,
// Time before formatting : 2021-10-04 18:01:59.6659258 +0100 WAT m=+0.004952101
// Time After formatting: 09-04-2021
The layout format must follow the timestamp format — in this case, Jan 2 15:04:05 2006 MST
. Check the method’s documentation for more information.
Here, however, we use template.FuncMap
to map a string
to the custom function. The function will then be referenced using the string inside the template.
Whenever you see your favorite newsletter that is customized to you, chances are an email template and a templating processing package like text/template
in Go made it look that way. The following program will customize a newsletter from the various techniques and methods in the text/template
package.
package main
import (
"fmt"
"os"
"text/template"
"time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
// template.Must takes the reponse of template.ParseFiles and does error checking
temp = template.Must(template.New("template-08.txt").Funcs(funcMap).ParseFiles("template-08.txt"))
}
// Custom function must have only 1 return value, or 1 return value and an error
func formatDate(timeStamp time.Time) string {
//Define layout for formatting timestamp to string
// return timeStamp.Format("01-02-2006")
return timeStamp.Format("Mon, 02 Jan 2006")
}
// Map name formatDate to formatDate function above
var funcMap = template.FuncMap{
"formatDate": formatDate,
}
type customer struct {
Username string
Subscriptions []string
LastLoginDays int
SubscriptionDeadline time.Time
}
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
customers := []customer{
{
Username: "wisman",
Subscriptions: []string{"Kikat", "Lollipop", "Marshmallow", "Oreo", "Jelly Bean"},
LastLoginDays: 10,
SubscriptionDeadline: time.Now(),
},
{
Username: "berry blings",
Subscriptions: []string{"Honeycomb", "Gingerbread", "Nougat", "Froyo", "Donut"},
LastLoginDays: 5,
SubscriptionDeadline: time.Date(2023, 3, 15, 5, 5, 5, 3, time.UTC),
},
{
Username: "savy",
Subscriptions: []string{"Honeycomb", "Gingerbread", "Nougat", "Froyo", "Donut"},
LastLoginDays: 5,
SubscriptionDeadline: time.Date(2023, 6, 15, 5, 5, 5, 3, time.UTC),
},
}
for _, user := range customers {
// Create a new file for each customer
file, err := os.Create(fmt.Sprintf("output/%v.txt", user.Username))
check(err)
// Execute the template for each user into their respective files
err = temp.Execute(file, user)
check(err)
}
}
The program executes an email template using the customers’ data.
Hi {{.Username}},
We notice you last logged in {{.LastLoginDays}} days ago.
Your current subscriptions:
{{range $subscription := .Subscriptions}}{{$subscription}}
{{end}}
expires on {{formatDate .SubscriptionDeadline}}.
We have seen a range of features in the text/template
package with usage examples. All the features implemented here are the same for the html/template
package. Outputs generated from html/template
are safe from cross-site scripting by providing auto-escaping and context-sensitive sanitization of inputs.
When using templates in the web context where the output is HTML, use the html/template
package.