Open j1mmyson opened 3 years ago
The workaround from @j1mmyson above doesn't handle templates in recursive directories. Combining this solution from stack exchange with the above lets you attach templates from recursive embedded directories:
package main
import (
"embed"
"github.com/gin-gonic/gin"
"html/template"
"io/fs"
"net/http"
"strings"
)
// template files in directory:
// templates/index.html
// templates/test/nested.html
//go:embed templates/**
var f embed.FS
func main() {
r := gin.Default()
root := template.New("")
LoadAndAddToRoot(r.FuncMap, root)
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "templates/index.html", gin.H{})
})
r.GET("/embed", func(c *gin.Context) {
c.HTML(http.StatusOK, "templates/test/nested.html", gin.H{})
})
r.Run("127.0.0.1:8080")
}
func LoadAndAddToRoot(funcMap template.FuncMap, rootTemplate *template.Template) error {
// This solution is CC by share alike 4.0
// Copyright Rik-777
// https://stackoverflow.com/a/50581032
err := fs.WalkDir(f, ".", func(path string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if !d.IsDir() && strings.HasSuffix(path, ".html") {
data, readErr := f.ReadFile(path)
if readErr != nil {
return readErr
}
t := rootTemplate.New(path).Funcs(funcMap)
if _, parseErr := t.Parse(string(data)); parseErr != nil {
return parseErr
}
}
return nil
})
return err
}
@sesopenko Thank you for comments! according to your comments, it finally attach templates from recursive embedded directories.
func LoadHTMLFromEmbedFS(engine *gin.Engine, embedFS embed.FS, pattern string) {
root := template.New("")
tmpl := template.Must(root, LoadAndAddToRoot(engine.FuncMap, root, embedFS, pattern))
engine.SetHTMLTemplate(tmpl)
}
// Method version
// func (engine *gin.Engine) LoadHTMLFromFS(embedFS embed.FS, pattern string) {
// root := template.New("")
// tmpl := template.Must(root, LoadAndAddToRoot(engine.FuncMap, root, embedFS, pattern))
// engine.SetHTMLTemplate(tmpl)
// }
func LoadAndAddToRoot(funcMap template.FuncMap, rootTemplate *template.Template, embedFS embed.FS, pattern string) error {
pattern = strings.ReplaceAll(pattern, ".", "\\.")
pattern = strings.ReplaceAll(pattern, "*", ".*")
err := fs.WalkDir(embedFS, ".", func(path string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if matched, _ := regexp.MatchString(pattern, path); !d.IsDir() && matched {
data, readErr := embedFS.ReadFile(path)
if readErr != nil {
return readErr
}
t := rootTemplate.New(path).Funcs(funcMap)
if _, parseErr := t.Parse(string(data)); parseErr != nil {
return parseErr
}
}
return nil
})
return err
}
and use it like
package main
import (
"embed"
"html/template"
"io/fs"
"net/http"
"regexp"
"strings"
"github.com/gin-gonic/gin"
)
//go:embed web/templates
var templatesFS embed.FS
func main() {
r := gin.Default()
LoadHTMLFromEmbedFS(r, templatesFS, "web/templates/*html")
// if method
// r.LoadHTMLFromEmbedFS(templatesFS, "web/templates/*html")
...
r.Run()
}
following is an example: https://github.com/j1mmyson/LoadHTMLFromEmbedFS-example
from now on, is there any improvement point or problems? Thanks again for your comments!
the template has method
func ParseFS(fs fs.FS, patterns ...string) (*Template, error)
func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error)
can add a method like this:
func (engine *Engine) LoadHTMLFS(fs fs.FS, patterns ...string) {
if IsDebugging() {
engine.HTMLRender = render.HTMLDebug{Files: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
return
}
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(fs, patterns...))
engine.SetHTMLTemplate(templ)
}
First of all, thank you for creating a very useful framework !!
Recently, I found out that the gin framework cannot load template with embed.FS type and I have solved it in the following way.
Is it convenient to use this function in the form of a method?
and users use it like
I don't know whether my code is safe or not, so leave it as an issue for now. Thank you!