Tuesday, October 25, 2011

Go Templates - Part 2


There is some documentation, which I consider inadequate, about the template packages at http://golang.org/pkg/text/template/ and http://golang.org/pkg/html/template/ - the html sub-package having additional security features against attacks like code injection and should be the one to be used when generating content for the web. (Here, I might use either as their interfaces are the same.) It contains definitions for many of the structures and formats that are parseable by the template package. So here I shall try to only supplement that with a few examples because I do not find the available documentation clear. First though, let me try to explain a few thoughts on terminology.

pipelines

Unix users will already be aware of ‘piping’ data. Many commands produce a textual output - a stream of text. If you type the command ls at the prompt, you will get a list of files in the directory. This can translated as get a list of all files in the current directory, and then pipe it to the default output which in this case is the command line screen. At the unix command line the pipe ‘|’ symbol which is the vertical line, allows you to ‘pipe’ the text stream to another command. For example,

ls | grep "a"
will get all the files in the local directory, and the pipe it to the grep command which filters only those files that conain the letter ‘a’ in it. Of course you could further pipe this text stream to another command. ls | grep "a" | grep "o"
, will list only those files with both an ‘a’ and an ‘o’ in it.

In Go, every such text stream is called a pipeline and it can also be piped to other commands. In the example below we are simply printing two pipelines with constant strings. Note that these constant strings which are within {{ }} is different from static text outside of braces - the static text is copied as is without changes always. The pipeline data on the other hand could be manipulated, even though in this particular example we are not doing anything.

Full program - illustrating a pipeline
package main

import (
    "text/template"
    "os"
    )

func main() {
    t := template.New("template test") 
    t = template.Must(t.Parse("This is just static text. \n{{\"This is pipeline data - because it is evaluated within the double braces.\"}} {{`So is this, but within reverse quotes.`}}\n")); 
    t.Execute(os.Stdout, nil)
}

This is just static text.
This is pipeline data - because it is evaluated within the double braces. So is this, but within reverse quotes.


template if-else-end

The syntax of if-else construct is similar to normal if-else statements - in Go, if the pipeline is empty, then the if condition evaluates to false. The following example illustrates the two.

Full program - illustrating template if-else
package main

import (
    "os"
    "text/template"
    )

func main() {
    tEmpty := template.New("template test")
    tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} Will not print. {{end}}\n")) //empty pipeline following if
    tEmpty.Execute(os.Stdout, nil)

    tWithValue := template.New("template test")
    tWithValue = template.Must(tWithValue.Parse("Non empty pipeline if demo: {{if `anything`}} Will print. {{end}}\n")) //non empty pipeline following if condition
    tWithValue.Execute(os.Stdout, nil)

    tIfElse := template.New("template test")
    tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} Print IF part. {{else}} Print ELSE part.{{end}}\n")) //non empty pipeline following if condition
    tIfElse.Execute(os.Stdout, nil)
}

Empty pipeline if demo:
Non empty pipeline if demo: Will print.
if-else demo: Print IF part.


The dot - .

The dot (.) is used in Go templates to refer to the current pipeline. This is similar to a cursor used with database access to indicate the current row that is being used among all the rows returned by a query. Or if you are used to Java and C++, you could think it is something like the this operator - well, not really, but kinda.

Along with certain constructs the value of the ‘dot’ gets automatically set to the current value in the pipeline. You can therefore refer to its value as {{.}}.

template with-end

The with statement sets the value of dot with the value of the pipeline. If the pipeline is empty, then whatever is between the with-end block is skipped.

Full program - illustrating template if-else
package main

import (
    "os"
    "text/template"
    )

func main() {
    t, _ := template.New("test").Parse("{{with `hello`}}{{.}}{{end}}!\n")
    t.Execute(os.Stdout, nil)

    t1, _ = template.New("test").Parse("{{with `hello`}}{{.}} {{with `Mary`}}{{.}}{{end}}{{end}}!\n") //when nested, the dot takes the value according to closest scope.
    t1.Execute(os.Stdout, nil)
}

hello!
hello Mary!

template variables

You can create local variables for the pipelines within the template by prefixing the variable name with a "$" sign. Variable names have to be composed of alphanumeric characters and the underscore. In the example below I have used a few variations that work for variable names.

Full program - illustrating template if-else
package main

import (
    "os"
    "text/template"
    )

func main() {
    t := template.Must(template.New("name").Parse("{{with $3 := `hello`}}{{$3}}{{end}}!\n"))
    t.Execute(os.Stdout, nil)

    t1 := template.Must(template.New("name1").Parse("{{with $x3 := `hola`}}{{$x3}}{{end}}!\n"))
    t1.Execute(os.Stdout, nil)

    t2 := template.Must(template.New("name2").Parse("{{with $x_1 := `hey`}}{{$x_1}} {{.}} {{$x_1}}{{end}}!\n"))
    t2.Execute(os.Stdout, nil)
}

hello!
hola!
hey hey hey!


Predefined template functions

There are also a few predefined template functions that you can employ within your code. I shall illustrate the printf function which works similar to the function fmt.Sprintf.

Full program - illustrating template if-else
package main

import (
    "os"
    "text/template"
    )

func main() {
    t := template.New("test")
    t = template.Must(t.Parse("{{with $x := `hello`}}{{printf `%s %s` $x `Mary`}}{{end}}!\n"))
    t.Execute(os.Stdout, nil)
}

hello Mary!

1 comment:

  1. Thanks for your awesome tutorials.

    In the "template with-end" section, the second "=" should be ":=".

    ReplyDelete

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.

Note: Only a member of this blog may post a comment.