Search code examples
functionparsingtemplatesgo

In Go templates, I can get Parse to work but cannot get ParseFiles to work in like manner. Why?


I have the following code:

t, err := template.New("template").Funcs(funcMap).Parse("Howdy {{ myfunc . }}")

In this form everything works fine. But if I do exactly the same thing with ParseFiles, placing the text above in template.html it's a no go:

t, err := template.New("template").Funcs(funcMap).ParseFiles("template.html")

I was able to get ParseFiles to work in the following form, but cannot get Funcs to take effect:

t, err := template.ParseFiles("template.html")
t.Funcs(funcMap)

Of course, this last form is a direct call to a function instead of a call to the receiver method, so not the same thing.

Any one have any ideas what's going on here? Difficult to find a lot of detail on templates out in the either.


Solution

  • Did some digging and found this comment for template.ParseFiles in the source:

    First template becomes return value if not already defined, and we use that one for subsequent New calls to associate all the templates together. Also, if this file has the same name as t, this file becomes the contents of t, so t, err := New(name).Funcs(xxx).ParseFiles(name) works. Otherwise we create a new template associated with t.

    So the format should be as follows, given my example above:

    t, err := template.New("template.html").Funcs(funcMap).ParseFiles("path/template.html")
    

    .New("template.html") creates an empty template with the given name, .Funcs(funcMap) associates any custom functions we want to apply to our templates, and then .ParseFiles("path/template.html") parses one or more templates with an awareness of those functions and associates the contents with a template of that name.

    Note that the base name of the first file MUST be the same as the name used in New. Any content parsed will be associated with either an empty preexisting template having the same base name of the first file in the series or with a new template having that base name.

    So in my example above, one empty template named "template" was created and had a function map associated with it. Then a new template named "template.html" was created. THESE ARE NOT THE SAME! And since, ParseFiles was called last, t ends up being the "template.html" template, without any functions attached.

    What about the last example? Why didn't this work? template.ParseFiles calls the receiver method Parse, which in turn applies any previously registered functions:

    trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
    

    This means that custom functions have to be registered prior to parsing. Adding the functions after parsing the templates doesn't have any affect, leading to nil pointer errors at runtime when attempting to call a custom function.

    So I think that covers my original question. The design here seems a little clunky. Doesn't make sense that I can chain on ParseFiles to one template and end up returning a different template from the one I am chaining from if they don't happen to be named the same. That is counterintuitive and probably ought to be addressed in future releases to avoid confusion.