diff --git a/HISTORY.md b/HISTORY.md index 079c3ff2..d111a06e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,8 +28,10 @@ The codebase for Dependency Injection, Internationalization and localization and ## Fixes and Improvements +- Support of direct embedded view templates with `embed.FS` or `fs.FS` (in addition to `string` and `http.FileSystem` types). + - Add `iris.Patches()` package-level function to customize Iris Request Context REST (and more to come) behavior. -- Add support for `embed.FS` on `app.HandleDir`. +- Add support for `embed.FS` and `fs.FS` on `app.HandleDir`. - Minor fixes. - Enable setting a custom "go-redis" client through `SetClient` go redis driver method or `Client` struct field on sessions/database/redis driver as requested at [chat](https://chat.iris-go.com). diff --git a/_examples/README.md b/_examples/README.md index 35fe0955..e4c9ec76 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -151,6 +151,7 @@ * [Inject Data Between Handlers](view/context-view-data/main.go) * [Inject Engine Between Handlers](view/context-view-engine/main.go) * [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go) + * [Embedding Templates Into App Executable File (Bindata)](view/embedding-templates-into-app-bindata/main.go) * [Write to a custom `io.Writer`](view/write-to) * Parse a Template from Text * [HTML, Pug and Ace](view/parse-parse/main.go) diff --git a/_examples/view/embedding-templates-into-app/bindata.go b/_examples/view/embedding-templates-into-app-bindata/bindata.go similarity index 100% rename from _examples/view/embedding-templates-into-app/bindata.go rename to _examples/view/embedding-templates-into-app-bindata/bindata.go diff --git a/_examples/view/embedding-templates-into-app-bindata/main.go b/_examples/view/embedding-templates-into-app-bindata/main.go new file mode 100644 index 00000000..ffd83683 --- /dev/null +++ b/_examples/view/embedding-templates-into-app-bindata/main.go @@ -0,0 +1,52 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + + // $ go install github.com/go-bindata/go-bindata/v3/go-bindata@latest + // $ go-bindata -fs -prefix "templates" ./templates/... + // $ go run . + // html files are not used, you can delete the folder and run the example. + tmpl := iris.HTML(AssetFile(), ".html") + tmpl.Layout("layouts/layout.html") + tmpl.AddFunc("greet", func(s string) string { + return "Greetings " + s + "!" + }) + + app.RegisterView(tmpl) + + app.Get("/", func(ctx iris.Context) { + if err := ctx.View("page1.html"); err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + ctx.Writef(err.Error()) + } + }) + + // remove the layout for a specific route + app.Get("/nolayout", func(ctx iris.Context) { + ctx.ViewLayout(iris.NoLayout) + if err := ctx.View("page1.html"); err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + ctx.Writef(err.Error()) + } + }) + + // set a layout for a party, .Layout should be BEFORE any Get or other Handle party's method + my := app.Party("/my").Layout("layouts/mylayout.html") + { // both of these will use the layouts/mylayout.html as their layout. + my.Get("/", func(ctx iris.Context) { + ctx.View("page1.html") + }) + my.Get("/other", func(ctx iris.Context) { + ctx.View("page1.html") + }) + } + + // http://localhost:8080 + // http://localhost:8080/nolayout + // http://localhost:8080/my + // http://localhost:8080/my/other + app.Listen(":8080") +} diff --git a/_examples/view/embedding-templates-into-app-bindata/templates/layouts/layout.html b/_examples/view/embedding-templates-into-app-bindata/templates/layouts/layout.html new file mode 100644 index 00000000..69b545ec --- /dev/null +++ b/_examples/view/embedding-templates-into-app-bindata/templates/layouts/layout.html @@ -0,0 +1,12 @@ + + +Layout + + + +

This is the global layout

+
+ + {{ yield }} + + diff --git a/_examples/view/embedding-templates-into-app-bindata/templates/layouts/mylayout.html b/_examples/view/embedding-templates-into-app-bindata/templates/layouts/mylayout.html new file mode 100644 index 00000000..d22426fe --- /dev/null +++ b/_examples/view/embedding-templates-into-app-bindata/templates/layouts/mylayout.html @@ -0,0 +1,12 @@ + + +my Layout + + + +

This is the layout for the /my/ and /my/other routes only

+
+ + {{ yield }} + + diff --git a/_examples/view/embedding-templates-into-app-bindata/templates/page1.html b/_examples/view/embedding-templates-into-app-bindata/templates/page1.html new file mode 100644 index 00000000..6f63f7b3 --- /dev/null +++ b/_examples/view/embedding-templates-into-app-bindata/templates/page1.html @@ -0,0 +1,7 @@ +
+ +

Page 1 {{ greet "iris developer"}}

+ + {{ render "partials/page1_partial1.html"}} + +
diff --git a/_examples/view/embedding-templates-into-app-bindata/templates/partials/page1_partial1.html b/_examples/view/embedding-templates-into-app-bindata/templates/partials/page1_partial1.html new file mode 100644 index 00000000..66ba9266 --- /dev/null +++ b/_examples/view/embedding-templates-into-app-bindata/templates/partials/page1_partial1.html @@ -0,0 +1,3 @@ +
+

