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.
Related
In my template, I use a sub-template that generates a piece of output.
The template output must be shifted though (because the output is in YAML format).
Is there any possibility to post-process template output?
{{ template "subtemplate" | indent 10 }}
This indent 10 is fictional, just to explain what I need.
It is possible (as #icza suggested) to save the output
into a variable and then work with it,
but maybe there is a better, more elegant approach?
{{$var := execTempl "subtemplate"}}
{{$var}}
The closest you can get to {{ template "subtemplate" | indent 10 }} is to define a function that parses and executes the subtemplate and outputs the result as string.
var externalTemplates = map[string]*template.Template{
"subtemplate": template.Must(template.New("subtemplate").Parse(sub_template)),
}
// Executes external template, must be registered with FuncMap in the main template.
func xtemplate(name string) (string, error) {
var b bytes.Buffer
if err := externalTemplates[name].ExecuteTemplate(&b, name, nil); err != nil {
return "", err
}
return b.String(), nil
}
t := template.Must(template.New("t").Funcs(template.FuncMap{
"xtemplate": xtemplate, // register func
}).Parse(main_template))
In the main template you can then use the function like this:
{{ xtemplate "subtemplate" | indent 10 }}
https://play.golang.org/p/brolOLFT4xL
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
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 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