These steps cover writing and running your application:
- From your terminal/console application, create a new directory called chapter1/templates.
- Navigate to this directory.
- Copy tests from https://github.com/agtorre/go-cookbook/tree/master/chapter1/templates, or use this as an exercise to write some of your own!
- Create a file called templates.go with the following contents:
package templates
import (
"os"
"strings"
"text/template"
)
const sampleTemplate = `
This template demonstrates printing a {{ .Variable |
printf "%#v" }}.
{{if .Condition}}
If condition is set, we'll print this
{{else}}
Otherwise, we'll print this instead
{{end}}
Next we'll iterate over an array of strings:
{{range $index, $item := .Items}}
{{$index}}: {{$item}}
{{end}}
We can also easily import other functions like
strings.Split
then immediately used the array created as a result:
{{ range $index, $item := split .Words ","}}
{{$index}}: {{$item}}
{{end}}
Blocks are a way to embed templates into one another
{{ block "block_example" .}}
No Block defined!
{{end}}
{{/*
This is a way
to insert a multi-line comment
*/}}
`
const secondTemplate = `
{{ define "block_example" }}
{{.OtherVariable}}
{{end}}
`
// RunTemplate initializes a template and demonstrates a
// variety of template helper functions
func RunTemplate() error {
data := struct {
Condition bool
Variable string
Items []string
Words string
OtherVariable string
}{
Condition: true,
Variable: "variable",
Items: []string{"item1", "item2", "item3"},
Words:
"another_item1,another_item2,another_item3",
OtherVariable: "I'm defined in a second
template!",
}
funcmap := template.FuncMap{
"split": strings.Split,
}
// these can also be chained
t := template.New("example")
t = t.Funcs(funcmap)
// We could use Must instead to panic on error
// template.Must(t.Parse(sampleTemplate))
t, err := t.Parse(sampleTemplate)
if err != nil {
return err
}
// to demonstrate blocks we'll create another template
// by cloning the first template, then parsing a second
t2, err := t.Clone()
if err != nil {
return err
}
t2, err = t2.Parse(secondTemplate)
if err != nil {
return err
}
// write the template to stdout and populate it
// with data
err = t2.Execute(os.Stdout, &data)
if err != nil {
return err
}
return nil
}
- Create a file called template_files.go with the following contents:
package templates
import (
"io/ioutil"
"os"
"path/filepath"
"text/template"
)
//CreateTemplate will create a template file that contains data
func CreateTemplate(path string, data string) error {
return ioutil.WriteFile(path, []byte(data),
os.FileMode(0755))
}
// InitTemplates sets up templates from a directory
func InitTemplates() error {
tempdir, err := ioutil.TempDir("", "temp")
if err != nil {
return err
}
defer os.RemoveAll(tempdir)
err = CreateTemplate(filepath.Join(tempdir, "t1.tmpl"),
`Template 1! {{ .Var1 }}
{{ block "template2" .}} {{end}}
{{ block "template3" .}} {{end}}
`)
if err != nil {
return err
}
err = CreateTemplate(filepath.Join(tempdir, "t2.tmpl"),
`{{ define "template2"}}Template 2! {{ .Var2 }}{{end}}
`)
if err != nil {
return err
}
err = CreateTemplate(filepath.Join(tempdir, "t3.tmpl"),
`{{ define "template3"}}Template 3! {{ .Var3 }}{{end}}
`)
if err != nil {
return err
}
pattern := filepath.Join(tempdir, "*.tmpl")
// Parse glob will combine all the files that match
// glob and combine them into a single template
tmpl, err := template.ParseGlob(pattern)
if err != nil {
return err
}
// Execute can also work with a map instead
// of a struct
tmpl.Execute(os.Stdout, map[string]string{
"Var1": "Var1!!",
"Var2": "Var2!!",
"Var3": "Var3!!",
})
return nil
}
- Create a file called html_templates.go with the following content:
package templates
import (
"fmt"
"html/template"
"os"
)
// HTMLDifferences highlights some of the differences
// between html/template and text/template
func HTMLDifferences() error {
t := template.New("html")
t, err := t.Parse("<h1>Hello! {{.Name}}</h1>n")
if err != nil {
return err
}
// html/template auto-escapes unsafe operations like
// javascript injection this is contextually aware and
// will behave differently
// depending on where a variable is rendered
err = t.Execute(os.Stdout, map[string]string{"Name": " <script>alert('Can you see me?')</script>"})
if err != nil {
return err
}
// you can also manually call the escapers
fmt.Println(template.JSEscaper(`example
<example@example.com>`))
fmt.Println(template.HTMLEscaper(`example
<example@example.com>`))
fmt.Println(template.URLQueryEscaper(`example
<example@example.com>`))
return nil
}
- Create a new directory named example.
- Navigate to example.
- Create a main.go file with the following contents and ensure that you modify the tempfiles imported to use the path you set up in step 2:
package main
import "github.com/agtorre/go-cookbook/chapter1/templates"
func main() {
if err := templates.RunTemplate(); err != nil {
panic(err)
}
if err := templates.InitTemplates(); err != nil {
panic(err)
}
if err := templates.HTMLDifferences(); err != nil {
panic(err)
}
}
- Run go run main.go.
- You may also run these:
go build
./example
You should see (with a different path) the following output:
$ go run main.go
This template demonstrates printing a "variable".
If condition is set, we'll print this
Next we'll iterate over an array of strings:
0: item1
1: item2
2: item3
We can also easily import other functions like strings.Split
then immediately used the array created as a result:
0: another_item1
1: another_item2
2: another_item3
Blocks are a way to embed templates into one another
I'm defined in a second template!
Template 1! Var1!!
Template 2! Var2!!
Template 3! Var3!!
<h1>Hello! <script>alert('Can you see
me?')</script></h1>
example x3Cexample@example.comx3E
example <example@example.com>
example+%3Cexample%40example.com%3E
- If you copied or wrote your own tests, go up one directory and run go test, and ensure all tests pass.