Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If method loading html files from embed.FS type could be helpful?? #2795

Open
j1mmyson opened this issue Jul 22, 2021 · 3 comments
Open

If method loading html files from embed.FS type could be helpful?? #2795

j1mmyson opened this issue Jul 22, 2021 · 3 comments

Comments

@j1mmyson
Copy link

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.

//embed:go web/templates/*.html
var templatesFS embed.FS

func main(){
    r := gin.Default()
    LoadHTMLFromEmbedFS(r, templatesFS, "web/templates/*")
    ...
    r.Run()
}

func LoadHTMLFromEmbedFS(engine *gin.Engine, embedFS embed.FS, pattern string){
    templ := template.Must(template.ParseFS(embedFS, pattern))
    engine.SetHTMLTemplate(templ)
}

Is it convenient to use this function in the form of a method?

func (engine *Engine) LoadHTMLFromFS(embedFS embed.FS, pattern string){
    templ := template.Must(template.ParseFS(embedFS, pattern))
    engine.SetHTMLTemplate(templ)
}

and users use it like

//embed:go web/templates/*.html
var templatesFS embed.FS

func main(){
    r := gin.Default()
    r.LoadHTMLFromFS(templatesFS, "web/templates/*")
    ...
    r.Run()
}

I don't know whether my code is safe or not, so leave it as an issue for now. Thank you!

@sesopenko
Copy link

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
}

@j1mmyson
Copy link
Author

j1mmyson commented Aug 4, 2021

@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!

@walnut-tom
Copy link

walnut-tom commented Nov 8, 2023

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)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants