mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
add 'iris.Blocks' template engine
read more about its features at: https://github.com/kataras/blocks
This commit is contained in:
parent
6844be57ea
commit
b363492cca
|
@ -359,7 +359,9 @@ Response:
|
|||
|
||||
Other Improvements:
|
||||
|
||||
- Add [Ace](_examples/view/template_ace_0) template parser to the view engine and other minor improvements. <!-- a new html/template-based engine, with a different layout-page syntax but better performance, follows this week. -->
|
||||
- Add [Blocks](_examples/view/template_blocks_0) template engine. <!-- Reminder for @kataras: follow https://github.com/flosch/pongo2/pull/236#issuecomment-668950566 discussion so we can get back on using the original pongo2 repository as they fixed the issue about an incompatible 3rd party package (although they need more fixes, that's why I commented there) -->
|
||||
|
||||
- Add [Ace](_examples/view/template_ace_0) template parser to the view engine and other minor improvements.
|
||||
|
||||
- Fix huge repo size of 55.7MB, which slows down the overall Iris installation experience. Now, go-get performs ~3 times faster. I 've managed it using the [bfg-repo-cleaner](https://github.com/rtyley/bfg-repo-cleaner) tool - an alternative to git-filter-branch command. Watch the small gif below to learn how:
|
||||
|
||||
|
|
21
NOTICE
21
NOTICE
|
@ -6,7 +6,7 @@
|
|||
|
||||
The following 3rd-party software packages may be used by or distributed with iris. This document was automatically generated by FOSSA on 2020-5-8; any information relevant to third-party vendors listed below are collected using common, reasonable means.
|
||||
|
||||
Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911
|
||||
Revision ID: 6844be57ea3a098ef28fce3468959bf5bb6f735a
|
||||
|
||||
----------------- ----------------- ------------------------------------------
|
||||
Package Version Website
|
||||
|
@ -20,12 +20,15 @@ Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911
|
|||
bbolt a8af23b57f672fe https://github.com/etcd-io/bbolt
|
||||
f05637de531bba5
|
||||
aa00013364
|
||||
blackfriday 48b3da6a6f3865c https://github.com/iris-contrib/
|
||||
7eb1eba96d74cf0 blackfriday
|
||||
a16f63faca
|
||||
bluemonday 0a75d7616912ab9 https://github.com/microcosm-cc/
|
||||
beb9cc6f7283ec1 bluemonday
|
||||
blackfriday d3b5b032dc8e892 https://github.com/russross/blackfriday
|
||||
7d31a5071b56e14
|
||||
c89f045135
|
||||
bluemonday 0a75d7616912ab9 https://github.com/microcosm-cc/bluemonday
|
||||
beb9cc6f7283ec1
|
||||
917c61b135
|
||||
blocks 39dac49c58634f7 https://github.com/kataras/blocks
|
||||
9bc54fcd5c299da
|
||||
fe08dbd738
|
||||
brotli c3da72aa01ed78f https://github.com/andybalholm/brotli
|
||||
164593b9624fd91
|
||||
d25082d2d2
|
||||
|
@ -71,9 +74,9 @@ Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911
|
|||
pio 2e3d576cc65913a https://github.com/kataras/pio
|
||||
dd6106f1ce02837
|
||||
2c7e6d943c
|
||||
pongo2 0738cc45f23da6a https://github.com/iris-contrib/pongo2
|
||||
8c14959779a75d3
|
||||
37b7944fb6
|
||||
pongo2 5abacdfa4915f8a https://github.com/flosch/pongo2
|
||||
fb6de6231455488
|
||||
066273bea6
|
||||
protobuf 6c66de79d66478d https://github.com/golang/protobuf
|
||||
166c7ea05f5d2cc
|
||||
af016fbd6b
|
||||
|
|
|
@ -103,6 +103,8 @@
|
|||
* [Inject Data Between Handlers](view/context-view-data/main.go)
|
||||
* [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go)
|
||||
* [Write to a custom `io.Writer`](view/write-to)
|
||||
* [Blocks](view/template_blocks_0)
|
||||
* [Blocks Embedded](view/template_blocks_1_embedded)
|
||||
* [Pug: Greeting](view/template_pug_0)
|
||||
* [Pug: `Actions`](view/template_pug_1)
|
||||
* [Pug: `Includes`](view/template_pug_2)
|
||||
|
|
|
@ -15,8 +15,7 @@ func main() {
|
|||
|
||||
// $ go get -u github.com/go-bindata/go-bindata/v3/go-bindata
|
||||
// $ go-bindata ./templates/...
|
||||
// $ go build
|
||||
// $ ./embedding-templates-into-app
|
||||
// $ go run .
|
||||
// html files are not used, you can delete the folder and run the example.
|
||||
tmpl.Binary(Asset, AssetNames) // <-- IMPORTANT
|
||||
|
||||
|
|
35
_examples/view/template_blocks_0/main.go
Normal file
35
_examples/view/template_blocks_0/main.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import "github.com/kataras/iris/v12"
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
// Read about its syntax at: https://github.com/kataras/blocks
|
||||
app.RegisterView(iris.Blocks("./views", ".html").Reload(true))
|
||||
|
||||
app.Get("/", index)
|
||||
app.Get("/500", internalServerError)
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
func index(ctx iris.Context) {
|
||||
data := iris.Map{
|
||||
"Title": "Page Title",
|
||||
}
|
||||
|
||||
ctx.ViewLayout("main")
|
||||
ctx.View("index", data)
|
||||
}
|
||||
|
||||
func internalServerError(ctx iris.Context) {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
|
||||
data := iris.Map{
|
||||
"Code": iris.StatusInternalServerError,
|
||||
"Message": "Internal Server Error",
|
||||
}
|
||||
|
||||
ctx.ViewLayout("error")
|
||||
ctx.View("500", data)
|
||||
}
|
12
_examples/view/template_blocks_0/views/500.html
Normal file
12
_examples/view/template_blocks_0/views/500.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!-- You can define more than one block.
|
||||
The default one is "content" which should be the main template's body.
|
||||
So, even if it's missing (see index.html), it's added automatically by the view engine.
|
||||
When you need to define more than one block, you have to be more specific:
|
||||
-->
|
||||
{{ define "content" }}
|
||||
<h1>Internal Server Error</h1>
|
||||
{{ end }}
|
||||
|
||||
{{ define "message" }}
|
||||
<p style="color:red;">{{.Message}}</p>
|
||||
{{ end }}
|
1
_examples/view/template_blocks_0/views/index.html
Normal file
1
_examples/view/template_blocks_0/views/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
<h1>Index Body</h1>
|
13
_examples/view/template_blocks_0/views/layouts/error.html
Normal file
13
_examples/view/template_blocks_0/views/layouts/error.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Code}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "content" .}}
|
||||
|
||||
{{block "message" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
13
_examples/view/template_blocks_0/views/layouts/main.html
Normal file
13
_examples/view/template_blocks_0/views/layouts/main.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ if .Title }}{{ .Title }}{{ else }}Default Main Title{{ end }}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "content" . }}
|
||||
|
||||
<footer>{{ partial "partials/footer" .}}</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
<h3>Footer Partial</h3>
|
343
_examples/view/template_blocks_1_embedded/bindata.go
Normal file
343
_examples/view/template_blocks_1_embedded/bindata.go
Normal file
|
@ -0,0 +1,343 @@
|
|||
// Code generated by go-bindata. (@generated) DO NOT EDIT.
|
||||
|
||||
//Package main generated by go-bindata.// sources:
|
||||
// ../template_blocks_0/views/500.html
|
||||
// ../template_blocks_0/views/index.html
|
||||
// ../template_blocks_0/views/layouts/error.html
|
||||
// ../template_blocks_0/views/layouts/main.html
|
||||
// ../template_blocks_0/views/partials/footer.html
|
||||
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 os.FileInfo
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
// Name return file name
|
||||
func (fi bindataFileInfo) Name() string {
|
||||
return fi.name
|
||||
}
|
||||
|
||||
// Size return file size
|
||||
func (fi bindataFileInfo) Size() int64 {
|
||||
return fi.size
|
||||
}
|
||||
|
||||
// Mode return file mode
|
||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||
return fi.mode
|
||||
}
|
||||
|
||||
// ModTime return file modify time
|
||||
func (fi bindataFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
|
||||
// IsDir return file whether a directory
|
||||
func (fi bindataFileInfo) IsDir() bool {
|
||||
return fi.mode&os.ModeDir != 0
|
||||
}
|
||||
|
||||
// Sys return file is sys mode
|
||||
func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _views500Html = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x8f\xb1\x4e\xf3\x30\x14\x85\xf7\x48\x79\x87\xfb\x67\xe9\x8f\xd4\xa4\xea\x5a\x42\x37\x06\x06\xa6\x22\x21\x46\xc7\x3e\xad\x2d\x9c\x7b\x23\xdb\x49\x89\xa2\xbc\x3b\x6a\x5a\x04\x2c\xac\xf7\x7e\xe7\x3b\x3a\xf5\xbf\xb2\xa4\x37\xe9\x49\x2b\x26\x83\xa3\x63\x50\x2b\x01\x94\xac\x62\x12\x06\x35\x5e\xf4\x7b\x95\x67\x2f\x16\x17\x40\xf5\x3e\x2d\x77\x17\xa9\xd0\xc2\x09\x9c\x0a\x3a\x5b\xa7\x2d\x45\x2b\xbd\x37\xd4\x5c\xd2\xa0\x56\x39\xa6\x84\xb6\xf3\x2a\x61\x15\xa9\x11\x33\x56\x79\x76\x90\x35\x61\x00\x93\x3b\x92\x4b\xab\x48\xad\x8b\xd1\xf1\x89\xfe\x47\x80\x1c\x1b\x7c\x54\x36\xb5\xfe\x6e\x7d\x7d\x2b\x63\x60\x48\xf5\x49\x5a\x95\x9c\x56\xde\x8f\xd4\x8c\x4b\xc3\xe0\x70\x26\xf0\xc9\x31\xaa\x3c\x7b\xb5\x60\x1a\xa5\x27\x06\x0c\x25\xf9\x63\xce\x7a\xe1\xac\x1a\x70\xe1\x9a\x1b\x13\x3b\x68\x77\x74\x7a\x97\x67\x65\xb9\xcf\xb3\x69\xfa\x52\x7c\x0f\x9d\xe7\x3c\xab\xed\x76\xff\xc4\x09\x81\x95\xa7\x03\xc2\x80\x40\x8f\x21\x48\xa8\x37\x76\x7b\xcd\x81\xcd\x82\xfe\x92\xb4\x88\x51\x9d\x70\x93\x74\x14\xd3\xe8\xf1\x50\x68\xf1\x12\x76\x01\xe6\xbe\xd8\x4f\x53\xf5\x7c\xa5\xe6\xb9\xde\x74\x3f\x65\x9f\x01\x00\x00\xff\xff\xb3\xfa\x91\xbd\xaa\x01\x00\x00")
|
||||
|
||||
func views500HtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_views500Html,
|
||||
"views/500.html",
|
||||
)
|
||||
}
|
||||
|
||||
func views500Html() (*asset, error) {
|
||||
bytes, err := views500HtmlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "views/500.html", size: 426, mode: os.FileMode(438), modTime: time.Unix(1596515591, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _viewsIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb2\xc9\x30\xb4\xf3\xcc\x4b\x49\xad\x50\x70\xca\x4f\xa9\xb4\xd1\xcf\x30\xb4\x03\x04\x00\x00\xff\xff\xcc\x4b\x98\x69\x13\x00\x00\x00")
|
||||
|
||||
func viewsIndexHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_viewsIndexHtml,
|
||||
"views/index.html",
|
||||
)
|
||||
}
|
||||
|
||||
func viewsIndexHtml() (*asset, error) {
|
||||
bytes, err := viewsIndexHtmlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "views/index.html", size: 19, mode: os.FileMode(438), modTime: time.Unix(1596514225, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _viewsLayoutsErrorHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x90\xbf\x4e\xf3\x40\x10\xc4\x7b\x4b\x7e\x87\xfd\xb6\xfe\x6c\x43\x47\x71\xe7\x26\x40\x0b\x45\x28\x28\x37\x77\xa3\xf8\xc4\xfd\x89\xe2\x55\x22\x74\xf2\xbb\xa3\x18\x83\x44\xb5\xda\x99\x9f\x66\x57\x63\xfe\x3d\xbe\xec\xf6\xef\xaf\x4f\x34\x69\x8a\x63\xdb\x98\xdb\xa4\x28\xf9\x68\x19\x99\x57\x05\xe2\xc7\xb6\x21\x22\x32\x09\x2a\xe4\x26\x39\xcf\x50\xcb\x6f\xfb\xe7\xee\x81\xff\x78\x59\x12\x2c\x5f\x02\xae\xa7\x72\x56\x26\x57\xb2\x22\xab\xe5\x6b\xf0\x3a\x59\x8f\x4b\x70\xe8\xd6\xe5\x3f\x85\x1c\x34\x48\xec\x66\x27\x11\xf6\xbe\xbf\xfb\xcd\xd2\xa0\x11\x63\xad\xfd\xae\x78\x2c\x8b\x19\xbe\x85\xb6\x31\xc3\xf6\x8e\x39\x14\xff\xb9\xe1\xb5\x92\x22\x9d\xa2\x28\x88\xb7\x8b\x4c\xfd\xb2\xb4\xcd\x0f\x70\x88\xc5\x7d\x10\x27\xcc\xb3\x1c\xb1\x9a\xb5\x22\xfb\x1b\x63\x86\x2d\xcb\x0c\x6b\x0b\x5f\x01\x00\x00\xff\xff\xbe\xb7\x11\x67\x15\x01\x00\x00")
|
||||
|
||||
func viewsLayoutsErrorHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_viewsLayoutsErrorHtml,
|
||||
"views/layouts/error.html",
|
||||
)
|
||||
}
|
||||
|
||||
func viewsLayoutsErrorHtml() (*asset, error) {
|
||||
bytes, err := viewsLayoutsErrorHtmlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "views/layouts/error.html", size: 277, mode: os.FileMode(438), modTime: time.Unix(1596514340, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _viewsLayoutsMainHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x90\xb1\x4e\xf4\x30\x10\x84\xfb\x48\x79\x87\xf9\x5d\xff\x49\xa0\xa3\xb0\xd3\x70\xd0\x21\x28\x42\x41\xb9\x24\x1b\x62\xc9\x71\xa2\x64\xb9\x13\xb2\xfc\xee\xc8\x39\x0b\xe9\x2a\xcf\xfa\xb3\x66\xc6\xab\xff\x9d\x5e\x1f\xbb\x8f\xb7\x27\x4c\x32\xbb\xb6\x2c\x74\x3a\xe1\xc8\x7f\x19\xc5\x5e\x1d\x37\x4c\x43\x5b\x16\x00\xa0\x67\x16\x42\x3f\xd1\xb6\xb3\x18\xf5\xde\x3d\x57\x0f\xea\x86\x79\x9a\xd9\xa8\xb3\xe5\xcb\xba\x6c\xa2\xd0\x2f\x5e\xd8\x8b\x51\x17\x3b\xc8\x64\x06\x3e\xdb\x9e\xab\x63\xf8\x0f\xeb\xad\x58\x72\xd5\xde\x93\x63\x73\x5f\xdf\xfd\x79\x89\x15\xc7\x6d\x08\xb0\x23\xea\x2e\x0d\x88\x31\x84\x1b\xcd\x6e\x4f\xea\xc4\x23\x7d\x3b\xc1\x0b\x59\x8f\x03\x27\xe6\x07\xc4\xa8\x9b\xab\x4f\x59\xe8\x26\xff\x42\x7f\x2e\xc3\x4f\x4e\x09\x01\xc2\xf3\xea\x48\x18\x2a\x17\x55\xa8\x11\x63\x59\x94\x85\x1e\x97\x45\x78\x4b\x25\x56\xda\x52\x4f\xa8\x2c\xf6\xe6\xca\x14\xea\x14\x92\x1f\xa6\x94\xec\xae\x9b\x63\x9d\xbf\x01\x00\x00\xff\xff\x44\x95\x63\x98\x5e\x01\x00\x00")
|
||||
|
||||
func viewsLayoutsMainHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_viewsLayoutsMainHtml,
|
||||
"views/layouts/main.html",
|
||||
)
|
||||
}
|
||||
|
||||
func viewsLayoutsMainHtml() (*asset, error) {
|
||||
bytes, err := viewsLayoutsMainHtmlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "views/layouts/main.html", size: 350, mode: os.FileMode(438), modTime: time.Unix(1596514155, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _viewsPartialsFooterHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb2\xc9\x30\xb6\x73\xcb\xcf\x2f\x49\x2d\x52\x08\x48\x2c\x2a\xc9\x4c\xcc\xb1\xd1\xcf\x30\xb6\x03\x04\x00\x00\xff\xff\x08\xe6\xe9\xf8\x17\x00\x00\x00")
|
||||
|
||||
func viewsPartialsFooterHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_viewsPartialsFooterHtml,
|
||||
"views/partials/footer.html",
|
||||
)
|
||||
}
|
||||
|
||||
func viewsPartialsFooterHtml() (*asset, error) {
|
||||
bytes, err := viewsPartialsFooterHtmlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "views/partials/footer.html", size: 23, mode: os.FileMode(438), modTime: time.Unix(1596514093, 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, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
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, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
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/500.html": views500Html,
|
||||
"views/index.html": viewsIndexHtml,
|
||||
"views/layouts/error.html": viewsLayoutsErrorHtml,
|
||||
"views/layouts/main.html": viewsLayoutsMainHtml,
|
||||
"views/partials/footer.html": viewsPartialsFooterHtml,
|
||||
}
|
||||
|
||||
// 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, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
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{nil, map[string]*bintree{
|
||||
"views": {nil, map[string]*bintree{
|
||||
"500.html": {views500Html, map[string]*bintree{}},
|
||||
"index.html": {viewsIndexHtml, map[string]*bintree{}},
|
||||
"layouts": {nil, map[string]*bintree{
|
||||
"error.html": {viewsLayoutsErrorHtml, map[string]*bintree{}},
|
||||
"main.html": {viewsLayoutsMainHtml, map[string]*bintree{}},
|
||||
}},
|
||||
"partials": {nil, map[string]*bintree{
|
||||
"footer.html": {viewsPartialsFooterHtml, 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
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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, "/")...)...)
|
||||
}
|
40
_examples/view/template_blocks_1_embedded/main.go
Normal file
40
_examples/view/template_blocks_1_embedded/main.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import "github.com/kataras/iris/v12"
|
||||
|
||||
// $ go get -u github.com/go-bindata/go-bindata/v3/go-bindata
|
||||
// $ go-bindata -prefix "../template_blocks_0" ../template_blocks_0/views/...
|
||||
// $ go run .
|
||||
// # OR go-bindata -prefix "../template_blocks_0/views" ../template_blocks_0/views/... with iris.Blocks("").Binary(...)
|
||||
// System files are not used, you can optionally delete the folder and run the example now.
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.RegisterView(iris.Blocks("./views", ".html").Binary(Asset, AssetNames))
|
||||
|
||||
app.Get("/", index)
|
||||
app.Get("/500", internalServerError)
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
func index(ctx iris.Context) {
|
||||
data := iris.Map{
|
||||
"Title": "Page Title",
|
||||
}
|
||||
|
||||
ctx.ViewLayout("main")
|
||||
ctx.View("index", data)
|
||||
}
|
||||
|
||||
func internalServerError(ctx iris.Context) {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
|
||||
data := iris.Map{
|
||||
"Code": iris.StatusInternalServerError,
|
||||
"Message": "Internal Server Error",
|
||||
}
|
||||
|
||||
ctx.ViewLayout("error")
|
||||
ctx.View("500", data)
|
||||
}
|
18
aliases.go
18
aliases.go
|
@ -190,25 +190,29 @@ const NoLayout = view.NoLayout
|
|||
|
||||
var (
|
||||
// HTML view engine.
|
||||
// Shortcut of the kataras/iris/view.HTML.
|
||||
// Shortcut of the view.HTML.
|
||||
HTML = view.HTML
|
||||
// Blocks view engine.
|
||||
// Can be used as a faster alternative of the HTML engine.
|
||||
// Shortcut of the view.Blocks.
|
||||
Blocks = view.Blocks
|
||||
// Django view engine.
|
||||
// Shortcut of the kataras/iris/view.Django.
|
||||
// Shortcut of the view.Django.
|
||||
Django = view.Django
|
||||
// Handlebars view engine.
|
||||
// Shortcut of the kataras/iris/view.Handlebars.
|
||||
// Shortcut of the view.Handlebars.
|
||||
Handlebars = view.Handlebars
|
||||
// Pug view engine.
|
||||
// Shortcut of the kataras/iris/view.Pug.
|
||||
// Shortcut of the view.Pug.
|
||||
Pug = view.Pug
|
||||
// Amber view engine.
|
||||
// Shortcut of the kataras/iris/view.Amber.
|
||||
// Shortcut of the view.Amber.
|
||||
Amber = view.Amber
|
||||
// Jet view engine.
|
||||
// Shortcut of the kataras/iris/view.Jet.
|
||||
// Shortcut of the view.Jet.
|
||||
Jet = view.Jet
|
||||
// Ace view engine.
|
||||
// Shortcut of the kataras/iris/view.Ace.
|
||||
// Shortcut of the view.Ace.
|
||||
Ace = view.Ace
|
||||
)
|
||||
|
||||
|
|
|
@ -30,10 +30,10 @@ import (
|
|||
|
||||
"github.com/Shopify/goreferrer"
|
||||
"github.com/fatih/structs"
|
||||
"github.com/iris-contrib/blackfriday"
|
||||
"github.com/iris-contrib/schema"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
"golang.org/x/time/rate"
|
||||
|
|
3
go.mod
3
go.mod
|
@ -14,12 +14,12 @@ require (
|
|||
github.com/gomodule/redigo v1.8.2
|
||||
github.com/google/uuid v1.1.2-0.20200519141726-cb32006e483f
|
||||
github.com/hashicorp/go-version v1.2.1
|
||||
github.com/iris-contrib/blackfriday v2.0.0+incompatible
|
||||
github.com/iris-contrib/httpexpect/v2 v2.0.5
|
||||
github.com/iris-contrib/jade v1.1.4
|
||||
github.com/iris-contrib/pongo2 v0.0.1
|
||||
github.com/iris-contrib/schema v0.0.2
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/kataras/blocks v0.0.1
|
||||
github.com/kataras/golog v0.0.18
|
||||
github.com/kataras/neffos v0.0.16
|
||||
github.com/kataras/pio v0.0.8
|
||||
|
@ -28,6 +28,7 @@ require (
|
|||
github.com/klauspost/compress v1.10.10
|
||||
github.com/mediocregopher/radix/v3 v3.5.2
|
||||
github.com/microcosm-cc/bluemonday v1.0.3
|
||||
github.com/russross/blackfriday/v2 v2.0.1
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
# View
|
||||
|
||||
Iris supports 7 template engines out-of-the-box, developers can still use any external golang template engine,
|
||||
Iris supports 8 template engines out-of-the-box, developers can still use any external golang template engine,
|
||||
as `Context.ResponseWriter()` is an `io.Writer`.
|
||||
|
||||
All of these six template engines have common features with common API,
|
||||
like Layout, Template Funcs, Party-specific layout, partial rendering and more.
|
||||
All template engines share a common API i.e.
|
||||
Parse using embedded assets, Layouts and Party-specific layout, Template Funcs, Partial Render and more.
|
||||
|
||||
- The standard html, its template parser is the [golang.org/pkg/html/template/](https://golang.org/pkg/html/template/)
|
||||
- Django, its template parser is the [github.com/iris-contrib/pongo2](https://github.com/iris-contrib/pongo2)
|
||||
- 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)
|
||||
- Ace, its template parser is the [github.com/yosssi/ace](https://github.com/yosssi/ace)
|
||||
| # | Name | Parser |
|
||||
|:---|:-----------|----------|
|
||||
| 1 | HTML | [html/template](https://pkg.go.dev/html/template) |
|
||||
| 2 | Blocks | [kataras/blocks](https://github.com/kataras/blocks) |
|
||||
| 3 | Django | [flosch/pongo2](https://github.com/flosch/pongo2) |
|
||||
| 4 | Pug | [Joker/jade](https://github.com/Joker/jade) |
|
||||
| 5 | Handlebars | [aymerick/raymond](https://github.com/aymerick/raymond) |
|
||||
| 6 | Amber | [eknkc/amber](https://github.com/eknkc/amber) |
|
||||
| 7 | Jet | [CloudyKit/jet](https://github.com/CloudyKit/jet) |
|
||||
| 8 | Ace | [yosssi/ace](https://github.com/yosssi/ace) |
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -24,6 +27,8 @@ like Layout, Template Funcs, Party-specific layout, partial rendering and more.
|
|||
- [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)
|
||||
- [Blocks](https://github.com/kataras/iris/blob/master/_examples/view/template_blocks_0)
|
||||
- [Blocks Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_blocks_1_embedded)
|
||||
- [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)
|
||||
|
@ -32,7 +37,7 @@ like Layout, Template Funcs, Party-specific layout, partial rendering and more.
|
|||
- [Jet Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_1_embedded)
|
||||
- [Ace](https://github.com/kataras/iris/blob/master/_examples/view/template_ace_0)
|
||||
|
||||
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `context#ResponseWriter`, take a look at the [iris/_examples/view/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/view/quicktemplate) example.
|
||||
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `Context.ResponseWriter`, take a look at the [iris/_examples/view/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/view/quicktemplate) example.
|
||||
|
||||
## Overview
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
// Ace returns a new ace view engine.
|
||||
// It shares the same exactly logic with the
|
||||
// html view engine, it uses the same exactly configuration.
|
||||
// The given "extension" MUST begin with a dot.
|
||||
//
|
||||
// Read more about the Ace Go Parser: https://github.com/yosssi/ace
|
||||
func Ace(directory, extension string) *HTMLEngine {
|
||||
|
|
|
@ -29,6 +29,7 @@ type AmberEngine struct {
|
|||
var _ Engine = (*AmberEngine)(nil)
|
||||
|
||||
// Amber creates and returns a new amber view engine.
|
||||
// The given "extension" MUST begin with a dot.
|
||||
func Amber(directory, extension string) *AmberEngine {
|
||||
s := &AmberEngine{
|
||||
directory: directory,
|
||||
|
|
109
view/blocks.go
Normal file
109
view/blocks.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
|
||||
"github.com/kataras/blocks"
|
||||
)
|
||||
|
||||
// BlocksEngine is an Iris view engine adapter for the blocks view engine.
|
||||
// The blocks engine is based on the html/template standard Go package.
|
||||
//
|
||||
// To initialize a fresh one use the `Blocks` function.
|
||||
// To wrap an existing one use the `WrapBlocks` function.
|
||||
//
|
||||
// It contains the following four default template functions:
|
||||
// - url "routename" parameters...
|
||||
// - urlpath "routename" parameters...
|
||||
// - tr "language" "key" arguments...
|
||||
// - partial "template_name" data
|
||||
//
|
||||
// Read more at: https://github.com/kataras/blocks.
|
||||
type BlocksEngine struct {
|
||||
Engine *blocks.Blocks
|
||||
}
|
||||
|
||||
var _ Engine = (*BlocksEngine)(nil)
|
||||
|
||||
// WrapBlocks wraps an initialized blocks engine and returns its Iris adapter.
|
||||
// See `Blocks` package-level function too.
|
||||
func WrapBlocks(v *blocks.Blocks) *BlocksEngine {
|
||||
return &BlocksEngine{Engine: v}
|
||||
}
|
||||
|
||||
// Blocks returns a new blocks view engine.
|
||||
// The given "extension" MUST begin with a dot.
|
||||
//
|
||||
// See `WrapBlocks` package-level function too.
|
||||
func Blocks(directory, extension string) *BlocksEngine {
|
||||
return WrapBlocks(blocks.New(directory).Extension(extension))
|
||||
}
|
||||
|
||||
// Ext returns empty ext as this template engine
|
||||
// supports template blocks without file suffix.
|
||||
// Note that, if more than one view engine is registered to a single
|
||||
// Iris application then, this Blocks engine should be the last entry one.
|
||||
func (s *BlocksEngine) Ext() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// AddFunc implements the `EngineFuncer` which is being used
|
||||
// by the framework to add template functions like:
|
||||
// - url func(routeName string, args ...string) string
|
||||
// - urlpath func(routeName string, args ...string) string
|
||||
// - tr func(lang, key string, args ...interface{}) string
|
||||
func (s *BlocksEngine) AddFunc(funcName string, funcBody interface{}) *BlocksEngine {
|
||||
s.Engine.Funcs(template.FuncMap{funcName: funcBody})
|
||||
return s
|
||||
}
|
||||
|
||||
// AddLayoutFunc adds a template function for templates that are marked as layouts.
|
||||
func (s *BlocksEngine) AddLayoutFunc(funcName string, funcBody interface{}) *BlocksEngine {
|
||||
s.Engine.LayoutFuncs(template.FuncMap{funcName: funcBody})
|
||||
return s
|
||||
}
|
||||
|
||||
// Binary sets the function which reads contents based on a filename
|
||||
// and a function that returns all the filenames.
|
||||
// These functions are used to parse the corresponding files into templates.
|
||||
// You do not need to set them when the given "rootDir" was a system directory.
|
||||
// It's mostly useful when the application contains embedded template files,
|
||||
// e.g. pass go-bindata's `Asset` and `AssetNames` functions
|
||||
// to load templates from go-bindata generated content.
|
||||
func (s *BlocksEngine) Binary(asset blocks.AssetFunc, assetNames blocks.AssetNamesFunc) *BlocksEngine {
|
||||
s.Engine.Assets(asset, assetNames)
|
||||
return s
|
||||
}
|
||||
|
||||
// Layout sets the default layout which inside should use
|
||||
// the {{ template "content" . }} to render the main template.
|
||||
//
|
||||
// Example for ./views/layouts/main.html:
|
||||
// Blocks("./views", ".html").Layout("layouts/main")
|
||||
func (s *BlocksEngine) Layout(layoutName string) *BlocksEngine {
|
||||
s.Engine.DefaultLayout(layoutName)
|
||||
return s
|
||||
}
|
||||
|
||||
// Reload if called with a true parameter,
|
||||
// each `ExecuteWriter` call will re-parse the templates.
|
||||
// Useful when the application is at a development stage.
|
||||
func (s *BlocksEngine) Reload(b bool) *BlocksEngine {
|
||||
s.Engine.Reload(b)
|
||||
return s
|
||||
}
|
||||
|
||||
// Load parses the files into templates.
|
||||
func (s *BlocksEngine) Load() error {
|
||||
return s.Engine.Load()
|
||||
}
|
||||
|
||||
// ExecuteWriter renders a template on "w".
|
||||
func (s *BlocksEngine) ExecuteWriter(w io.Writer, tmplName, layoutName string, data interface{}) error {
|
||||
if layoutName == NoLayout {
|
||||
layoutName = ""
|
||||
}
|
||||
|
||||
return s.Engine.ExecuteTemplate(w, tmplName, layoutName, data)
|
||||
}
|
|
@ -108,7 +108,8 @@ type DjangoEngine struct {
|
|||
|
||||
var _ Engine = (*DjangoEngine)(nil)
|
||||
|
||||
// Django creates and returns a new amber view engine.
|
||||
// Django creates and returns a new django view engine.
|
||||
// The given "extension" MUST begin with a dot.
|
||||
func Django(directory, extension string) *DjangoEngine {
|
||||
s := &DjangoEngine{
|
||||
directory: directory,
|
||||
|
|
|
@ -30,6 +30,7 @@ type HandlebarsEngine struct {
|
|||
var _ Engine = (*HandlebarsEngine)(nil)
|
||||
|
||||
// Handlebars creates and returns a new handlebars view engine.
|
||||
// The given "extension" MUST begin with a dot.
|
||||
func Handlebars(directory, extension string) *HandlebarsEngine {
|
||||
s := &HandlebarsEngine{
|
||||
directory: directory,
|
||||
|
|
16
view/html.go
16
view/html.go
|
@ -61,6 +61,7 @@ var emptyFuncs = template.FuncMap{
|
|||
// HTML creates and returns a new html view engine.
|
||||
// The html engine used like the "html/template" standard go package
|
||||
// but with a lot of extra features.
|
||||
// The given "extension" MUST begin with a dot.
|
||||
func HTML(directory, extension string) *HTMLEngine {
|
||||
s := &HTMLEngine{
|
||||
directory: directory,
|
||||
|
@ -173,6 +174,7 @@ func (s *HTMLEngine) AddLayoutFunc(funcName string, funcBody interface{}) *HTMLE
|
|||
// - url func(routeName string, args ...string) string
|
||||
// - urlpath func(routeName string, args ...string) string
|
||||
// - render func(fullPartialName string) (template.HTML, error).
|
||||
// - tr func(lang, key string, args ...interface{}) string
|
||||
func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) *HTMLEngine {
|
||||
s.rmu.Lock()
|
||||
s.funcs[funcName] = funcBody
|
||||
|
@ -206,7 +208,7 @@ func (s *HTMLEngine) Funcs(funcMap template.FuncMap) *HTMLEngine {
|
|||
// Load parses the templates to the engine.
|
||||
// It's also responsible to add the necessary global functions.
|
||||
//
|
||||
// Returns an error if something bad happens, user is responsible to catch it.
|
||||
// Returns an error if something bad happens, caller is responsible to handle that.
|
||||
func (s *HTMLEngine) Load() error {
|
||||
// No need to make this with a complicated and "pro" way, just add lockers to the `ExecuteWriter`.
|
||||
// if `Reload(true)` and add a note for non conc access on dev mode.
|
||||
|
@ -500,6 +502,12 @@ func (s *HTMLEngine) ExecuteWriter(w io.Writer, name string, layout string, bind
|
|||
}
|
||||
}
|
||||
|
||||
t := s.Templates.Lookup(name)
|
||||
if t == nil {
|
||||
return fmt.Errorf("template: %s does not exist in the dir: %s", name, s.directory)
|
||||
}
|
||||
s.runtimeFuncsFor(t, name, bindingData)
|
||||
|
||||
if layout = getLayout(layout, s.layout); layout != "" {
|
||||
lt := s.Templates.Lookup(layout)
|
||||
if lt == nil {
|
||||
|
@ -510,11 +518,5 @@ func (s *HTMLEngine) ExecuteWriter(w io.Writer, name string, layout string, bind
|
|||
return lt.Execute(w, bindingData)
|
||||
}
|
||||
|
||||
t := s.Templates.Lookup(name)
|
||||
if t == nil {
|
||||
return fmt.Errorf("template: %s does not exist in the dir: %s", name, s.directory)
|
||||
}
|
||||
s.runtimeFuncsFor(t, name, bindingData)
|
||||
|
||||
return t.Execute(w, bindingData)
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ var jetExtensions = [...]string{
|
|||
}
|
||||
|
||||
// Jet creates and returns a new jet view engine.
|
||||
// The given "extension" MUST begin with a dot.
|
||||
func Jet(directory, extension string) *JetEngine {
|
||||
// if _, err := os.Stat(directory); os.IsNotExist(err) {
|
||||
// panic(err)
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
// Pug (or Jade) returns a new pug view engine.
|
||||
// It shares the same exactly logic with the
|
||||
// html view engine, it uses the same exactly configuration.
|
||||
// The given "extension" MUST begin with a dot.
|
||||
//
|
||||
// Read more about the Jade Go Parser: https://github.com/Joker/jade
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue
Block a user