I'm wondering if it's possible to use a custom function as a template block with Golang templates. The code below shows an example.
{{ custom_func . }}
This is content that "custom_func" should do something with.
{{ end }}
Use case is a bit peculiar and non-standard. Basically I want the ability for the template author to pass in large block of text where newlines etc. is respected and for that entire block of text to be passed to the function. I could have done something like:
{{ custom_func "This is a lot of text\n with many lines etc." }}
But this is not very user friendly to the template author. The end goal is for them to write something like this:
Author is writing something normal...
{{ note }}
But would like to wrap this content as a "note".
Which when passed to the "note" function, will wrap the content with appropriate divs etc.
{{ end }}
Basically I'm trying an experiment to see if I can achieve "markdown/reStructuredText"-like content with pure go templates. It's mostly an experiment for now.
Eventually I'll probably need to write a proper PEG parser for this, but I want to see if this is possible first.
String arguments to functions may be wrapped both in double quotes " or in backticks `.
String literals wrapped in backticks in templates are called raw string constants, and they work like raw string literals in Go source: may include newlines (and cannot contain escape sequences).
So it's possible what you want if you use backticks for the argument.
For example, a.tmpl:
START
{{ note `a
b\t
c
d`}}
END
App to load and execute the template:
t := template.Must(template.New("").Funcs(template.FuncMap{
"note": func(s string) string { return "<note>\n" + s + "\n</note>" },
}).ParseFiles("a.tmpl"))
if err := t.ExecuteTemplate(os.Stdout, "a.tmpl", nil); err != nil {
panic(err)
}
This will output:
START
<note>
a
b\t
c
d
</note>
END
It's a bit tricky if you define the template in your Go source, as if you use backticks for the template text (because you want to write multiple lines), you can't embed backticks in a raw string literal. You have to break the literal, and concatenate the backticks.
Example doing this in a Go source file:
func main() {
t := template.Must(template.New("").Funcs(template.FuncMap{
"note": func(s string) string { return "<note>\n" + s + "\n</note>" },
}).Parse(src))
if err := t.Execute(os.Stdout, nil); err != nil {
panic(err)
}
}
const src = `START
{{ note ` + "`" + `a
b\t
c
d` + "`" + `}}
END
`
This will output the same, try it on the Go Playground.
Related
The eq function allows for comparing if two strings are equal
{{if eq .Name "MyName"}}
Is there a way to test if a string ends in (or contains) another string?
Use a function map containing the relevant string functions.
funcs := map[string]any{
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix}
tmpl := `{{if hasSuffix . ".txt"}}yes!{{end}}`
t := template.Must(template.New("").Funcs(funcs).Parse(tmpl))
t.Execute(os.Stdout, "example.txt") // writes yes! to standard out
Run the example on the playground.
Some applications that use Go templates as a feature (Hugo and Helm are examples) provide these functions by default.
(h/t to mkopriva).
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
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
Using text/html I define a block in my base template, containing default content. In some situations I would like this block to be empty, so I thought I could just re-define its name and make it contain nothing like:
{{ block "something" . }}
<h1>Default content</h1>
{{ end }}
// later in a place that does not want "something" ...
{{ define "something" }}{{ end }}
Somehow Go seems to think that this definition is "Zero" and will still render the default content unless I put any non-whitespace content into the definition.
I found this issue on the Golang repo which describes the very same thing nicely in a Playground example:
package main
import (
"fmt"
"os"
"runtime"
"text/template"
)
func main() {
fmt.Printf("Version: %q\n", runtime.Version())
t, err := template.New("master").Parse(`{{block "area51" .}}Original Content{{end}}`)
if err != nil {
panic(err)
}
t, err = t.New("other_template").Parse(`{{define "area51"}}{{end}}`)
if err != nil {
panic(err)
}
fmt.Printf("Output:\n")
if err := t.ExecuteTemplate(os.Stdout, "master", nil); err != nil {
panic(err)
}
fmt.Printf("\n")
}
Weirdly, the issue mention it is fixed (and landed in 1.8.1 if I understand it correctly), but it does not work for me, neither with 1.8.1+ nor 1.9.
Is this a bug in Golang or is the approach flawed? Do I need to do anything differently in order to re-define the block so that it renders empty?
This is the expected behavior. This is documented at Template.Parse():
Templates can be redefined in successive calls to Parse, before the first use of Execute on t or any associated template. A template definition with a body containing only white space and comments is considered empty and will not replace an existing template's body. This allows using Parse to add new named template definitions without overwriting the main template body.
So you can't "erase" an already defined template (you can't replace its content to be empty).
If you "conditionally" need it, then use an {{if}} action to decide if the template is to be called. Alternatively you may put an {{if}} inside the template, and the template itself may choose not to render anything. In this case you have to make sure to pass the proper argument that controls what the template will render.
P.S. If you're working with HTML templates, you should always use html/template instead of text/template, as the former provides the same interface as package text/template but also provides contextual escaping to generate HTML output safe against code injection.
I've got a slice of strings (.Table.PKey.Columns) that I'm trying to loop over in my template to generate a go file that does some appends, but when I output $value in my template, apparently Go is quoting it for me, so it is giving me the error:
5:27: expected selector or type assertion, found 'STRING' "ID"
i.e., instead of the template output looking something like o.ID -- which is what I'm aiming for, it ends up looking something like o."ID" (I presume).
Am I right in my assumption that this is the result of using a range loop? Because it seems when I access variables directly in other places (for example, say I had a string and I did: o.{{.Table.MyString}}) it works fine, but as soon as I try and incorporate a range loop into the mix it seems to be quoting things.
{{- range $key, $value := .Table.PKey.Columns }}
args = append(args, o.{{$value}})
{{ end -}}
Any suggestions? Thank you.
The {{range}} does not quote anything. If you see "ID" in your output, then your input value is "ID" with quotation marks included!
See this example:
func main() {
m := map[string]interface{}{
"Id": "Id1",
"Quoted": `"Id2"`,
"Ids": []string{"Id1", `"Id2"`, "Abc"},
}
t := template.Must(template.New("").Parse(src))
t.Execute(os.Stdout, m)
}
const src = `{{.Id}} {{index .Ids 0}} {{.Quoted}}
{{range $key, $value := .Ids}}{{$value}}
{{end}}
`
Output (try it on the Go Playground):
Id1 Id1 "Id2"
Id1
"Id2"
Abc
If the variable Go Template is rendering is within tags, then Go Template will wrap any strings with quotes for you.
Some options include generating the in code prior to asking Go Template to render it.