mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
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:
parent
b71d4032e6
commit
076d9121f1
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
||||
|
|
|
@ -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" }}
|
||||
|
|
|
@ -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:
|
||||
|
|
11
_examples/view/template_jet_0/README.md
Normal file
11
_examples/view/template_jet_0/README.md
Normal 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).
|
131
_examples/view/template_jet_0/main.go
Normal file
131
_examples/view/template_jet_0/main.go
Normal 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))
|
||||
}
|
10
_examples/view/template_jet_0/views/layouts/application.jet
Normal file
10
_examples/view/template_jet_0/views/layouts/application.jet
Normal 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>
|
30
_examples/view/template_jet_0/views/todos/index.jet
Normal file
30
_examples/view/template_jet_0/views/todos/index.jet
Normal 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}}
|
9
_examples/view/template_jet_0/views/todos/show.jet
Normal file
9
_examples/view/template_jet_0/views/todos/show.jet
Normal 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}}
|
25
_examples/view/template_jet_1_embedded/README.md
Normal file
25
_examples/view/template_jet_1_embedded/README.md
Normal 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.
|
387
_examples/view/template_jet_1_embedded/bindata.go
Normal file
387
_examples/view/template_jet_1_embedded/bindata.go
Normal 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, "/")...)...)
|
||||
}
|
31
_examples/view/template_jet_1_embedded/main.go
Normal file
31
_examples/view/template_jet_1_embedded/main.go
Normal 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))
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<p>Included partial</p>
|
|
@ -0,0 +1,3 @@
|
|||
{{block menu()}}
|
||||
<p>The menu block was invoked.</p>
|
||||
{{end}}
|
16
_examples/view/template_jet_1_embedded/views/index.jet
Normal file
16
_examples/view/template_jet_1_embedded/views/index.jet
Normal 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}}
|
|
@ -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>
|
|
@ -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
6
doc.go
|
@ -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
13
iris.go
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
```
|
|
@ -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
408
view/jet.go
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user