Page 1's Partial 1

+
diff --git a/_examples/view/embedding-templates-into-app/main.go b/_examples/view/embedding-templates-into-app/main.go index ffd83683..fca9a2ee 100644 --- a/_examples/view/embedding-templates-into-app/main.go +++ b/_examples/view/embedding-templates-into-app/main.go @@ -1,15 +1,19 @@ package main -import "github.com/kataras/iris/v12" +import ( + "embed" + + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/x/errors" +) + +//go:embed templates/* +var embeddedTemplatesFS embed.FS func main() { app := iris.New() - // $ go install github.com/go-bindata/go-bindata/v3/go-bindata@latest - // $ go-bindata -fs -prefix "templates" ./templates/... - // $ go run . - // html files are not used, you can delete the folder and run the example. - tmpl := iris.HTML(AssetFile(), ".html") + tmpl := iris.HTML(embeddedTemplatesFS, ".html") tmpl.Layout("layouts/layout.html") tmpl.AddFunc("greet", func(s string) string { return "Greetings " + s + "!" @@ -19,8 +23,8 @@ func main() { app.Get("/", func(ctx iris.Context) { if err := ctx.View("page1.html"); err != nil { - ctx.StatusCode(iris.StatusInternalServerError) - ctx.Writef(err.Error()) + errors.InvalidArgument.Err(ctx, err) + return } }) @@ -28,8 +32,8 @@ func main() { app.Get("/nolayout", func(ctx iris.Context) { ctx.ViewLayout(iris.NoLayout) if err := ctx.View("page1.html"); err != nil { - ctx.StatusCode(iris.StatusInternalServerError) - ctx.Writef(err.Error()) + errors.InvalidArgument.Err(ctx, err) + return } }) diff --git a/aliases.go b/aliases.go index b4f671d9..7d68f146 100644 --- a/aliases.go +++ b/aliases.go @@ -805,6 +805,13 @@ func (cp *ContextPatches) SetCookieKVExpiration(patch time.Duration) { context.SetCookieKVExpiration = patch } +// ResolveFS modifies the default way to resolve a filesystem by any type of value. +// It affects the view engine filesystem resolver +// and the Application's API Builder's `HandleDir` method. +func (cp *ContextPatches) ResolveFS(patchFunc func(fsOrDir interface{}) http.FileSystem) { + context.ResolveFS = patchFunc +} + // ContextWriterPatches features the context's writers patches. type ContextWriterPatches struct{} diff --git a/context/context_fs.go b/context/context_fs.go new file mode 100644 index 00000000..9495cf5d --- /dev/null +++ b/context/context_fs.go @@ -0,0 +1,46 @@ +package context + +import ( + "embed" + "fmt" + "io/fs" + "net/http" +) + +// ResolveFS accepts a single input argument of any type +// and tries to cast it to http.FileSystem. +// +// It affects the view engine filesystem resolver +// and the Application's API Builder's `HandleDir` method. +// +// This package-level variable can be modified on initialization. +var ResolveFS = func(fsOrDir interface{}) http.FileSystem { + var fileSystem http.FileSystem + switch v := fsOrDir.(type) { + case string: + fileSystem = http.Dir(v) + case http.FileSystem: + fileSystem = v + case embed.FS: + direEtries, err := v.ReadDir(".") + if err != nil { + panic(err) + } + + if len(direEtries) == 0 { + panic("HandleDir: no directories found under the embedded file system") + } + + subfs, err := fs.Sub(v, direEtries[0].Name()) + if err != nil { + panic(err) + } + fileSystem = http.FS(subfs) + case fs.FS: + fileSystem = http.FS(v) + default: + panic(fmt.Sprintf(`unexpected "fsOrDir" argument type of %T (string or http.FileSystem or embed.FS or fs.FS)`, v)) + } + + return fileSystem +} diff --git a/view/fs.go b/view/fs.go index 8e54fa5a..8fb503c9 100644 --- a/view/fs.go +++ b/view/fs.go @@ -7,6 +7,8 @@ import ( "path" "path/filepath" "sort" + + "github.com/kataras/iris/v12/context" ) // walk recursively in "fs" descends "root" path, calling "walkFn". @@ -90,17 +92,14 @@ func getFS(fsOrDir interface{}) (fs http.FileSystem) { return noOpFS{} } - switch v := fsOrDir.(type) { - case string: + if v, ok := fsOrDir.(string); ok { if v == "" { fs = noOpFS{} } else { fs = httpDirWrapper{http.Dir(v)} } - case http.FileSystem: - fs = v - default: - panic(fmt.Errorf(`unexpected "fsOrDir" argument type of %T (string or http.FileSystem)`, v)) + } else { + fs = context.ResolveFS(fsOrDir) } return