mirror of https://github.com/gin-gonic/gin.git
Move OnlyHTMLFS to internal and add test
This commit is contained in:
parent
e21fcce345
commit
eb4a5e1866
|
@ -1351,6 +1351,8 @@ func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.LoadHTMLGlob("templates/*")
|
router.LoadHTMLGlob("templates/*")
|
||||||
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
|
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
|
||||||
|
//router.LoadHTMLFS(http.Dir("templates"), "template1.html", "template2.html")
|
||||||
|
//or
|
||||||
//router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html")
|
//router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html")
|
||||||
router.GET("/index", func(c *gin.Context) {
|
router.GET("/index", func(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||||
|
|
17
fs.go
17
fs.go
|
@ -5,7 +5,6 @@
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
@ -26,22 +25,6 @@ func (o OnlyFilesFS) Open(name string) (http.File, error) {
|
||||||
return neutralizedReaddirFile{f}, nil
|
return neutralizedReaddirFile{f}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnlyHTMLFS implements an [fs.FS].
|
|
||||||
type OnlyHTMLFS struct {
|
|
||||||
FileSystem http.FileSystem
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open passes `Open` to the upstream implementation and return an [fs.File].
|
|
||||||
func (o OnlyHTMLFS) Open(name string) (fs.File, error) {
|
|
||||||
f, err := o.FileSystem.Open(name)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.File(f), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
|
// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
|
||||||
type neutralizedReaddirFile struct {
|
type neutralizedReaddirFile struct {
|
||||||
http.File
|
http.File
|
||||||
|
|
30
fs_test.go
30
fs_test.go
|
@ -48,36 +48,6 @@ func TestOnlyFilesFS_Open_err(t *testing.T) {
|
||||||
assert.Nil(t, file)
|
assert.Nil(t, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnlyHTMLFS_Open(t *testing.T) {
|
|
||||||
var testFile *os.File
|
|
||||||
mockFS := &mockFileSystem{
|
|
||||||
open: func(name string) (http.File, error) {
|
|
||||||
return testFile, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fs := &OnlyHTMLFS{FileSystem: mockFS}
|
|
||||||
|
|
||||||
file, err := fs.Open("foo")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, testFile, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOnlyHTMLFS_Open_err(t *testing.T) {
|
|
||||||
testError := errors.New("mock")
|
|
||||||
mockFS := &mockFileSystem{
|
|
||||||
open: func(_ string) (http.File, error) {
|
|
||||||
return nil, testError
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fs := &OnlyHTMLFS{FileSystem: mockFS}
|
|
||||||
|
|
||||||
file, err := fs.Open("foo")
|
|
||||||
|
|
||||||
require.ErrorIs(t, err, testError)
|
|
||||||
assert.Nil(t, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_neuteredReaddirFile_Readdir(t *testing.T) {
|
func Test_neuteredReaddirFile_Readdir(t *testing.T) {
|
||||||
n := neutralizedReaddirFile{}
|
n := neutralizedReaddirFile{}
|
||||||
|
|
||||||
|
|
6
gin.go
6
gin.go
|
@ -16,6 +16,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
|
filesystem "github.com/gin-gonic/gin/internal/fs"
|
||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
|
@ -289,11 +290,12 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||||
// and associates the result with HTML renderer.
|
// and associates the result with HTML renderer.
|
||||||
func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) {
|
func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
engine.HTMLRender = render.HTMLDebug{FS: OnlyHTMLFS{fs}, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
|
engine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(OnlyHTMLFS{fs}, patterns...))
|
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(
|
||||||
|
filesystem.FileSystem{FileSystem: fs}, patterns...))
|
||||||
engine.SetHTMLTemplate(templ)
|
engine.SetHTMLTemplate(templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
gin_test.go
14
gin_test.go
|
@ -6,7 +6,6 @@ package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"embed"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
@ -326,8 +325,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
||||||
assert.Equal(t, "Date: 2017/07/01", string(resp))
|
assert.Equal(t, "Date: 2017/07/01", string(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed testdata/template/*.tmpl
|
var tmplFS = http.Dir("testdata/template")
|
||||||
var tmplFS embed.FS
|
|
||||||
|
|
||||||
func TestLoadHTMLFSTestMode(t *testing.T) {
|
func TestLoadHTMLFSTestMode(t *testing.T) {
|
||||||
ts := setupHTMLFiles(
|
ts := setupHTMLFiles(
|
||||||
|
@ -335,7 +333,7 @@ func TestLoadHTMLFSTestMode(t *testing.T) {
|
||||||
TestMode,
|
TestMode,
|
||||||
false,
|
false,
|
||||||
func(router *Engine) {
|
func(router *Engine) {
|
||||||
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
|
router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
@ -355,7 +353,7 @@ func TestLoadHTMLFSDebugMode(t *testing.T) {
|
||||||
DebugMode,
|
DebugMode,
|
||||||
false,
|
false,
|
||||||
func(router *Engine) {
|
func(router *Engine) {
|
||||||
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
|
router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
@ -375,7 +373,7 @@ func TestLoadHTMLFSReleaseMode(t *testing.T) {
|
||||||
ReleaseMode,
|
ReleaseMode,
|
||||||
false,
|
false,
|
||||||
func(router *Engine) {
|
func(router *Engine) {
|
||||||
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
|
router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
@ -395,7 +393,7 @@ func TestLoadHTMLFSUsingTLS(t *testing.T) {
|
||||||
TestMode,
|
TestMode,
|
||||||
true,
|
true,
|
||||||
func(router *Engine) {
|
func(router *Engine) {
|
||||||
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
|
router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
@ -422,7 +420,7 @@ func TestLoadHTMLFSFuncMap(t *testing.T) {
|
||||||
TestMode,
|
TestMode,
|
||||||
false,
|
false,
|
||||||
func(router *Engine) {
|
func(router *Engine) {
|
||||||
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
|
router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileSystem implements an [fs.FS].
|
||||||
|
type FileSystem struct {
|
||||||
|
http.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open passes `Open` to the upstream implementation and return an [fs.File].
|
||||||
|
func (o FileSystem) Open(name string) (fs.File, error) {
|
||||||
|
f, err := o.FileSystem.Open(name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.File(f), nil
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockFileSystem struct {
|
||||||
|
open func(name string) (http.File, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFileSystem) Open(name string) (http.File, error) {
|
||||||
|
return m.open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TesFileSystem_Open(t *testing.T) {
|
||||||
|
var testFile *os.File
|
||||||
|
mockFS := &mockFileSystem{
|
||||||
|
open: func(name string) (http.File, error) {
|
||||||
|
return testFile, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fs := &FileSystem{mockFS}
|
||||||
|
|
||||||
|
file, err := fs.Open("foo")
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, testFile, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileSystem_Open_err(t *testing.T) {
|
||||||
|
testError := errors.New("mock")
|
||||||
|
mockFS := &mockFileSystem{
|
||||||
|
open: func(_ string) (http.File, error) {
|
||||||
|
return nil, testError
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fs := &FileSystem{mockFS}
|
||||||
|
|
||||||
|
file, err := fs.Open("foo")
|
||||||
|
|
||||||
|
require.ErrorIs(t, err, testError)
|
||||||
|
assert.Nil(t, file)
|
||||||
|
}
|
|
@ -6,8 +6,9 @@ package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delims represents a set of Left and Right delimiters for HTML template rendering.
|
// Delims represents a set of Left and Right delimiters for HTML template rendering.
|
||||||
|
@ -32,12 +33,12 @@ type HTMLProduction struct {
|
||||||
|
|
||||||
// HTMLDebug contains template delims and pattern and function with file list.
|
// HTMLDebug contains template delims and pattern and function with file list.
|
||||||
type HTMLDebug struct {
|
type HTMLDebug struct {
|
||||||
Files []string
|
Files []string
|
||||||
Glob string
|
Glob string
|
||||||
FS fs.FS
|
FileSystem http.FileSystem
|
||||||
Patterns []string
|
Patterns []string
|
||||||
Delims Delims
|
Delims Delims
|
||||||
FuncMap template.FuncMap
|
FuncMap template.FuncMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML contains template reference and its name with given interface object.
|
// HTML contains template reference and its name with given interface object.
|
||||||
|
@ -76,8 +77,9 @@ func (r HTMLDebug) loadTemplate() *template.Template {
|
||||||
if r.Glob != "" {
|
if r.Glob != "" {
|
||||||
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
|
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
|
||||||
}
|
}
|
||||||
if r.FS != nil && len(r.Patterns) > 0 {
|
if r.FileSystem != nil && len(r.Patterns) > 0 {
|
||||||
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(r.FS, r.Patterns...))
|
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(
|
||||||
|
fs.FileSystem{FileSystem: r.FileSystem}, r.Patterns...))
|
||||||
}
|
}
|
||||||
panic("the HTML debug render was created without files or glob pattern or file system with patterns")
|
panic("the HTML debug render was created without files or glob pattern or file system with patterns")
|
||||||
}
|
}
|
||||||
|
|
|
@ -489,10 +489,12 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
|
||||||
func TestRenderHTMLDebugFiles(t *testing.T) {
|
func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
htmlRender := HTMLDebug{
|
htmlRender := HTMLDebug{
|
||||||
Files: []string{"../testdata/template/hello.tmpl"},
|
Files: []string{"../testdata/template/hello.tmpl"},
|
||||||
Glob: "",
|
Glob: "",
|
||||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
FileSystem: nil,
|
||||||
FuncMap: nil,
|
Patterns: nil,
|
||||||
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
|
FuncMap: nil,
|
||||||
}
|
}
|
||||||
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
||||||
"name": "thinkerou",
|
"name": "thinkerou",
|
||||||
|
@ -508,10 +510,33 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||||
func TestRenderHTMLDebugGlob(t *testing.T) {
|
func TestRenderHTMLDebugGlob(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
htmlRender := HTMLDebug{
|
htmlRender := HTMLDebug{
|
||||||
Files: nil,
|
Files: nil,
|
||||||
Glob: "../testdata/template/hello*",
|
Glob: "../testdata/template/hello*",
|
||||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
FileSystem: nil,
|
||||||
FuncMap: nil,
|
Patterns: nil,
|
||||||
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
|
FuncMap: nil,
|
||||||
|
}
|
||||||
|
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
||||||
|
"name": "thinkerou",
|
||||||
|
})
|
||||||
|
|
||||||
|
err := instance.Render(w)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
|
||||||
|
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderHTMLDebugFS(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
htmlRender := HTMLDebug{
|
||||||
|
Files: nil,
|
||||||
|
Glob: "",
|
||||||
|
FileSystem: http.Dir("../testdata/template"),
|
||||||
|
Patterns: []string{"hello.tmpl"},
|
||||||
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
|
FuncMap: nil,
|
||||||
}
|
}
|
||||||
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
||||||
"name": "thinkerou",
|
"name": "thinkerou",
|
||||||
|
@ -526,10 +551,12 @@ func TestRenderHTMLDebugGlob(t *testing.T) {
|
||||||
|
|
||||||
func TestRenderHTMLDebugPanics(t *testing.T) {
|
func TestRenderHTMLDebugPanics(t *testing.T) {
|
||||||
htmlRender := HTMLDebug{
|
htmlRender := HTMLDebug{
|
||||||
Files: nil,
|
Files: nil,
|
||||||
Glob: "",
|
Glob: "",
|
||||||
Delims: Delims{"{{", "}}"},
|
FileSystem: nil,
|
||||||
FuncMap: nil,
|
Patterns: nil,
|
||||||
|
Delims: Delims{"{{", "}}"},
|
||||||
|
FuncMap: nil,
|
||||||
}
|
}
|
||||||
assert.Panics(t, func() { htmlRender.Instance("", nil) })
|
assert.Panics(t, func() { htmlRender.Instance("", nil) })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue