Part 012 Go Template Function With Multiple Types

Note: These functions are now included as a part of the standard set of functions in the Go templates. I have left this blog post up because it was critical to my understanding of just how extensible the Go template system is.

Another useful function is gt. It compares two values of different types and returns true if the first is larger than the second values.

This is an example of using reflection to check the types. Then comparing in a type safe way for different types.

An example (ex12.go):

package main

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

// Example of calling a function that has an array of interface as a parameter.

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"os"
	"reflect"
)

// -----------------------------------------------------------------------------
//
// Function: "eq"
// I didn't write "eq" - I think that it is amazing!
//
// -----------------------------------------------------------------------------
//
// From:  https://groups.google.com/forum/#!topic/golang-nuts/OEdSDgEC7js
// By:Russ Cox
//
//		Comment by:
//		Rob 'Commander' Pike
//		Date: 4/4/12
//
//		I would like to point out the loveliness of Russ's eq function. The
//		beauty is concentrated in the range loop, where x==y is an interface
//		equality check that verifies type and value equality while avoiding
//		allocation. It is enabled by the "case string, int, ..." etc. line,
//		which does the type check but leaves x of interface type.
//		By the way, there could be more types on that case; if you borrow this
//		function, be sure to add the types you need.
//
//		-rob
//
// I have found this function to be useful in my templates.
// I install it as "eq".
//
// eq reports whether the first argument is equal to one of the suceeding arguments.
//
func eq(args ...interface{}) bool {
	if len(args) == 0 {
		return false
	}
	x := args[0]
	switch x := x.(type) {
	case string, int, int64, byte, float32, float64:
		for _, y := range args[1:] {
			if x == y {
				return true
			}
		}
		return false
	}

	for _, y := range args[1:] {
		if reflect.DeepEqual(x, y) {
			return true
		}
	}
	return false
}

// ------------------------------------------------------------------------------------------------------------------
//
// Function: "gt"
// I didn't write "gt" - This is from the same thread as "eq".
//
// ------------------------------------------------------------------------------------------------------------------
//
// From:  https://groups.google.com/forum/#!topic/golang-nuts/OEdSDgEC7js
//
// gt returns true if the arguments are of the same type (with int8 and int64 as the same type) and
// the first argument is greater than the second. This is only defined on string, intX, uintX and floatX all
// other types return false.
//
func gt(a1, a2 interface{}) bool {
	switch a1.(type) {
	case string:
		switch a2.(type) {
		case string:
			return reflect.ValueOf(a1).String() > reflect.ValueOf(a2).String()
		}
	case int, int8, int16, int32, int64:
		switch a2.(type) {
		case int, int8, int16, int32, int64:
			return reflect.ValueOf(a1).Int() > reflect.ValueOf(a2).Int()
		}
	case uint, uint8, uint16, uint32, uint64:
		switch a2.(type) {
		case uint, uint8, uint16, uint32, uint64:
			return reflect.ValueOf(a1).Uint() > reflect.ValueOf(a2).Uint()
		}
	case float32, float64:
		switch a2.(type) {
		case float32, float64:
			return reflect.ValueOf(a1).Float() > reflect.ValueOf(a2).Float()
		}
	}
	return false
}

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: ex12 TemplateFileName Data.JSON\n")
		os.Exit(1)
	}

	var tmpl_fn string = os.Args[1] // Look for *literal* in the template
	var data_fn string = os.Args[2]

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

	funcMap := template.FuncMap{
		"eq": eq,
		"gt": gt,
	}

	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 ex12.go ex12.tmpl ex12.json


To compile it and run it

$ go build ex12.go
$ ./ex12 ex12.tmpl ex12.json

From this input (ex12.tmpl):


{{gt "a" "b"}} -- Should be false

{{gt 12 22}} -- Should be false

{{gt "c" "b"}} -- Should be true

{{gt 42 22}} -- Should be true

{{gt "z" "z"}} -- Should be false

{{gt 42 42}} -- Should be false

{{gt .Data1 .Data2}} -- {{.Data1}} is gt than {{.Data2}} is false

{{gt .Data2 .Data1}} -- {{.Data2}} is gt than {{.Data1}} is true




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


false -- Should be false

false -- Should be false

true -- Should be true

true -- Should be true

false -- Should be false

false -- Should be false

false -- [DATA 1] is gt than [DATA 2] is false

true -- [DATA 2] is gt than [DATA 1] is true




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


•       •       •       •       •       •

Summary: # of Words: 275
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