Implement a new View Engine for the Jet template parser as requested at: https://github.com/kataras/iris/issues/1281

Former-commit-id: 3e00bdfbf1f3998a1744c390c12fd70430ac0320
This commit is contained in:
Gerasimos (Makis) Maropoulos 2019-06-22 21:34:19 +03:00
parent b71d4032e6
commit 076d9121f1
22 changed files with 1124 additions and 40 deletions

View File

@ -339,6 +339,7 @@ Follow the examples below,
| handlebars | `iris.Handlebars(...)` |
| amber | `iris.Amber(...)` |
| pug(jade) | `iris.Pug(...)` |
| jet | `iris.Jet(...)` |
- [Overview](view/overview/main.go)
- [Hi](view/template_html_0/main.go)
@ -353,6 +354,8 @@ Follow the examples below,
- [Pug (Jade) Actions`](view/template_pug_1)
- [Pug (Jade) Includes`](view/template_pug_2)
- [Pug (Jade) Extends`](view/template_pug_3)
- [Jet](/view/template_jet_0) **NEW**
- [Jet Embedded](view/template_jet_1_embedded) **NEW**
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [hero templates](https://github.com/shiyanhui/hero/hero) files too, simply by using the `context#ResponseWriter`, take a look at the [http_responsewriter/quicktemplate](http_responsewriter/quicktemplate) and [http_responsewriter/herotemplate](http_responsewriter/herotemplate) examples.

View File

@ -32,7 +32,7 @@ func main() {
sess := sessions.New(sessions.Config{
Cookie: "sessionscookieid",
Expires: 0, // defauls to 0: unlimited life. Another good value is: 45 * time.Minute,
Expires: 0, // defaults to 0: unlimited life. Another good value is: 45 * time.Minute,
AllowReclaim: true,
})

View File

@ -5,11 +5,6 @@ import "github.com/kataras/iris"
func main() {
app := iris.New()
// - standard html | iris.HTML(...)
// - django | iris.Django(...)
// - pug(jade) | iris.Pug(...)
// - handlebars | iris.Handlebars(...)
// - amber | iris.Amber(...)
// with default template funcs:
//
// - {{ urlpath "mynamedroute" "pathParameter_ifneeded" }}

View File

@ -7,12 +7,6 @@ import (
func main() {
app := iris.New() // defaults to these
// - standard html | iris.HTML(...)
// - django | iris.Django(...)
// - pug(jade) | iris.Pug(...)
// - handlebars | iris.Handlebars(...)
// - amber | iris.Amber(...)
tmpl := iris.HTML("./templates", ".html")
tmpl.Reload(true) // reload templates on each request (development mode)
// default template funcs are:

View File

@ -0,0 +1,11 @@
# Jet Engine Example
This example is a fork of <https://github.com/CloudyKit/jet/tree/master/examples/todos> to work with Iris, so you can
notice the differences side by side.
Read more at: https://github.com/kataras/iris/issues/1281
> The Iris Jet View Engine fixes some bugs that the underline [jet template parser](https://github.com/CloudyKit/jet) currently has.
Continue by learning how you can [serve embedded templates](../template_jet_1_embedded).

View File

@ -0,0 +1,131 @@
// Package main shows how to use jet template parser with ease using the Iris built-in Jet view engine.
// This example is customized fork of https://github.com/CloudyKit/jet/tree/master/examples/todos, so you can
// notice the differences side by side.
package main
import (
"bytes"
"encoding/base64"
"fmt"
"os"
"reflect"
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/view"
)
type tTODO struct {
Text string
Done bool
}
type doneTODOs struct {
list map[string]*tTODO
keys []string
len int
i int
}
func (dt *doneTODOs) New(todos map[string]*tTODO) *doneTODOs {
dt.len = len(todos)
for k := range todos {
dt.keys = append(dt.keys, k)
}
dt.list = todos
return dt
}
// Range satisfies the jet.Ranger interface and only returns TODOs that are done,
// even when the list contains TODOs that are not done.
func (dt *doneTODOs) Range() (reflect.Value, reflect.Value, bool) {
for dt.i < dt.len {
key := dt.keys[dt.i]
dt.i++
if dt.list[key].Done {
return reflect.ValueOf(key), reflect.ValueOf(dt.list[key]), false
}
}
return reflect.Value{}, reflect.Value{}, true
}
func (dt *doneTODOs) Render(r *view.JetRuntime) {
r.Write([]byte(fmt.Sprintf("custom renderer")))
}
// Render implements jet.Renderer interface
func (t *tTODO) Render(r *view.JetRuntime) {
done := "yes"
if !t.Done {
done = "no"
}
r.Write([]byte(fmt.Sprintf("TODO: %s (done: %s)", t.Text, done)))
}
func main() {
//
// Type aliases:
// view.JetRuntimeVars = jet.VarMap
// view.JetRuntime = jet.Runtime
// view.JetArguments = jet.Arguments
//
// Iris also gives you the ability to put runtime variables
// from middlewares as well, by:
// view.AddJetRuntimeVars(ctx, vars)
// or tmpl.AddRuntimeVars(ctx, vars)
//
// The Iris Jet fixes the issue when custom jet.Ranger and custom jet.Renderer are not actually work at all,
// hope that this is a temp issue on the jet parser itself and authors will fix it soon
// so we can remove the "hacks" we've putted for those to work as expected.
app := iris.New()
tmpl := iris.Jet("./views", ".jet") // <--
tmpl.Reload(true) // remove in production.
tmpl.AddFunc("base64", func(a view.JetArguments) reflect.Value {
a.RequireNumOfArguments("base64", 1, 1)
buffer := bytes.NewBuffer(nil)
fmt.Fprint(buffer, a.Get(0))
return reflect.ValueOf(base64.URLEncoding.EncodeToString(buffer.Bytes()))
})
app.RegisterView(tmpl) // <--
var todos = map[string]*tTODO{
"example-todo-1": {Text: "Add an show todo page to the example project", Done: true},
"example-todo-2": {Text: "Add an add todo page to the example project"},
"example-todo-3": {Text: "Add an update todo page to the example project"},
"example-todo-4": {Text: "Add an delete todo page to the example project", Done: true},
}
app.Get("/", func(ctx iris.Context) {
ctx.View("todos/index.jet", todos) // <--
// Note that the `ctx.View` already logs the error if logger level is allowing it and returns the error.
})
app.Get("/todo", func(ctx iris.Context) {
id := ctx.URLParam("id")
todo, ok := todos[id]
if !ok {
ctx.Redirect("/")
return
}
ctx.View("todos/show.jet", todo)
})
app.Get("/all-done", func(ctx iris.Context) {
vars := make(view.JetRuntimeVars) // <-- or keep use the jet.VarMap, decision up to you, it refers to the same type.
vars.Set("showingAllDone", true)
view.AddJetRuntimeVars(ctx, vars) // <--
ctx.View("todos/index.jet", (&doneTODOs{}).New(todos))
})
port := os.Getenv("PORT")
if len(port) == 0 {
port = ":8080"
} else if !strings.HasPrefix(":", port) {
port = ":" + port
}
app.Run(iris.Addr(port))
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ isset(title) ? title : "" }}</title>
</head>
<body>
{{block documentBody()}}{{end}}
</body>
</html>

View File

@ -0,0 +1,30 @@
{{extends "layouts/application.jet"}}
{{block button(label, href="javascript:void(0)")}}
<a href="{{ href }}">{{ label }}</a>
{{end}}
{{block ul()}}
<ul>
{{yield content}}
</ul>
{{end}}
{{block documentBody()}}
<h1>List of TODOs</h1>
{{if isset(showingAllDone) && showingAllDone}}
<p>Showing only TODOs that are done</p>
{{else}}
<p><a href="/all-done">Show only TODOs that are done</a></p>
{{end}}
{{yield ul() content}}
{{range id, value := .}}
<li {{if value.Done}}style="color:red;text-decoration: line-through;"{{end}}>
<a href="/todo?id={{ id }}">{{ value.Text }}</a>
{{yield button(label="UP", href="/update/?id="+base64(id))}} - {{yield button(href="/delete/?id="+id, label="DL")}}
</li>
{{end}}
{{end}}
{{end}}

View File

@ -0,0 +1,9 @@
{{extends "layouts/application.jet"}}
{{block documentBody()}}
<h1>Show TODO</h1>
<p>This uses a custom renderer by implementing the jet.Renderer interface.
<p>
{{.}}
</p>
{{end}}

View File

@ -0,0 +1,25 @@
# Jet Engine Example (Embedded)
Take a look at the [../template_jet_0](../template_jet_0)'s README first.
This example teaches you how to use jet templates embedded in your applications with ease using the Iris built-in Jet view engine.
This example is a customized fork of https://github.com/CloudyKit/jet/tree/master/examples/asset_packaging, so you can
notice the differences side by side. For example, you don't have to use any external package inside your application,
Iris manually builds the template loader for binary data when Asset and AssetNames are available through tools like the [go-bindata](github.com/shuLhan/go-bindata).
Note that you can still use any custom loaders through the `JetEngine.SetLoader`
which overrides any previous loaders like `JetEngine.Binary` we use on this example.
## How to run
```sh
$ go get -u github.com/shuLhan/go-bindata/... # or any active alternative
$ go-bindata ./views/...
$ go build
$ ./template_jet_0_embedded
```
Repeat the above steps on any `./views` changes.
> html files are not used, only binary data. You can move or delete the `./views` folder.

View File

@ -0,0 +1,387 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// views\includes\_partial.jet
// views\includes\blocks.jet
// views\index.jet
// views\layouts\application.jet
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info fileInfoEx
}
type fileInfoEx interface {
os.FileInfo
MD5Checksum() string
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
md5checksum string
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) MD5Checksum() string {
return fi.md5checksum
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _bindataViewsincludespartialjet = []byte(
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb2\x29\xb0\xf3\xcc\x4b\xce\x29\x4d\x49\x4d\x51\x28\x48\x2c\x2a\xc9\x4c" +
"\xcc\xb1\xd1\x2f\xb0\xe3\x02\x04\x00\x00\xff\xff\xd0\x10\x20\x0a\x18\x00\x00\x00")
func bindataViewsincludespartialjetBytes() ([]byte, error) {
return bindataRead(
_bindataViewsincludespartialjet,
"views/includes/_partial.jet",
)
}
func bindataViewsincludespartialjet() (*asset, error) {
bytes, err := bindataViewsincludespartialjetBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{
name: "views/includes/_partial.jet",
size: 24,
md5checksum: "",
mode: os.FileMode(438),
modTime: time.Unix(1533831061, 0),
}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _bindataViewsincludesblocksjet = []byte(
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xaa\xae\x4e\xca\xc9\x4f\xce\x56\xc8\x4d\xcd\x2b\xd5\xd0\xac\xad\xe5\x52" +
"\x50\xb0\x29\xb0\x0b\xc9\x48\x05\x8b\x28\x40\x24\xcb\x13\x8b\x15\x32\xf3\xca\xf2\xb3\x53\x53\xf4\x6c\xf4\x0b\xec" +
"\xb8\xaa\xab\x53\xf3\x52\x6a\x6b\xb9\x00\x01\x00\x00\xff\xff\x3f\xde\x27\x27\x3e\x00\x00\x00")
func bindataViewsincludesblocksjetBytes() ([]byte, error) {
return bindataRead(
_bindataViewsincludesblocksjet,
"views/includes/blocks.jet",
)
}
func bindataViewsincludesblocksjet() (*asset, error) {
bytes, err := bindataViewsincludesblocksjetBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{
name: "views/includes/blocks.jet",
size: 62,
md5checksum: "",
mode: os.FileMode(438),
modTime: time.Unix(1533831061, 0),
}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _bindataViewsindexjet = []byte(
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x90\xb1\x6e\xf3\x30\x0c\x84\x77\x3d\xc5\xfd\x5e\xfe\x74\xa9\x91\xb5" +
"\x30\x3c\x14\xc8\xd0\xa5\x4b\x1f\xa0\x50\x2c\x06\x56\x23\x8b\x42\x48\xd7\x36\x04\xbf\x7b\x11\xc5\x29\x8a\x76\x22" +
"\xc8\xfb\x8e\x3c\x30\x67\x9a\x95\xa2\x13\x54\xc1\x2e\x3c\xaa\xd4\x36\xa5\xe0\x3b\xab\x9e\xe3\xe3\x07\x69\xb5\xae" +
"\x26\x67\x3f\x24\xbe\x28\x2a\x1f\xbb\x30\x3a\x92\xfa\x18\xb8\x3b\xcb\x1d\x30\x39\x97\x01\x1c\x77\xe3\x40\x51\x9f" +
"\xd9\x2d\xbb\x87\x75\x35\x40\xd3\xef\xdb\xc3\x70\x24\xe7\xc8\x81\x66\x3b\xa4\x40\x4d\xdd\xef\x5b\x73\x15\xa3\xfd" +
"\x6c\x0d\x00\xe4\xbc\x78\x0a\x0e\x03\xc5\xf1\xee\xac\x8b\x6a\xae\xe2\x76\xf8\x47\x82\xf7\x64\x2f\xea\x6d\xf8\xce" +
"\x50\xb0\x13\xfe\x6d\xc4\xcb\xe9\x30\x7b\x51\xd9\x55\x8e\x49\x5e\x59\x4b\x5b\xe8\xdb\x7a\xa0\x49\xed\x5b\xcf\x93" +
"\xa0\xe7\xe9\x8f\x0f\x13\x5f\xce\xf2\x84\xdf\x6e\x4c\x56\x10\x59\xb1\xf1\x0e\x47\xea\xec\x28\x04\xaf\x05\x8e\xff" +
"\x15\x54\xe8\xa6\x4e\x6d\x89\x45\xd1\x95\x37\xde\xea\x57\x00\x00\x00\xff\xff\xff\xe0\x1f\xae\x75\x01\x00\x00")
func bindataViewsindexjetBytes() ([]byte, error) {
return bindataRead(
_bindataViewsindexjet,
"views/index.jet",
)
}
func bindataViewsindexjet() (*asset, error) {
bytes, err := bindataViewsindexjetBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{
name: "views/index.jet",
size: 373,
md5checksum: "",
mode: os.FileMode(438),
modTime: time.Unix(1561227802, 0),
}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _bindataViewslayoutsapplicationjet = []byte(
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\x8e\xbd\xae\xc2\x30\x0c\x85\xf7\x3e\x85\x6f\xa6\xcb\x80\xba\x32\xb8" +
"\x1d\x80\xb2\xc2\x50\x06\xc6\x34\xb6\x08\x22\x3f\x08\x8c\x44\x15\xe5\xdd\x51\x08\x4c\xb6\xec\xf3\x7d\x3a\xf8\xb7" +
"\xdd\x6f\xc6\xd3\x61\x00\x2b\xde\xf5\x0d\x96\x01\x4e\x87\x73\xa7\x38\xa8\xbe\x01\x40\xcb\x9a\xca\x02\x80\x9e\x45" +
"\x83\xb1\xfa\xfe\x60\xe9\xd4\x71\xdc\x2d\x57\xea\xfb\x92\x8b\x38\xee\x07\x3f\x31\x11\x13\xf0\x4b\xfb\x9b\x63\x6c" +
"\xeb\xbd\x78\xda\x9f\x08\xa7\x48\x73\xc5\x52\x9a\x5c\x34\x57\xa0\x68\x9e\x9e\x83\xac\x23\xcd\xff\x8b\x9c\x53\xe2" +
"\x40\x39\x7f\xb0\x9a\xc6\xb6\x36\x7c\x07\x00\x00\xff\xff\x76\x86\x91\x20\xb2\x00\x00\x00")
func bindataViewslayoutsapplicationjetBytes() ([]byte, error) {
return bindataRead(
_bindataViewslayoutsapplicationjet,
"views/layouts/application.jet",
)
}
func bindataViewslayoutsapplicationjet() (*asset, error) {
bytes, err := bindataViewslayoutsapplicationjetBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{
name: "views/layouts/application.jet",
size: 178,
md5checksum: "",
mode: os.FileMode(438),
modTime: time.Unix(1561227776, 0),
}
a := &asset{bytes: bytes, info: info}
return a, nil
}
//
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
//
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
}
//
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
// nolint: deadcode
//
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
//
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or could not be loaded.
//
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
}
//
// AssetNames returns the names of the assets.
// nolint: deadcode
//
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
//
// _bindata is a table, holding each asset generator, mapped to its name.
//
var _bindata = map[string]func() (*asset, error){
"views/includes/_partial.jet": bindataViewsincludespartialjet,
"views/includes/blocks.jet": bindataViewsincludesblocksjet,
"views/index.jet": bindataViewsindexjet,
"views/layouts/application.jet": bindataViewslayoutsapplicationjet,
}
//
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
//
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, &os.PathError{
Op: "open",
Path: name,
Err: os.ErrNotExist,
}
}
}
}
if node.Func != nil {
return nil, &os.PathError{
Op: "open",
Path: name,
Err: os.ErrNotExist,
}
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{Func: nil, Children: map[string]*bintree{
"views": {Func: nil, Children: map[string]*bintree{
"includes": {Func: nil, Children: map[string]*bintree{
"_partial.jet": {Func: bindataViewsincludespartialjet, Children: map[string]*bintree{}},
"blocks.jet": {Func: bindataViewsincludesblocksjet, Children: map[string]*bintree{}},
}},
"index.jet": {Func: bindataViewsindexjet, Children: map[string]*bintree{}},
"layouts": {Func: nil, Children: map[string]*bintree{
"application.jet": {Func: bindataViewslayoutsapplicationjet, Children: map[string]*bintree{}},
}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@ -0,0 +1,31 @@
// Package main shows how to use jet templates embedded in your application with ease using the Iris built-in Jet view engine.
// This example is a customized fork of https://github.com/CloudyKit/jet/tree/master/examples/asset_packaging, so you can
// notice the differences side by side. For example, you don't have to use any external package inside your application,
// Iris manually builds the template loader for binary data when Asset and AssetNames are available via tools like the go-bindata.
package main
import (
"os"
"strings"
"github.com/kataras/iris"
)
func main() {
app := iris.New()
tmpl := iris.Jet("./views", ".jet").Binary(Asset, AssetNames)
app.RegisterView(tmpl)
app.Get("/", func(ctx iris.Context) {
ctx.View("index.jet")
})
port := os.Getenv("PORT")
if len(port) == 0 {
port = ":8080"
} else if !strings.HasPrefix(":", port) {
port = ":" + port
}
app.Run(iris.Addr(port))
}

View File

@ -0,0 +1 @@
<p>Included partial</p>

View File

@ -0,0 +1,3 @@
{{block menu()}}
<p>The menu block was invoked.</p>
{{end}}

View File

@ -0,0 +1,16 @@
{{extends "layouts/application.jet"}}
{{import "includes/blocks.jet"}}
{{block documentBody()}}
<h1>Embedded example</h1>
<nav>
{{yield menu()}}
</nav>
{{include "includes/_partial.jet"}}
{{if !includeIfExists("doesNotExist.jet")}}
<p>Shows how !includeIfExists works: doesNotExist.jet was not included because it doesn't exist.</p>
{{end}}
{{end}}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Embedded example</title>
</head>
<body>
{{block documentBody()}}{{end}}
</body>
</html>

View File

@ -2790,7 +2790,7 @@ func (ctx *context) View(filename string, optionalViewModel ...interface{}) erro
bindingData = ctx.values.Get(cfg.GetViewDataContextKey())
}
err := ctx.Application().View(ctx.writer, filename, layout, bindingData)
err := ctx.Application().View(ctx, filename, layout, bindingData)
if err != nil {
ctx.StatusCode(http.StatusInternalServerError)
ctx.StopExecution()

6
doc.go
View File

@ -1069,7 +1069,7 @@ Example code:
View Engine
Iris supports 5 template engines out-of-the-box, developers can still use any external golang template engine,
Iris supports 6 template engines out-of-the-box, developers can still use any external golang template engine,
as `context/context#ResponseWriter()` is an `io.Writer`.
All of these five template engines have common features with common API,
@ -1090,6 +1090,8 @@ like Layout, Template Funcs, Party-specific layout, partial rendering and more.
Amber,
its template parser is the github.com/eknkc/amber
Jet,
its template parser is the github.com/CloudyKit/jet
Example code:
@ -1105,6 +1107,7 @@ Example code:
// - pug(jade) | iris.Pug(...)
// - handlebars | iris.Handlebars(...)
// - amber | iris.Amber(...)
// - jet | iris.Jet(...)
tmpl := iris.HTML("./templates", ".html")
tmpl.Reload(true) // reload templates on each request (development mode)
@ -1195,6 +1198,7 @@ access to the engines' variables can be granded by "github.com/kataras/iris" pac
iris.Pug(...) >> >> view.Pug(...)
iris.Handlebars(...) >> >> view.Handlebars(...)
iris.Amber(...) >> >> view.Amber(...)
iris.Jet(...) >> >> view.Jet(...)
Each one of these template engines has different options located here: https://github.com/kataras/iris/tree/master/view .

13
iris.go
View File

@ -286,20 +286,23 @@ func (app *Application) Logger() *golog.Logger {
var (
// HTML view engine.
// Conversion for the view.HTML.
// Shortcut of the kataras/iris/view.HTML.
HTML = view.HTML
// Django view engine.
// Conversion for the view.Django.
// Shortcut of the kataras/iris/view.Django.
Django = view.Django
// Handlebars view engine.
// Conversion for the view.Handlebars.
// Shortcut of the kataras/iris/view.Handlebars.
Handlebars = view.Handlebars
// Pug view engine.
// Conversion for the view.Pug.
// Shortcut of the kataras/iris/view.Pug.
Pug = view.Pug
// Amber view engine.
// Conversion for the view.Amber.
// Shortcut of the kataras/iris/view.Amber.
Amber = view.Amber
// Jet view engine.
// Shortcut of the kataras/iris/view.Jet.
Jet = view.Jet
)
// NoLayout to disable layout for a particular template file

View File

@ -1,6 +1,6 @@
# View
Iris supports 5 template engines out-of-the-box, developers can still use any external golang template engine,
Iris supports 6 template engines out-of-the-box, developers can still use any external golang template engine,
as `context/context#ResponseWriter()` is an `io.Writer`.
All of these five template engines have common features with common API,
@ -11,6 +11,26 @@ like Layout, Template Funcs, Party-specific layout, partial rendering and more.
- Pug(Jade), its template parser is the [github.com/Joker/jade](https://github.com/Joker/jade)
- Handlebars, its template parser is the [github.com/aymerick/raymond](https://github.com/aymerick/raymond)
- Amber, its template parser is the [github.com/eknkc/amber](https://github.com/eknkc/amber)
- Jet, its template parser is the [github.com/CloudyKit/jet](https://github.com/CloudyKit/jet)
## Examples
- [Overview](https://github.com/kataras/iris/blob/master/_examples/view/overview/main.go)
- [Hi](https://github.com/kataras/iris/blob/master/_examples/view/template_html_0/main.go)
- [A simple Layout](https://github.com/kataras/iris/blob/master/_examples/view/template_html_1/main.go)
- [Layouts: `yield` and `render` tmpl funcs](https://github.com/kataras/iris/blob/master/_examples/view/template_html_2/main.go)
- [The `urlpath` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_3/main.go)
- [The `url` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_4/main.go)
- [Inject Data Between Handlers](https://github.com/kataras/iris/blob/master/_examples/view/context-view-data/main.go)
- [Embedding Templates Into App Executable File](https://github.com/kataras/iris/blob/master/_examples/view/embedding-templates-into-app/main.go)
- [Greeting with Pug (Jade)`](view/template_pug_0)
- [Pug (Jade) Actions`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_1)
- [Pug (Jade) Includes`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_2)
- [Pug (Jade) Extends`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_3)
- [Jet](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_0) **NEW**
- [Jet Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_1_embedded) **NEW**
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `context#ResponseWriter`, take a look at the [iris/_examples/http_responsewriter/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/http_responsewriter/quicktemplate) example.
## Overview
@ -69,12 +89,6 @@ import "github.com/kataras/iris"
func main() {
app := iris.New()
// - standard html | iris.HTML(...)
// - django | iris.Django(...)
// - pug(jade) | iris.Pug(...)
// - handlebars | iris.Handlebars(...)
// - amber | iris.Amber(...)
tmpl := iris.HTML("./templates", ".html")
// builtin template funcs are:
@ -161,17 +175,4 @@ Example code:
pugEngine := iris.Pug("./templates", ".jade")
pugEngine.Reload(true) // <--- set to true to re-build the templates on each request.
app.RegisterView(pugEngine)
```
## Examples
- [Overview](https://github.com/kataras/iris/blob/master/_examples/view/overview/main.go)
- [Hi](https://github.com/kataras/iris/blob/master/_examples/view/template_html_0/main.go)
- [A simple Layout](https://github.com/kataras/iris/blob/master/_examples/view/template_html_1/main.go)
- [Layouts: `yield` and `render` tmpl funcs](https://github.com/kataras/iris/blob/master/_examples/view/template_html_2/main.go)
- [The `urlpath` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_3/main.go)
- [The `url` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_4/main.go)
- [Inject Data Between Handlers](https://github.com/kataras/iris/blob/master/_examples/view/context-view-data/main.go)
- [Embedding Templates Into App Executable File](https://github.com/kataras/iris/blob/master/_examples/view/embedding-templates-into-app/main.go)
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `context#ResponseWriter`, take a look at the [iris/_examples/http_responsewriter/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/http_responsewriter/quicktemplate) example.
```

View File

@ -29,3 +29,15 @@ type Engine interface {
// Ext should return the final file extension which this view engine is responsible to render.
Ext() string
}
type namedEngine interface {
String() string
}
func getEngineName(e Engine) string {
if n, ok := e.(namedEngine); ok {
return n.String()
}
return ""
}

408
view/jet.go Normal file
View File

@ -0,0 +1,408 @@
package view
import (
"fmt"
"io"
"os"
"path"
"reflect"
"strings"
"github.com/kataras/iris/context"
"github.com/CloudyKit/jet"
)
const jetEngineName = "jet"
// JetEngine is the jet template parser's view engine.
type JetEngine struct {
directory string
extension string
// physical system files or app-embedded, see `Binary(..., ...)`. Defaults to file system on initialization.
loader jet.Loader
developmentMode bool
// The Set is the `*jet.Set`, exported to offer any custom capabilities that jet users may want.
// Available after `Load`.
Set *jet.Set
// Note that global vars and functions are set in a single spot on the jet parser.
// If AddFunc or AddVar called before `Load` then these will be set here to be used via `Load` and clear.
vars map[string]interface{}
}
var _ Engine = (*JetEngine)(nil)
// jet library does not export or give us any option to modify them via Set
// (unless we parse the files by ourselves but this is not a smart choice).
var jetExtensions = [...]string{
".html.jet",
".jet.html",
".jet",
}
// Jet creates and returns a new jet view engine.
func Jet(directory, extension string) *JetEngine {
extOK := false
for _, ext := range jetExtensions {
if ext == extension {
extOK = true
break
}
}
if !extOK {
panic(fmt.Sprintf("%s extension is not a valid jet engine extension[%s]", extension, strings.Join(jetExtensions[0:], ", ")))
}
s := &JetEngine{
directory: directory,
extension: extension,
loader: jet.NewOSFileSystemLoader(directory),
}
return s
}
// String returns the name of this view engine, the "jet".
func (s *JetEngine) String() string {
return jetEngineName
}
// Ext should return the final file extension which this view engine is responsible to render.
func (s *JetEngine) Ext() string {
return s.extension
}
// Delims sets the action delimiters to the specified strings, to be used in
// templates. An empty delimiter stands for the
// corresponding default: {{ or }}.
// Should act before `Load` or `iris.Application#RegisterView`.
func (s *JetEngine) Delims(left, right string) *JetEngine {
s.Set.Delims(left, right)
return s
}
// JetArguments is a type alias of `jet.Arguments`,
// can be used on `AddFunc$funcBody`.
type JetArguments = jet.Arguments
// AddFunc should adds a global function to the jet template set.
func (s *JetEngine) AddFunc(funcName string, funcBody interface{}) {
// if something like "urlpath" is registered.
if generalFunc, ok := funcBody.(func(string, ...interface{}) string); ok {
// jet, unlike others does not accept a func(string, ...interface{}) string,
// instead it wants:
// func(JetArguments) reflect.Value.
s.AddVar(funcName, func(args JetArguments) reflect.Value {
n := args.NumOfArguments()
if n == 0 { // no input, don't execute the function, panic instead.
panic(funcName + " expects one or more input arguments")
}
firstInput := args.Get(0).String()
if n == 1 { // if only the first argument is given.
return reflect.ValueOf(generalFunc(firstInput))
}
// if has variadic.
variadicN := n - 1
variadicInputs := make([]interface{}, variadicN) // except the first one.
for i := 0; i < variadicN; i++ {
variadicInputs[i] = args.Get(i + 1).Interface()
}
return reflect.ValueOf(generalFunc(firstInput, variadicInputs...))
})
return
}
if jetFunc, ok := funcBody.(jet.Func); !ok {
alternativeJetFunc, ok := funcBody.(func(JetArguments) reflect.Value)
if !ok {
panic(fmt.Sprintf("JetEngine.AddFunc: funcBody argument is not a type of func(JetArguments) reflect.Value. Got %T instead", funcBody))
}
s.AddVar(funcName, jet.Func(alternativeJetFunc))
} else {
s.AddVar(funcName, jetFunc)
}
}
// AddVar adds a global variable to the jet template set.
func (s *JetEngine) AddVar(key string, value interface{}) {
if s.Set != nil {
s.Set.AddGlobal(key, value)
} else {
if s.vars == nil {
s.vars = make(map[string]interface{})
}
s.vars[key] = value
}
}
// Reload if setted to true the templates are reloading on each render,
// use it when you're in development and you're boring of restarting
// the whole app when you edit a template file.
//
// Note that if `true` is passed then only one `View -> ExecuteWriter` will be render each time,
// not safe concurrent access across clients, use it only on development state.
func (s *JetEngine) Reload(developmentMode bool) *JetEngine {
s.developmentMode = developmentMode
if s.Set != nil {
s.Set.SetDevelopmentMode(developmentMode)
}
return s
}
// SetLoader can be used when the caller wants to use something like
// multi.Loader or httpfs.Loader of the jet subpackages,
// overrides any previous loader may set by `Binary` or the default.
// Should act before `Load` or `iris.Application#RegisterView`.
func (s *JetEngine) SetLoader(loader jet.Loader) *JetEngine {
s.loader = loader
return s
}
// Binary optionally, use it when template files are distributed
// inside the app executable (.go generated files).
//
// The assetFn and namesFn can come from the go-bindata library.
// Should act before `Load` or `iris.Application#RegisterView`.
func (s *JetEngine) Binary(assetFn func(name string) ([]byte, error), assetNames func() []string) *JetEngine {
// embedded.
vdir := s.directory
if vdir[0] == '.' {
vdir = vdir[1:]
}
// second check for /something, (or ./something if we had dot on 0 it will be removed)
if vdir[0] == '/' || vdir[0] == os.PathSeparator {
vdir = vdir[1:]
}
// check for trailing slashes because new users may be do that by mistake
// although all examples are showing the correct way but you never know
// i.e "./assets/" is not correct, if was inside "./assets".
// remove last "/".
if trailingSlashIdx := len(vdir) - 1; vdir[trailingSlashIdx] == '/' {
vdir = vdir[0:trailingSlashIdx]
}
namesSlice := assetNames()
names := make(map[string]struct{})
for _, name := range namesSlice {
if !strings.HasPrefix(name, vdir) {
continue
}
extOK := false
fileExt := path.Ext(name)
for _, ext := range jetExtensions {
if ext == fileExt {
extOK = true
break
}
}
if !extOK {
continue
}
names[name] = struct{}{}
}
if len(names) == 0 {
panic("JetEngine.Binary: no embedded files found in directory: " + vdir)
}
s.loader = &embeddedLoader{
vdir: vdir,
asset: assetFn,
names: names,
}
return s
}
type (
embeddedLoader struct {
vdir string
asset func(name string) ([]byte, error)
names map[string]struct{}
}
embeddedFile struct {
contents []byte // the contents are NOT consumed.
readen int64
}
)
var (
_ jet.Loader = (*embeddedLoader)(nil)
_ io.ReadCloser = (*embeddedFile)(nil)
)
func (f *embeddedFile) Close() error { return nil }
func (f *embeddedFile) Read(p []byte) (int, error) {
if f.readen >= int64(len(f.contents)) {
return 0, io.EOF
}
n := copy(p, f.contents[f.readen:])
f.readen += int64(n)
return n, nil
}
// Open opens a file from OS file system.
func (l *embeddedLoader) Open(name string) (io.ReadCloser, error) {
// name = path.Join(l.vdir, name)
contents, err := l.asset(name)
if err != nil {
return nil, err
}
return &embeddedFile{
contents: contents,
}, nil
}
// Exists checks if the template name exists by walking the list of template paths
// returns string with the full path of the template and bool true if the template file was found
func (l *embeddedLoader) Exists(name string) (string, bool) {
fileName := path.Join(l.vdir, name)
if _, ok := l.names[fileName]; ok {
return fileName, true
}
return "", false
}
// Load should load the templates from a physical system directory or by an embedded one (assets/go-bindata).
func (s *JetEngine) Load() error {
s.Set = jet.NewHTMLSetLoader(s.loader)
s.Set.SetDevelopmentMode(s.developmentMode)
if s.vars != nil {
for key, value := range s.vars {
s.Set.AddGlobal(key, value)
}
}
// Note that, unlike the rest of template engines implementations,
// we don't call the Set.GetTemplate to parse the templates,
// we let it to the jet template parser itself which does that at serve-time and caches each template by itself.
return nil
}
type (
// JetRuntimeVars is a type alias for `jet.VarMap`.
// Can be used at `AddJetRuntimeVars/JetEngine.AddRuntimeVars`
// to set a runtime variable ${name} to the executing template.
JetRuntimeVars = jet.VarMap
// JetRuntime is a type alias of `jet.Runtime`,
// can be used on RuntimeVariable input function.
JetRuntime = jet.Runtime
)
// JetRuntimeVarsContextKey is the Iris Context key to keep any custom jet runtime variables.
// See `AddJetRuntimeVars` package-level function and `JetEngine.AddRuntimeVars` method.
const JetRuntimeVarsContextKey = "iris.jetvarmap"
// AddJetRuntimeVars sets or inserts runtime jet variables through the Iris Context.
// This gives the ability to add runtime variables from different handlers in the request chain,
// something that the jet template parser does not offer at all.
//
// Usage: view.AddJetRuntimeVars(ctx, view.JetRuntimeVars{...}).
// See `JetEngine.AddRuntimeVars` too.
func AddJetRuntimeVars(ctx context.Context, jetVarMap JetRuntimeVars) {
if v := ctx.Values().Get(JetRuntimeVarsContextKey); v != nil {
if vars, ok := v.(JetRuntimeVars); ok {
for key, value := range jetVarMap {
vars[key] = value
}
return
}
}
ctx.Values().Set(JetRuntimeVarsContextKey, jetVarMap)
}
// AddRuntimeVars sets or inserts runtime jet variables through the Iris Context.
// This gives the ability to add runtime variables from different handlers in the request chain,
// something that the jet template parser does not offer at all.
//
// Usage: view.AddJetRuntimeVars(ctx, view.JetRuntimeVars{...}).
// See `view.AddJetRuntimeVars` if package-level access is more meanful to the code flow.
func (s *JetEngine) AddRuntimeVars(ctx context.Context, vars JetRuntimeVars) {
AddJetRuntimeVars(ctx, vars)
}
type rangerAndRenderer struct {
ranger jet.Ranger
renderer jet.Renderer
}
func (rr rangerAndRenderer) Range() (reflect.Value, reflect.Value, bool) {
return rr.ranger.Range()
}
func (rr rangerAndRenderer) Render(jetRuntime *jet.Runtime) {
rr.renderer.Render(jetRuntime)
}
// ExecuteWriter should execute a template by its filename with an optional layout and bindingData.
func (s *JetEngine) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
tmpl, err := s.Set.GetTemplate(filename)
if err != nil {
return err
}
var vars JetRuntimeVars
if ctx, ok := w.(context.Context); ok {
runtimeVars := ctx.Values().Get(JetRuntimeVarsContextKey)
if runtimeVars != nil {
if jetVars, ok := runtimeVars.(JetRuntimeVars); ok {
vars = jetVars
}
}
}
if ranger, ok := bindingData.(jet.Ranger); ok {
// Externally fixes a BUG on the jet template parser:
// eval.go#executeList(list *ListNode):NodeRange.isSet.getRanger(expression = st.evalPrimaryExpressionGroup)
// which does not act the "ranger" as element, instead is converted to a value of struct, which makes a jet.Ranger func(*myStruct) Range...
// not a compatible jet.Ranger.
// getRanger(st.context) should work but author of the jet library is not currently available,
// to allow a recommentation or a PR and I don't really want to vendor it because
// some end-users may use the jet import path to pass things like Global Funcs and etc.
// So to fix it (at least temporarily and only for ref Ranger) we ptr the ptr the "ranger", not the bindingData, and this may
// have its downside because the same bindingData may be compatible with other node actions like range or custom Render
// but we have no other way at the moment. The same problem exists on the `Renderer` too!
// The solution below fixes the above issue but any fields of the struct are not available,
// this is ok because most of the times if not always, the users of jet don't use fields on Ranger and custom Renderer inside the templates.
if renderer, ok := bindingData.(jet.Renderer); ok {
// this can make a Ranger and Renderer both work together, unlike the jet parser itself.
return tmpl.Execute(w, vars, rangerAndRenderer{ranger, renderer})
}
return tmpl.Execute(w, vars, &ranger)
}
if renderer, ok := bindingData.(jet.Renderer); ok {
// Here the fields are not available but usually if completes the jet.Renderer no
// fields are used in the template.
return tmpl.Execute(w, vars, &renderer) // see above ^.
}
return tmpl.Execute(w, vars, bindingData)
}