Reputation: 2332
This is my directory structure:
app/
template/
layout/
base.tmpl
index.tmpl
template.ParseGlob("*/*.tmpl")
parses index.tmpl
but not base.tmpl
in the layout
subdirectory. Is there a way to parse all templates recursively?
Upvotes: 15
Views: 21197
Reputation: 41
With the answer of pete911 I could create this function to parse all the html templates on multiple subdirectories.
// parse.go
func ParseHtmlTemplates() *template.Template {
var directories []string
var filenames []string
// Root directory of template files
root := "./templates"
// Get all directories on /templates and check if there's repeated files
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
// Is file
filename := info.Name()
hasRepeatedFiles := contains(filenames, filename)
if hasRepeatedFiles {
return fmt.Errorf("You can't have repeated template files: %s", filename)
}
filenames = append(filenames, filename)
} else {
// Is directory
directories = append(directories, path)
}
return nil
})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Create a template for parsing all directories
tmpl := template.Must(template.ParseGlob(root + "/*.html"))
// Parse all directories (minus the root directory)
for _, path := range directories[1:] {
pattern := path + "/*.html"
template.Must(tmpl.ParseGlob(pattern))
}
return tmpl
}
// contains private method
func contains(filenames []string, filename string) bool {
for _, f := range filenames {
if f == filename {
return true
}
}
return false
}
// main.go
func main() {
tmpl = ParseHtmlTemplates()
if err := tmpl.Execute(os.Stdout, ""); err != nil {
panic(err)
}
}
Upvotes: 0
Reputation: 37734
I made a package that solves exactly this problem.
https://github.com/karelbilek/template-parse-recursive
package main
import (
"html/template"
"os"
recurparse "github.com/karelbilek/template-parse-recursive"
)
func main() {
t, err := recurparse.HTMLParse(
template.New("templates"),
"path/to/templates",
"*.html",
)
if err != nil {
panic(err)
}
templateUnder := t.Lookup("subdir/subdir/template.html")
templateUnder.Execute(os.Stdout, nil)
}
Upvotes: 0
Reputation: 9
You can load multiple subdirectories this way. Here we ignore if subdirectories do not exist. But we want to make sure that the first directory can be loaded.
func ParseTemplates() (*template.Template, error) {
templateBuilder := template.New("")
if t, _ := templateBuilder.ParseGlob("/*/*/*/*/*.tmpl"); t != nil {
templateBuilder = t
}
if t, _ := templateBuilder.ParseGlob("/*/*/*/*.tmpl"); t != nil {
templateBuilder = t
}
if t, _ := templateBuilder.ParseGlob("/*/*/*.tmpl"); t != nil {
templateBuilder = t
}
if t, _ := templateBuilder.ParseGlob("/*/*.tmpl"); t != nil {
templateBuilder = t
}
return templateBuilder.ParseGlob("/*.tmpl")
}
Upvotes: 0
Reputation: 10268
Datsik's answer has the drawback that there are name collision issues when several directories contain many templates. If two templates in different directories have the same filename it won't work properly: only the second of them will be usable.
This is caused by the implementation of template.ParseFiles, so we can solve it by avoiding template.ParseFiles. Here's a modified walk algorithm that does this by using template.Parse directly instead.
func findAndParseTemplates(rootDir string, funcMap template.FuncMap) (*template.Template, error) {
cleanRoot := filepath.Clean(rootDir)
pfx := len(cleanRoot)+1
root := template.New("")
err := filepath.Walk(cleanRoot, func(path string, info os.FileInfo, e1 error) error {
if !info.IsDir() && strings.HasSuffix(path, ".html") {
if e1 != nil {
return e1
}
b, e2 := ioutil.ReadFile(path)
if e2 != nil {
return e2
}
name := path[pfx:]
t := root.New(name).Funcs(funcMap)
_, e2 = t.Parse(string(b))
if e2 != nil {
return e2
}
}
return nil
})
return root, err
}
This will parse all your templates then you can render them by calling their names e.g.
template.ExecuteTemplate(w, "a/home.html", nil)
Upvotes: 18
Reputation: 819
if it is not deeply nested (if you know names of sub-directories beforehand) you can just do this:
t := template.Must(template.ParseGlob("template/*.tmpl"))
template.Must(t.ParseGlob("template/layout/*.tmpl"))
Then for each sub-directory do the same as for 'layout'
Upvotes: 15
Reputation: 14824
Not without implementing your own function to do it, I've been using something like this
func ParseTemplates() *template.Template {
templ := template.New("")
err := filepath.Walk("./views", func(path string, info os.FileInfo, err error) error {
if strings.Contains(path, ".html") {
_, err = templ.ParseFiles(path)
if err != nil {
log.Println(err)
}
}
return err
})
if err != nil {
panic(err)
}
return templ
}
This will parse all your templates then you can render them by calling their names e.g.
template.ExecuteTemplate(w, "home", nil)
Upvotes: 21