How to load templates in subdirectories - go

I currently have all my html files in a flat directory templates/ and I load everything in with
tmpl := template.Must(template.ParseGlob("templates/*.html"))
But I'd like to now bring in some structure and put templates into folders, components, base, etc. But when I do my site stops working. I'm thinking it could be the above, or could it also be that I need to reference the path in the template?
example
{{ template "navbar" }}
would become
{{ template "components/navbar" }}
Slightly confused...
I'm also using the native go library not a framework, for now.

Go's glob does not support matching files in sub-directories, i.e. ** is not supported.
You can either use a third party lib (there are a number of implementations on github), or you could invoke filepath.Glob for each "level" of sub-directories and aggregate the returned file names into a single slice and then pass the slice to template.ParseFiles:
dirs := []string{
"templates/*.html",
"templates/*/*.html",
"templates/*/*/*.html",
// ...
}
files := []string{}
for _, dir := range dirs {
ff, err := filepath.Glob(dir)
if err != nil {
panic(err)
}
files = append(files, ff...)
}
t, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
// ...
You also need to keep in mind how ParseFiles works: (emphasis mine)
ParseFiles creates a new Template and parses the template definitions
from the named files. The returned template's name will have the
(base) name and (parsed) contents of the first file. There must be at
least one file. If an error occurs, parsing stops and the returned
*Template is nil.
When parsing multiple files with the same name in different
directories, the last one mentioned will be the one that results. For
instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
named "foo", while "a/foo" is unavailable.
This means that, if you want to load all the files, you have to ensure at least one of two things: (1) that each file's base name is unique across all template files, not just in the directory in which the file's located, or (2) provide a unique template name for each file by using the {{ define "<template_name>" }} action at the top of the file's contents (and do not forget the {{ end }} to close the define action).
As an example for the 2nd approach, let's say, in your templates you have two files that have the same base name, e.g. templates/foo/header.html and templates/bar/header.html and their contents are as follows:
templates/foo/header.html
<head><title>Foo Site</title></head>
templates/bar/header.html
<head><title>Bar Site</title></head>
Now to give the these files a unique template name you can change the contents to this:
templates/foo/header.html
{{ define "foo/header" }}
<head><title>Foo Site</title></head>
{{ end }}
templates/bar/header.html
{{ define "bar/header" }}
<head><title>Bar Site</title></head>
{{ end }}
After you do this, you can either execute them directly with t.ExecuteTemplate(w, "foo/header", nil), or indirectly by having other templates reference them using the {{ template "bar/header" . }} action.

Related

Dynamically parse sub-templates based on those actually required by the main template

Suppose I have a large number of templates with sub-templates, then how do I parse sub-templates based on those required by template pipelines in the?
My idea is to read the current template to be rendered and find out which templates it uses, but I don't know how to do that, perhaps with regular expressions?
PS: answers don't have to consider multi-level nesting of sub-template.
Example
package main
import (
"html/template"
"path/filepath"
)
func CollectFiles(dir string, excludeList []string) (fileList []string, err error) {
// ...
return
}
func main() {
filePathList, _ := CollectFiles("dir/src", []string{".md"})
for _, curFile := range filePathList {
_, _ = template.New(filepath.Base(curFile)).
ParseFiles(curFile, "tmplA", "tmplB", "...", "tmplN")
}
}
Suppose the main template only needs tmplA and tmplB as sub-templates. How can I detect that it only requires those two?
I don't want to change the program every time a new template is added or adjusted.
You can find all the keywords in the template in this way.
regexp.MustCompile(`{{-? ?(template|partial) \"([^() ]*)\" ?.* ?-?}}`)
regex101
where partial is just an example for you to use when you have more complicated situations. You can delete it or add more keywords by yourself.
go-playground
The other parts, such as the CollectFiles I think are not so important, if someone needs to refer to the following
solution of embed
solution of filesystem
And then just need to filter templates to find out what templates the current file uses.
Finally, the code without to update whenever a template is added. (except you want to update the site context)
I wrote a simple example on Github that can run so that people can know what I want to do.
Nesting of sub-template.
If you want to handle this. try to use this function
Now this will work
If you render: index.gohtml not only base.gohtml include but also {head.gohtml, navbar.gohtml, footer.gohtml} are included
<!-- index.gohtml -->
{{- template "base.gohtml" . -}} <!-- 👈 -->
{{define "head"}}
<style>h2 {background-color: yellow;}
</style>
{{end}}
{{define "body"}}
<h2>Welcome to XXX</h2>
{{end}}
where base.gohtml
{{template "head.gohtml" . -}}
{{template "navbar.gohtml"}}
{{- block "body" . -}}
{{- end -}}
{{template "footer.gohtml"}}
The final version

Go template - syntax for range

In Go template, I have a map setup like this:
{{$key}}map := make(map[string]interface{})
And I want to iterate through the map using this:
{{ range $mapKey, $mapValue := {{$key}}map}}
And I am getting this error:
unexpected "{" in range
Looks like it does not allow nested {{}} inside another {{}}. Is there anyway I can solve this issue ???
You cannot generate variable names to be used in templates using the template engine itself. You seem to be in need of having multiple maps, one for each $key. So, use a map of maps:
m := make(map[string]map[string]interface{})
where m[key] gives the map for the key.
Then you can do:
{{ range $mapKey, $mapValue := (index $.m $.key)}}
...
{{end}}

Inserting template name as class

When creating a Go template, you can give it a name, like in this example, "my_home_template":
var tmplHome = template.Must(template.New("my_home_template").Funcs(funcMap).ParseFiles("templates/base.tmpl", "templates/content_home.tmpl"))
How can I get that template name and use it inside the actual template file?
Ultimately I just want to define a convenient css class, like so:
<body class="my_home_template">
Here's a working solution, taking mkopriva's advice:
When executing a template, pass some custom parameter with dummy data. Here, I just create a "PageHome" parameter to pass to the template, and value is a simple "1", but it could be any value:
tmplHome.ExecuteTemplate(w, "base", map[string]interface{}{"PageHome": "1", "Data": events, "UserFirstName": &u.FirstName, "UserProfilePic": &u.ProfilePic})
Then, inside the template itself, a simple if statement to check if the parameter exists, and do something accordingly:
{{ if .PageHome }}
<body class="PageHome">
{{ else }}
<body>
{{ end }}
All my other template executions don't pass a "PageHome" parameter at all, so the if statement never passes as true for them.
There's probably a more advanced solution using a functions via a template function map, and having a consistent "PageType":"something" parameter in all template executions, but in the end you still have to define a parameter per template execution and still have to build up if statements in your templates anyways.

Show default content in a template if an object is nil otherwise show based on the set property

In my template, I would like to include some default meta tags (90% of the time). However, when a specific property is set, I would like to show a different set of text.
I know I can set an anonymous struct and set a property with either "default" or "some-x". However, this means, I need to add an anonymous struct to 90% of my handlers that just currently pass nil.
Is there way to do something like
{{if eq . nil}}
// default meta tag
{{else if eq .MetaValue "some-x"}}
//other
{{end}}
If I try something like my above code, it compiles but doesn't do what I want. Appreciate any suggestions on how to handle it properly without adding a lot of boiler plate.
Thanks!
{{if not .}}
output when . is nil or otherwise empty including
false, 0, and any array, slice, map, or string of length zero
{{else if eq .MetaValue "some-x"}}
// some-x case
{{else}}
// other case
{{end}}
If you want to ensure you're only checking against nil and not 0, false, the empty string, or any other falsey type, you can use the kindIs function to accomplish this.
{{ if kindIs "invalid" . }}
// only if variable is literally nil. falsey values will fallthrough.
{{ else if eq .MetaValue "some-x" }}
// other
{{ else }}
// final case, if any
{{ end }}
I've been recently facing an issue with identifying nil vs 0 values in a Helm Chart (which uses Go templates, including sprig) and haven't found any solutions posted, so I thought I'd add mine here.
I came up with a kind of ugly solution which is to quote the value and then check for a string that matches "<nil>" (with quotes, so you'd actually be checking (quote .Values.thing | eq "\"<nil>\"")). This allows differentiating tests against empty values vs defined 0 values. In my case, I was trying to build a config file where some default options were non-0, so when 0 was explicitly set, I wanted to know that 0 was set instead of just omitted.
Hopefully this can be a help to someone else.
It would be nice to have a better way to do this, but so far I haven't found anything that doesn't require creating and adding my own template functions.

Print the emacs package name that a function belongs to

In elisp, M-x find-function funcName
is there to go to the function definition. But, how to print the package name that a function belongs to?
The built-in library help-fns.el contains find-lisp-object-file-name. If you call this with a function name and type set to nil it will print the full file name of the file containing the definition.
On my system
(find-lisp-object-file-name 'find-function nil)
returns
"/Applications/MacPorts/Emacs.app/Contents/Resources/lisp/emacs-lisp/find-func.el"
which you can strip down as required. Note this behaves well on undefinded functions
(find-lisp-object-file-name 'I-dont-exist nil)
nil
(find-lisp-object-file-name 'icicle-find-file nil)
"/Users/swann/.emacs.d/elpa/icicles-20141215.1749/icicles-cmd1.el"
Items in packages will be found under in one of the directories in package-directory-list and package-user-dir. By default the latter is ~/.emacs.d/elpa. Under such a directory the given package is a subdirectory with name package-name-VERSION.

Resources