Part 008 Go Templates Calling Useful Functions

One of the powerful things in the Go template processor is the ability to call functions inside the code. This is one of the things that confused me when I first used it. Hopefully this example will help alleviate the confusion.

An example (ex8.go):

package main

// (C) Philip Schlump, 2013.
// MIT Licensed, see LICENSE.txt

// Example of calling a function from a template

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strings"
)

// -----------------------------------------------------------------------------
//
// Function: literal
//
// Description:  Converts the input string into a template.HTML so that it will
// ***NOT*** get escaped in processing.  This can be a dangerous function
// because it can allow raw code to be placed in your output.
//
// However, warnings aside.  It is also useful.  For example:
//		literal "<!--[if lt IE 9]><script src='js/html5shif.js'></script><![endif]-->"
// Allows for use of Twitter Bootstrap with IE8!
//
// -----------------------------------------------------------------------------
func literal(b string) template.HTML {
	return template.HTML(b)
}

// -----------------------------------------------------------------------------
//
// Function: cleanPath
//
// Description cleanPath helps to scrub a path so that it will not allow the
// inclusion of files that are not under the current directory.
//
// Test File: test_cleanPath.go
//
// Called From: include
//
// -----------------------------------------------------------------------------
func cleanPath(fn string) string {
	// top, _ = os.Getwd()
	// sanitize the "fn" to prevent /etc/passwd as an include!
	// or ../../../../../../../../../etc/passwd
	fn = path.Clean(fn)
	fn = strings.Replace(fn, "../", "", -1)
	fn = path.Clean(fn)
	var re = regexp.MustCompile("^/")
	fn = re.ReplaceAllString(fn, "./")
	return fn
}

// -----------------------------------------------------------------------------
//
// Function: include
//
// Description: This function includes the body of the passed file.
//
// Example of Usage:
//		include "some-file.txt"
//
// -----------------------------------------------------------------------------
func include(fn string) string {

	fn = cleanPath(fn)

	// open input file
	fi, err := os.Open(fn)
	if err != nil {
		panic(err)
	}
	// close fi on exit and check for its returned error
	defer func() {
		if err := fi.Close(); err != nil {
			panic(err)
		}
	}()

	var rv []byte
	buf := make([]byte, 8192)
	for {
		// read a chunk
		n, err := fi.Read(buf)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if n == 0 {
			break
		}

		rv = append(rv, buf[:n]...)
	}

	return string(rv)
}

func readInFile(path string) string {
	file, err := ioutil.ReadFile(path)
	if err != nil {
		return ""
	}
	return string(file)
}

func readInJson(path string) map[string]interface{} {
	var jsonData map[string]interface{}
	file, err := ioutil.ReadFile(path)
	if err != nil {
		return jsonData
	}
	json.Unmarshal(file, &jsonData)
	return jsonData
}

func main() {

	if len(os.Args) != 3 {
		fmt.Printf("Usage: ex8 TemplateFileName Data.JSON\n")
		os.Exit(1)
	}

	var tmpl_fn string = os.Args[1]
	var data_fn string = os.Args[2]

	tmpl := readInFile(tmpl_fn)
	person := readInJson(data_fn)

	funcMap := template.FuncMap{
		"literal":   literal,
		"include":   include,
		"cleanPath": cleanPath,
	}

	t := template.New("file-template").Funcs(funcMap)
	t, err := t.Parse(tmpl)
	if err != nil {
		panic(err)
		os.Exit(1)
	}

	err = t.ExecuteTemplate(os.Stdout, "file-template", person)
	if err != nil {
		panic(err)
		os.Exit(1)
	}
}


In this example the function bob is called from inside the template.

To run this you can

$ go run ex8.go ex8.tmpl ex8.json


To compile it and run it

$ go build ex8.go
$ ./ex8 ex8.tmpl ex8.json

The JSON input is (ex8.json):

{
	"Name":"example name for ex8 demo",
	"FileName":"ex8a.txt"
}


The template input is (ex8.tmpl):



My name is: {{.Name}}.

{{literal "<!-- include comment in HTML output -->"}}

{{include "ex8.txt"}}

Note that the above lines should have the less than and
greater than symbols escaped in the final output

{{include "ex8.txt" | literal }}

And not escaped - showing as less than symbols.

{{.Name}}

{{.FileName | include }}




The output you should expect from running it is (ex8.out):



My name is: example name for ex8 demo.

<!-- include comment in HTML output -->


&lt;&lt;&lt; This is ex8.txt &gt;&gt;&gt;




Note that the above lines should have the less than and
greater than symbols escaped in the final output


<<< This is ex8.txt >>>




And not escaped - showing as less than symbols.

example name for ex8 demo


This is the file ex8a.txt 

It was included in the output






Code tested on: Ubuntu 12.04, Mac 10.8, Windows 7 in go 1.1.2


•       •       •       •       •       •

Summary: # of Words: 195
Author: Philip J. Schlump
Published On: 2013-10-23

Download code from this articles in .tar.gz for Mac/Linux/Unix or .zip with CR/LF for Windows format.

 

Before You Go....

Have you read "Unintend Consinsequences"?

"I laughed so hard it hurt..."
    Rod Brown

"Incredibly funny! Incredibly true!"
    Tad Stevens