I am trying to achieve a very simple thing in a Go template and failing!
The range action allows me to iterate through an array along with its zero-based index, as so:
{{range $index, $element := .Pages}}
Number: {{$index}}, Text: {{element}}
{{end}}
However, I am trying to output indices that start counting from 1. My first attempt failed:
Number: {{$index + 1}}
This throws an illegal number syntax: "+" error.
I looked into the go-lang official documentation and did not find anything particular regarding the arithmetic operation inside the template.
What am I missing?
You have to write a custom function to do this.
http://play.golang.org/p/WsSakENaC3
package main
import (
"os"
"text/template"
)
func main() {
funcMap := template.FuncMap{
// The name "inc" is what the function will be called in the template text.
"inc": func(i int) int {
return i + 1
},
}
var strs []string
strs = append(strs, "test1")
strs = append(strs, "test2")
tmpl, err := template.New("test").Funcs(funcMap).Parse(`{{range $index, $element := .}}
Number: {{inc $index}}, Text:{{$element}}
{{end}}`)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, strs)
if err != nil {
panic(err)
}
}
If you happen to be writing a Go template for use in consul-template, you may find their exposed arithmetic functions useful:
Number: {{add $index 1}}
Check the documentation about the built-in functions of text/template:
len can produce an integer from a string: {{ len "abc" }}
printf can produce a string of a given length from an integer: {{ printf "%*s" 3 "" }}
slice can truncate a string to a given length from an integer: {{ slice "abc" 0 2 }}
slice can truncate a string by a given length from an integer: {{ slice "abc" 1 }}
You can combine both to increment an integer:
{{ len (printf "a%*s" 3 "") }}
will produce:
4
Or to decrement an integer:
{{ len (slice (printf "%*s" 3 "") 1) }}
shows:
2
You can also define templates to reuse pieces of templates.
So we can define 1 argument functions with templates (see on the Go Playground):
{{ define "inc" }}{{ len (printf "%*s " . "") }}{{ end -}}
{{ define "op" }}{{.}} + 1 = {{ template "inc" . }}{{ end -}}
{{ template "op" 2 }}
{{ template "op" 5 }}
shows:
2 + 1 = 3
5 + 1 = 6
We can go further if the input of the template is an array of integers ([]int{4, 2, 7}):
{{ define "inc" }}{{ len (printf "%*s " . "") }}{{ end -}}
{{ define "op" }}{{.}} + 1 = {{ template "inc" . }}{{ end -}}
{{- range . -}}
{{ template "op" . }}
{{ end -}}
See full example on the Go Playground.
There's also way to just make a HTML list, however that won't fit for all cases.
<ol>
{{range $index, $element := .Pages}}
<li>Text: {{$element}}</li>
{{end}}
</ol>
And it can produce something like that
Text: Some page
Text: Some different page
Related
Given the below template:
{{ range $item := . }}
{{ if $item.IsA}}
Ok
{{ else }}
Fine
{{ end }}
{{ end }}
Done!
When I render it using:
t := template.New("test").Parse(_types)
text, err := t.Execute(&buffer, strct)
The output is something like:
!empty line
!empty line
Ok
!empty line
!empty line
Done!
This means that if I want to format the text correctly, I have to re-write it as
{{ range $item := .}}{{ if $item.IsA }}OK{{ else }}{{ end }}{{ end }}
Done!
Then I get something like:
Ok
Done!
Which is the desired output.
Writing the template the second way is very unreadable and messy. Is there any way that we can write the template with proper indentation but somehow configure rendering in such a way that the template placeholders would not be converted to new lines, or their indentation would be ignored (so that the desired output would be generated)?
Edit: using {- ""} even makes the whole thing worse! Why? Please consider the following:
{{- range $item := . }}
{{- if $item.IsA }}
{{- "How many spaces??" -}}OK
...
So let me put it in another way, is there any built-in post-processor available in golang for templates?
I came across this solution after playing with templates for a while. In order to make the template cleaner, we can do one trick. Templates do accepts custom functions and the trick is to define an indentation function:
const _indent = "___START___"
_funcs = template.FuncMap {
"Indent": func(indent int) string {
str := bytes.NewBufferString(_indent)
for i := 0; i < indent; i++ {
str.WriteString(" ")
}
return str.String()
},
}
We can include a character as indentation as well but here I only need spaces. Then we need a post processor:
func PostProcess(strBuffer bytes.Buffer) bytes.Buffer {
buffer := bytes.NewBufferString("")
lines := strings.Split(strBuffer.String(), "\n")
for _, line := range lines {
tmp := strings.TrimLeftFunc(line, func(r rune) bool {
return r == ' '
})
tmp = strings.TrimPrefix(tmp, _indent)
buffer.WriteString(tmp)
}
return *buffer
}
Now we can write the markup as:
{{ range $i := . }}
{{ if $i.IsOk }}
{{ 4 | Indent }}OK
...
and we can run the PostProcess function on the rendered text. My PostProcess function does not eliminate empty lines, but we can add it easily as well.
This is way more readable than the {{- " " -}} syntax.
So I'm using go template and I would like to be able to generate a configmap dynamically by getting the list of the variable from the template.
To be precise, let's say I have a file name test.yaml.tmpl that looks something like that:
car:
color: {{ .color }}
model: {{ .model }}
I would like to be able to generate an array in my go code containing [color model] or [.color .model] , I can work with the dots myself.
I looked at the differents options but I could not get the result I want, the closest I got was using template.Root.Nodes to extract the variable value but it's not perfect and may generate unwanted error in specific conditions.
Does someone has a clean way to generate an array of the template variable?
EDIT 1
I tried to use the tree, and that gives me this type of output:
&{test.yaml.tmpl test.yaml.tmpl ---
car:
color: {{ .color }}
model: {{ .model }}
0 ---
car:
color: {{ .color }}
model: {{ .model }}
[] <nil> [{8 995 45} {11 409 {{ 22} {0 0 0}] 1 [] map[] 0 0}
The issue is that I don't get to access the fields node the only methods available when accessing the tree are:
ErrorContext
Parse
Copy and it's methods
Mode
Name
ParseName
Root and the NodeType method
Still can't get the list of fields.
EDIT 2
When printing the tree.Root.Nodes I get the full yaml output with the variables to replace like that:
(*parse.ActionNode)(0xc00007f1d0)({{ .color }}),
(*parse.TextNode)(0xc00007f200)
Well you can user regex you know. ;P
package main
import (
"fmt"
"regexp"
)
var fieldSearch = regexp.MustCompile(`{{ \..* }}`)
func main() {
template := `
car:
color: {{ .color }}
model: {{ .model }}
`
res := fieldSearch.FindAll([]byte(template), -1)
for i := range res {
// this is just for readability you can replace this with
// magic numbers if you want
res[i] = res[i][len("{{ .") : len(res[i])-len(" }}")]
}
for _, v := range res {
fmt.Println(string(v))
}
}
Ok, so I used the link posted by #icza How to get a map or list of template 'actions' from a parsed template?and treated the returned string to get what I need, this resulted in the following:
func main() {
node := listNodeFields(t.Tree.Root)
var tNode []string
for _, n := range node {
r := n[len("{{.* .") : len(n)-len(" .*}}")]
if strings.Contains(n, "or ") {
r = r + " or"
}
tNode = append(tNode, r)
}
config := make(map[string]string)
for _, n := range tNode {
var value string
var present bool
if strings.Contains(n, " or") {
n = strings.Replace(n, " or", "", -1)
value, present = os.LookupEnv(n)
if !present {
fmt.Println("The variable " + value + " is not set but there is a default value in template " + t.Name() + "!")
}
} else {
value, present = os.LookupEnv(n)
if !present {
return nil, errors.New("The variable " + f + " is not set but exists in the template " + t.Name() + "!")
}
}
config[n] = value
}
}
func listNodeFields(node parse.Node) []string {
var res []string
if node.Type() == parse.NodeAction {
res = append(res, node.String())
}
if ln, ok := node.(*parse.ListNode); ok {
for _, n := range ln.Nodes {
res = append(res, listNodeFields(n)...)
}
}
return res
}
As I don't want to generate errors in case I'm using default value I added a specific treatment to not get errors from the missingkey=error option of the template.
I have this function.
func OrderedParamsFromUri(uri string) []string {
matches := pathParamRE.FindAllStringSubmatch(uri, -1)
result := make([]string, len(matches))
for i, m := range matches {
result[i] = m[1]
}
return result
}
I want to use this function inside my template to check if the returned result contains items or not.
I know I can do something like this:
( .OperationId | OrderedParamsFromUri | //here i want to check for the empty slice)
.OperationId => this is the argument.
I know I can check if the returned slice is empty or not using if not .returnedSlice
But how to combine these two?
Either
{{ if not (.OperationId | OrderedParamsFromUri) }}
empty
{{ else }}
{{ (.OperationId | OrderedParamsFromUri) }}
{{ end }}
Or
{{ if not (OrderedParamsFromUri .OperationId) }}
empty
{{ else }}
{{ (OrderedParamsFromUri .OperationId) }}
{{ end }}
https://play.golang.com/p/rkt7wP_vS4n
This question already has answers here:
Calling a template with several pipeline parameters
(10 answers)
Closed 5 years ago.
I have simple case, where a templates (text/templates) includes another like this
`index.html`
{{ template "image_row" . }}
`image_row.html`
{{ define "image_row" }}
To stuff here
{{ end }}
Now I want to reuse the image row template. Let's say I would like to pass a simple number, so that the image_row template builds up rows according to this number
I'd like to have something like that (where 5 is the additional argument)
index.html
{{ template "image_row" . | 5 }}
How could I achieve that in this case?
I'm not sure whether there exists a builtin solution for passing multiple arguments to a template invocation but, in case there isn't one, you could define a function that merges its arguments and returns them as a single slice value, then you can register that function and use it in the template invocation.
Something like:
func args(vs ...interface{}) []interface{} { return vs }
t, err := template.New("t").Funcs(template.FuncMap{"args":args}).Parse...
Then, in your index.html, you would do this:
{{ template "image_row" args . 5 }}
And then inside your image_row template you can access the arguments with the builtin index function like this:
{{ define "image_row" }}
To stuff here {{index . 0}} {{index . 1}}
{{ end }}
https://play.golang.org/p/gkdtvvJ1bb
There is no builtin for this. You can add a function that creates a map and use that in the child template:
func argsfn(kvs ...interface{}) (map[string]interface{}, error) {
if len(kvs)%2 != 0 {
return nil, errors.New("args requires even number of arguments.")
}
m := make(map[string]interface{})
for i := 0; i < len(kvs); i += 2 {
s, ok := kvs[i].(string)
if !ok {
return nil, errors.New("even args to args must be strings.")
}
m[s] = kvs[i+1]
}
return m, nil
}
Add it the function to the template like this:
t := template.Must(template.New("").Funcs(template.FuncMap{"args": argsfn}).Parse(......
Use it like this:
{{template "image_row" args "row" . "a" 5}}{{end}}
{{define "image_row"}}
{{$.row}} {{$.a}}
{{end}}
Run it in the playground
The advantage of using a map is that the arguments are "named". The advantage of using a slice as described in another answer is that the code is much simpler.
I am trying to convert a golang template, and allow ignoring if the match is not found. Is that possible?
Playground
package main
import (
"bytes"
"fmt"
"text/template"
)
type Person struct {
Name string
Age int
}
type Info struct {
Name string
Id int
}
func main() {
msg := "Hello {{ .Id }} With name {{ .Name }}"
p := Person{Name: "John", Age: 24}
i := Info{Name: "none", Id: 5}
t := template.New("My template")
t, _ = t.Parse(msg)
buf := new(bytes.Buffer)
t.Execute(buf, p)
fmt.Println(buf.String())
buf = new(bytes.Buffer)
t.Execute(buf, i)
fmt.Println(buf.String())
}
I would like this to print
Hello {{ .Id }} with name John
Hello 5 With name none
If you want it to print name only when it's not an empty string:
"Hello {{ .Id }} With name {{ if .Name }}{{ .Name }}{{ end }}"
Else, if you want to it print something else:
"Hello {{ .Id }} With name {{ if .Name }}{{ .Name }}{{ else }}none!{{ end }}"
Playground - also see the comparison operators for html/template and text/template.
A template can contain if statements which allow you to do what you would need. The following example allows displaying a list if supplied, or when not supplied putting a message.
{{if .MyList}}
{{range .MyList}}
{{.}}
{{end}}
{{else}}
There is no list provided.
{{end}}
Using this approach I think you can achieve what you need. But it might not be pretty as you want to leave the unprocessed {{.Id}} in place.