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.
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 an answer here:
Capture or assign golang template output to variable
(1 answer)
Closed 5 years ago.
I have two templates T1 and T2. I want to get the output of T1 and do a some extra processing on it inside T2. My question is:
how do I store the output of T1 in a variable inside T2? Is this even possible?
Here's some pseudo-template:
{{define "T1"}}
{{ printf "%s-%s" complex stuff }}
{{end}}
{{define "T2"}}
{{ $some_var := output_from_template "T1"}} <<<<<<<<<<<
{{ etc }}
{{end}}
There is no builtin support for storing the result of a template in a template variable, only for the inclusion of the result.
But you can register custom functions with any complex functionality you want. You may register a GetOutput function which would execute a template identified by its name, and it could return the result as a string, which you can store in a template variable.
Example doing this:
func main() {
t := template.New("")
t = template.Must(t.Funcs(template.FuncMap{
"GetOutput": func(name string) (string, error) {
buf := &bytes.Buffer{}
err := t.ExecuteTemplate(buf, name, nil)
return buf.String(), err
},
}).Parse(src))
if err := t.ExecuteTemplate(os.Stdout, "T2", nil); err != nil {
panic(err)
}
}
const src = `
{{define "T1"}}{{ printf "%s-%s" "complex" "stuff" }}{{end}}
{{define "T2"}}
{{ $t1Out := (GetOutput "T1")}}
{{ printf "%s-%s" "even-more" $t1Out }}
{{end}}`
Output will be (try it on the Go Playground):
even-more-complex-stuff
The "T1" template simply outputs "complex-stuff", and the "T2" template gets the output of "T1", and concatenates the static text "even-more-" and the result of "T1".
The registered GetOutput function gets the name of a template to execute, executes it by directing its output to a local buffer, and returns the content of the buffer (along with the optional error of its execution).
Edit: I've found an exact duplicate: Capture or assign golang template output to variable
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