Search code examples
gogo-templates

Golang: Custom template "block" functions?


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.


Solution

  • 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.