Sunny
Sunny

Reputation: 2332

Golang: Parse all templates in directory and subdirectories?

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

Answers (6)

Odas0R
Odas0R

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

Karel Bílek
Karel Bílek

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

Reindert Vetter
Reindert Vetter

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

Rick-777
Rick-777

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

pete911
pete911

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

Datsik
Datsik

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

Related Questions