update view benchmarks and remove ace template parser

This commit is contained in:
Gerasimos (Makis) Maropoulos 2023-03-17 12:38:07 +02:00
parent bd1a1486f4
commit ec2d9d016e
No known key found for this signature in database
GPG Key ID: B9839E9CD30B7B6B
33 changed files with 45 additions and 885 deletions

View File

@ -19,6 +19,12 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
**How to upgrade**: Open your command-line and execute this command: `go get github.com/kataras/iris/v12@latest` and `go mod tidy -compat=1.20`.
# Next
Change applies to `master` branch.
- Remove [ace](https://github.com/eknkc/amber) template parser support, as it was discontinued by its author more than five years ago.
# Sa, 11 March 2023 | v12.2.0
This release introduces new features and some breaking changes.
@ -41,7 +47,7 @@ All new features have been tested in production and seem to work fine. Fixed all
- Add `Context.Render` method for compatibility.
- Support of embedded [locale files](https://github.com/kataras/iris/blob/master/_examples/i18n/template-embedded/main.go) using standard `embed.FS` with the new `LoadFS` function.
- Support of direct embedded view engines (`HTML, Blocks, Django, Handlebars, Pug, Amber, Jet` and `Ace`) with `embed.FS` or `fs.FS` (in addition to `string` and `http.FileSystem` types).
- Support of direct embedded view engines (`HTML, Blocks, Django, Handlebars, Pug, Jet` and `Ace`) with `embed.FS` or `fs.FS` (in addition to `string` and `http.FileSystem` types).
- Add support for `embed.FS` and `fs.FS` on `app.HandleDir`.
- Add `iris.Patches()` package-level function to customize Iris Request Context REST (and more to come) behavior.
@ -306,7 +312,7 @@ func main() {
- Fix Response Recorder `Clone` concurrent access afterwards.
- Add a `ParseTemplate` method on view engines to manually parse and add a template from a text as [requested](https://github.com/kataras/iris/issues/1617). [Examples](https://github.com/kataras/iris/tree/master/_examples/view/parse-template).
- Full `http.FileSystem` interface support for all **view** engines as [requested](https://github.com/kataras/iris/issues/1575). The first argument of the functions(`HTML`, `Blocks`, `Pug`, `Amber`, `Ace`, `Jet`, `Django`, `Handlebars`) can now be either a directory of `string` type (like before) or a value which completes the `http.FileSystem` interface. The `.Binary` method of all view engines was removed: pass the go-bindata's latest version `AssetFile()` exported function as the first argument instead of string.
- Full `http.FileSystem` interface support for all **view** engines as [requested](https://github.com/kataras/iris/issues/1575). The first argument of the functions(`HTML`, `Blocks`, `Pug`, `Ace`, `Jet`, `Django`, `Handlebars`) can now be either a directory of `string` type (like before) or a value which completes the `http.FileSystem` interface. The `.Binary` method of all view engines was removed: pass the go-bindata's latest version `AssetFile()` exported function as the first argument instead of string.
- Add `Route.ExcludeSitemap() *Route` to exclude a route from sitemap as requested in [chat](https://chat.iris-go.com), also offline routes are excluded automatically now.

View File

@ -187,7 +187,7 @@ Some of the features Iris offers:
* Builtin support for ngrok to put your app on the internet, the fastest way
* Unique Router with dynamic path as parameter with standard types like :uuid, :string, :int... and the ability to create your own
* Compression
* View Engines (HTML, Django, Amber, Handlebars, Pug/Jade and more)
* View Engines (HTML, Django, Handlebars, Pug/Jade and more)
* Create your own File Server and host your own WebDAV server
* Cache
* Localization (i18n, sitemap)

View File

@ -184,7 +184,7 @@ Alguns dos recursos que o Iris Web Framework oferece:
* Suporte integrado para ngrok para colocar seu aplicativo na internet da maneira mais rápida
* Router único com caminho dinâmico como parametro com tipos padrões como :uuid, :string, :int... e a habilidade de criar o seu próprio router
* Compressão
* View Engines (HTML, Django, Amber, Handlebars, Pug/Jade e mais)
* View Engines (HTML, Django, Handlebars, Pug/Jade e mais)
* Cria seu próprio Servidor de Arquivo e hospeda seu próprio servidor WebDAV
* Cache
* Localização (i18n, sitemap)

View File

@ -195,7 +195,7 @@ Iris 提供了至少這些功能:
- 內建 ngrok 支援,讓您可以把 app 以最快速的方式推上網際網路
- 包含動態路徑、具唯一性的路由,支援如 :uuid、:string、:int 等等的標準類型,並且可以自己建立
- 壓縮功能
- 檢視 (View) 算繪引擎 (HTML、Django、Amber、Handlebars、Pug/Jade 等等)
- 檢視 (View) 算繪引擎 (HTML、Django、Handlebars、Pug/Jade 等等)
- 建立自己的檔案伺服器,並寄存您自己的 WebDAV 伺服器
- 快取
- 本地化 (i18n、sitemap

View File

@ -1,26 +1,27 @@
# View Engine Benchmarks
Benchmark between all 8 supported template parsers.
Benchmark between all 7 supported template parsers.
Amber, Ace and Pug parsers minifies the template before render. So, to have a fair benchmark, we must make sure that the byte amount of the total response body is exactly the same across all. Therefore, all other template files are minified too.
Ace and Pug parsers minifies the template before render. So, to have a fair benchmark, we must make sure that the byte amount of the total response body is exactly the same across all. Therefore, all other template files are minified too.
![Benchmarks Chart Graph](chart.png)
> Last updated: Oct 1, 2020 at 12:46pm (UTC)
> Last updated: Mar 17, 2023 at 10:31am (UTC)
## System
| | |
|----|:---|
| Processor | Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz |
| RAM | 15.85 GB |
| OS | Microsoft Windows 10 Pro |
| Processor | 12th Gen Intel(R) Core(TM) i7-12700H |
| RAM | 15.68 GB |
| OS | Microsoft Windows 11 Pro |
| [Bombardier](https://github.com/codesenberg/bombardier) | v1.2.4 |
| [Go](https://golang.org) | go1.15.2 |
| [Go](https://golang.org) | go1.20.2 |
| [Node.js](https://nodejs.org/) | v19.5.0 |
## Terminology
**Name** is the name of the template engine used under a particular test.
**Name** is the name of the framework(or router) used under a particular test.
**Reqs/sec** is the avg number of total requests could be processed per second (the higher the better).
@ -38,19 +39,18 @@ Amber, Ace and Pug parsers minifies the template before render. So, to have a fa
| Name | Language | Reqs/sec | Latency | Throughput | Time To Complete |
|------|:---------|:---------|:--------|:-----------|:-----------------|
| [Amber](./amber) | Go |125698 |0.99ms |44.67MB |7.96s |
| [Blocks](./blocks) | Go |123974 |1.01ms |43.99MB |8.07s |
| [Django](./django) | Go |118831 |1.05ms |42.17MB |8.41s |
| [Handlebars](./handlebars) | Go |101214 |1.23ms |35.91MB |9.88s |
| [Pug](./pug) | Go |89002 |1.40ms |31.81MB |11.24s |
| [Ace](./ace) | Go |64782 |1.93ms |22.98MB |15.44s |
| [HTML](./html) | Go |53918 |2.32ms |19.13MB |18.55s |
| [Jet](./jet) | Go |4829 |25.88ms |1.71MB |207.07s |
| [Jet](./jet) | Go |248957 |500.81us |88.26MB |4.02s |
| [Blocks](./blocks) | Go |238854 |521.76us |84.74MB |4.19s |
| [Pug](./pug) | Go |238153 |523.74us |85.07MB |4.20s |
| [Django](./django) | Go |224448 |555.40us |79.61MB |4.46s |
| [Handlebars](./handlebars) | Go |197267 |631.99us |69.96MB |5.07s |
| [Ace](./ace) | Go |157415 |792.53us |55.83MB |6.35s |
| [HTML](./html) | Go |120811 |1.03ms |42.82MB |8.29s |
## How to Run
```sh
$ go install github.com/kataras/server-benchmarks@latest
$ go install github.com/codesenberg/bombardier@latest
$ go install github.com/kataras/server-benchmarks@master
$ go install github.com/codesenberg/bombardier@master
$ server-benchmarks --wait-run=3s -o ./results
```

View File

@ -3,6 +3,6 @@ html
head
title {{.Title}}
body
{{ yield . . }}
{{ yield . }}
footer
= include partials/footer.ace .

View File

@ -1,29 +0,0 @@
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
// By default Amber engine minifies the template before render.
app.RegisterView(iris.Amber("./views", ".amber"))
app.Get("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
data := iris.Map{
"Title": "Page Title",
"FooterText": "Footer contents",
"Message": "Main contents",
}
// On Amber this is ignored: ctx.ViewLayout("layouts/main")
// Layouts are only rendered from inside the index page itself
// using the "extends" keyword.
if err := ctx.View("index", data); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
}

View File

@ -1,5 +0,0 @@
extends layouts/main.amber
block content
h1 Index Body
h3 Message: #{Message}

View File

@ -1,8 +0,0 @@
doctype html
html
head
title #{Title}
body
block content
footer
#{render("partials/footer.amber", $)}

View File

@ -1,2 +0,0 @@
h3 Footer Partial
h4 #{FooterText}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -11,8 +11,6 @@
Envs:
- Name: Ace
Dir: ./ace
- Name: Amber
Dir: ./amber
- Name: Blocks
Dir: ./blocks
- Name: Django

View File

@ -138,7 +138,6 @@
* [Overview](view/overview/main.go)
* [Layout](view/layout)
* [Ace](view/layout/ace)
* [Amber](view/layout/amber)
* [Blocks](view/layout/blocks)
* [Django](view/layout/django)
* [Handlebars](view/layout/handlebars)
@ -160,7 +159,6 @@
* Parse a Template from Text
* [HTML, Pug and Ace](view/parse-parse/main.go)
* [Django](view/parse-parse/django/main.go)
* [Amber](view/parse-parse/amber/main.go)
* [Jet](view/parse-parse/jet/main.go)
* [Handlebars](view/parse-parse/handlebars/main.go)
* [Blocks](view/template_blocks_0)
@ -170,8 +168,6 @@
* [Pug Embedded`](view/template_pug_2_embedded)
* [Ace](view/template_ace_0)
* [Django](view/template_django_0)
* [Amber](view/template_amber_0)
* [Amber Embedded](view/template_amber_1_embedded)
* [Jet](view/template_jet_0)
* [Jet Embedded](view/template_jet_1_embedded)
* [Jet 'urlpath' tmpl func](view/template_jet_2)

View File

@ -136,7 +136,6 @@
- [概覽](view/overview/main.go)
- [排版引擎](view/layout)
- [Ace](view/layout/ace)
- [Amber](view/layout/amber)
- [Blocks](view/layout/blocks)
- [Django](view/layout/django)
- [Handlebars](view/layout/handlebars)
@ -158,7 +157,6 @@
- 從文字解析樣板
- [HTML, Pug and Ace](view/parse-parse/main.go)
- [Django](view/parse-parse/django/main.go)
- [Amber](view/parse-parse/amber/main.go)
- [Jet](view/parse-parse/jet/main.go)
- [Handlebars](view/parse-parse/handlebars/main.go)
- [Blocks](view/template_blocks_0)
@ -168,8 +166,6 @@
- [Pug Embedded`](view/template_pug_2_embedded)
- [Ace](view/template_ace_0)
- [Django](view/template_django_0)
- [Amber](view/template_amber_0)
- [Amber Embedded](view/template_amber_1_embedded)
- [Jet](view/template_jet_0)
- [Jet Embedded](view/template_jet_1_embedded)
- [Jet 'urlpath' tmpl func](view/template_jet_2)

View File

@ -5,6 +5,6 @@
</head>
<body>
<!-- Render the current template here -->
{{ yield . . }}
{{ yield . }}
</body>
</html>

View File

@ -1,28 +0,0 @@
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
app.RegisterView(iris.Amber("./views", ".amber"))
app.Get("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
data := iris.Map{
"Title": "Page Title",
"FooterText": "Footer contents",
"Message": "Main contents",
}
// On Amber this is ignored: ctx.ViewLayout("layouts/main")
// Layouts are only rendered from inside the index page itself
// using the "extends" keyword.
if err := ctx.View("index", data); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
}

View File

@ -1,5 +0,0 @@
extends layouts/main.amber
block content
h1 Index Body
h3 Message: #{Message}

View File

@ -1,8 +0,0 @@
doctype html
html
head
title #{Title}
body
block content
footer
#{render("partials/footer.amber", $)}

View File

@ -1,2 +0,0 @@
h3 Footer Partial
h4 #{FooterText}

View File

@ -1,32 +0,0 @@
package main
import "github.com/kataras/iris/v12"
func main() {
e := iris.Amber(nil, ".amber") // You can still use a file system though.
e.AddFunc("greet", func(name string) string {
return "Hello, " + name + "!"
})
err := e.ParseTemplate("program.amber", []byte(`h1 #{ greet(Name) }`))
if err != nil {
panic(err)
}
e.Reload(true)
app := iris.New()
app.RegisterView(e)
app.Get("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
if err := ctx.View("program.amber", iris.Map{
"Name": "Gerasimos",
// Or per template:
// "greet": func(....)
}); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
}

View File

@ -2,4 +2,6 @@
h2 {{.Title}}
h3 Body
= include partials/footer.ace .

View File

@ -1,23 +0,0 @@
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
// Read about its markup syntax at: https://github.com/eknkc/amber
tmpl := iris.Amber("./views", ".amber")
app.RegisterView(tmpl)
app.Get("/", func(ctx iris.Context) {
if err := ctx.View("index.amber", iris.Map{
"Title": "Title of The Page",
}); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
})
app.Listen(":8080")
}

View File

@ -1,11 +0,0 @@
extends layouts/main.amber
block append meta
meta[name="keywords"][content="These are added by the child template"]
block menu
li Item 1
li Item 2
block content
p Content from child template

View File

@ -1,15 +0,0 @@
!!! transitional
html
head
title #{Title}
block meta
meta[name="description"][value="This is a sample"]
body
header#mainHeader
ul
block menu
div#content
block content

View File

@ -1,358 +0,0 @@
// Code generated by go-bindata. (@generated) DO NOT EDIT.
// Package main generated by go-bindata.// sources:
// ../template_amber_0/views/index.amber
// ../template_amber_0/views/layouts/main.amber
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"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
}
type assetFile struct {
*bytes.Reader
name string
childInfos []os.FileInfo
childInfoOffset int
}
type assetOperator struct{}
// Open implement http.FileSystem interface
func (f *assetOperator) Open(name string) (http.File, error) {
var err error
if len(name) > 0 && name[0] == '/' {
name = name[1:]
}
content, err := Asset(name)
if err == nil {
return &assetFile{name: name, Reader: bytes.NewReader(content)}, nil
}
children, err := AssetDir(name)
if err == nil {
childInfos := make([]os.FileInfo, 0, len(children))
for _, child := range children {
childPath := filepath.Join(name, child)
info, errInfo := AssetInfo(filepath.Join(name, child))
if errInfo == nil {
childInfos = append(childInfos, info)
} else {
childInfos = append(childInfos, newDirFileInfo(childPath))
}
}
return &assetFile{name: name, childInfos: childInfos}, nil
} else {
// If the error is not found, return an error that will
// result in a 404 error. Otherwise the server returns
// a 500 error for files not found.
if strings.Contains(err.Error(), "not found") {
return nil, os.ErrNotExist
}
return nil, err
}
}
// Close no need do anything
func (f *assetFile) Close() error {
return nil
}
// Readdir read dir's children file info
func (f *assetFile) Readdir(count int) ([]os.FileInfo, error) {
if len(f.childInfos) == 0 {
return nil, os.ErrNotExist
}
if count <= 0 {
return f.childInfos, nil
}
if f.childInfoOffset+count > len(f.childInfos) {
count = len(f.childInfos) - f.childInfoOffset
}
offset := f.childInfoOffset
f.childInfoOffset += count
return f.childInfos[offset : offset+count], nil
}
// Stat read file info from asset item
func (f *assetFile) Stat() (os.FileInfo, error) {
if len(f.childInfos) != 0 {
return newDirFileInfo(f.name), nil
}
return AssetInfo(f.name)
}
// newDirFileInfo return default dir file info
func newDirFileInfo(name string) os.FileInfo {
return &bindataFileInfo{
name: name,
size: 0,
mode: os.FileMode(2147484068), // equal os.FileMode(0644)|os.ModeDir
modTime: time.Time{}}
}
// AssetFile return a http.FileSystem instance that data backend by asset
func AssetFile() http.FileSystem {
return &assetOperator{}
}
var _indexAmber = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x8d\x31\x8a\xc3\x30\x14\x44\x7b\x83\xef\x30\xe8\x00\xbb\xec\xf6\xae\xb6\xda\x3e\x9d\x71\xf1\x6d\x4d\xb0\xb1\xbe\x24\xa4\x6f\x12\xdf\x3e\x10\x07\x4c\xd2\xcd\x63\x86\x79\xbc\x1b\xa3\xaf\x08\xb2\xa7\xcd\xea\xb7\xca\x12\xbf\x44\x47\x96\xb6\x69\x9b\x31\xa4\x69\x85\xe4\xcc\xe8\xa1\x34\x69\x1b\x00\xcf\xd4\x47\x51\x76\x6e\xe5\x7e\x4b\xc5\x57\x37\xf4\x53\x8a\xc6\x68\x9d\xbb\xcc\xac\x84\x14\x42\xbc\xa7\xc7\xb8\xc3\x66\x62\x9a\x97\xe0\x61\xd4\x1c\xc4\xe8\x86\x53\xa0\x8c\xdb\xf1\x1c\x16\xfc\x1b\x15\x3f\xef\xf8\x7b\x6e\x5f\x96\xa3\xcf\xf8\x3b\x10\xd7\x92\xf4\xc3\xf0\x08\x00\x00\xff\xff\xaa\xd2\x87\xd5\xdb\x00\x00\x00")
func indexAmberBytes() ([]byte, error) {
return bindataRead(
_indexAmber,
"index.amber",
)
}
func indexAmber() (*asset, error) {
bytes, err := indexAmberBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "index.amber", size: 219, mode: os.FileMode(438), modTime: time.Unix(1599264617, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _layoutsMainAmber = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x4e\xc1\xae\x83\x30\x0c\xbb\x23\xf1\x0f\xa1\x7c\x07\xf7\xf7\x01\xdc\x10\x87\x40\x23\x11\xbd\x34\x45\x34\x20\x4d\xd3\xfe\x7d\xea\xd8\x04\xf5\xa5\xae\xdd\xda\x6e\x9a\x06\x6c\x43\x4d\x6c\x1c\x15\xa5\xae\x16\x0b\x52\x57\x00\x00\x0b\xa1\x3f\x59\x86\xb1\x09\x41\xfb\xec\xf3\xf9\xaa\xab\xcb\x99\x24\xce\xff\x10\xc8\xf0\xd2\x32\xb2\x32\x28\x06\xea\x9c\xa7\x34\x6f\xbc\xe6\x0e\x37\x0e\x07\xca\x4e\x9d\xeb\x17\x4e\xc0\x09\x10\x12\x86\x55\xc8\x8d\xbf\xd4\x29\xfa\xc7\x95\x95\x77\xd0\xd6\x06\x64\xfd\xfb\xd0\xb2\x66\x97\xf2\x7e\x9f\xa4\xfb\x7d\xa8\xe7\xa3\x9d\xa3\x1a\xa9\x95\x5f\xce\xe7\x5f\xeb\x1d\x00\x00\xff\xff\x56\x99\x59\x53\x13\x01\x00\x00")
func layoutsMainAmberBytes() ([]byte, error) {
return bindataRead(
_layoutsMainAmber,
"layouts/main.amber",
)
}
func layoutsMainAmber() (*asset, error) {
bytes, err := layoutsMainAmberBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "layouts/main.amber", size: 275, mode: os.FileMode(438), modTime: time.Unix(1599263735, 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) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; 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) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; 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){
"index.amber": indexAmber,
"layouts/main.amber": layoutsMainAmber,
}
// 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("nonexistent") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
canonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(canonicalName, "/")
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{
"index.amber": {indexAmber, map[string]*bintree{}},
"layouts": {nil, map[string]*bintree{
"main.amber": {layoutsMainAmber, 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 = os.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 {
canonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
}

View File

@ -1,31 +0,0 @@
package main
import "github.com/kataras/iris/v12"
// $ go install github.com/go-bindata/go-bindata/v3/go-bindata@latest
//
// $ go-bindata -fs -prefix "../template_amber_0/views" ../template_amber_0/views/...
// $ go run .
// # OR: go-bindata -fs -prefix "views" ./views/... if the views dir is rel to the executable.
//
// System files are not used, you can optionally delete the folder and run the example now.
func main() {
app := iris.New()
// Read about its markup syntax at: https://github.com/eknkc/amber
tmpl := iris.Amber(AssetFile(), ".amber")
app.RegisterView(tmpl)
app.Get("/", func(ctx iris.Context) {
if err := ctx.View("index.amber", iris.Map{
"Title": "Title of The Page",
}); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
})
app.Listen(":8080")
}

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ isset(title) ? title : "" }}</title>
<title>{{ isset(title) ? title : "Default" }}</title>
</head>
<body>
{{block documentBody()}}{{end}}

View File

@ -46,7 +46,7 @@ type (
// Developers get request information from the client's request by a Context.
Context = *context.Context
// ViewEngine is an alias of `context.ViewEngine`.
// See HTML, Blocks, Django, Jet, Pug, Ace, Handlebars, Amber and e.t.c.
// See HTML, Blocks, Django, Jet, Pug, Ace, Handlebars and e.t.c.
ViewEngine = context.ViewEngine
// UnmarshalerFunc a shortcut, an alias for the `context#UnmarshalerFunc` type
// which implements the `context#Unmarshaler` interface for reading request's body
@ -267,9 +267,6 @@ var (
// Pug view engine.
// Shortcut of the view.Pug.
Pug = view.Pug
// Amber view engine.
// Shortcut of the view.Amber.
Amber = view.Amber
// Jet view engine.
// Shortcut of the view.Jet.
Jet = view.Jet

1
go.mod
View File

@ -12,7 +12,6 @@ require (
github.com/andybalholm/brotli v1.0.5
github.com/blang/semver/v4 v4.0.0
github.com/dgraph-io/badger/v2 v2.2007.4
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385
github.com/fatih/structs v1.1.0
github.com/flosch/pongo2/v4 v4.0.2
github.com/golang/snappy v0.0.4

2
go.sum generated
View File

@ -49,8 +49,6 @@ github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=

View File

@ -1,6 +1,6 @@
# View
Iris supports 8 template engines out-of-the-box, developers can still use any external golang template engine,
Iris supports 7 template engines out-of-the-box, developers can still use any external golang template engine,
as `Context.ResponseWriter()` is an `io.Writer`.
All template engines share a common API i.e.
@ -13,9 +13,8 @@ Parse using embedded assets, Layouts and Party-specific layout, Template Funcs,
| 3 | Django | [flosch/pongo2](https://github.com/flosch/pongo2) |
| 4 | Pug | [Joker/jade](https://github.com/Joker/jade) |
| 5 | Handlebars | [mailgun/raymond](https://github.com/mailgun/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) |
| 6 | Jet | [CloudyKit/jet](https://github.com/CloudyKit/jet) |
| 7 | Ace | [yosssi/ace](https://github.com/yosssi/ace) |
[List of Examples](https://github.com/kataras/iris/tree/master/_examples/view).

View File

@ -1,6 +1,7 @@
package view
import (
"strings"
"sync"
"github.com/yosssi/ace"
@ -62,6 +63,12 @@ func Ace(fs interface{}, extension string) *AceEngine {
[]*ace.File{},
)
if strings.Contains(name, "layout") {
for k, v := range s.layoutFuncs {
funcs[k] = v
}
}
opts := &ace.Options{
Extension: extension[1:],
FuncMap: funcs,

View File

@ -1,281 +0,0 @@
package view
import (
"bytes"
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"github.com/kataras/iris/v12/context"
"github.com/eknkc/amber"
)
// AmberEngine contains the amber view engine structure.
type AmberEngine struct {
fs fs.FS
// files configuration
rootDir string
extension string
reload bool
//
rmu sync.RWMutex // locks for `ExecuteWiter` when `reload` is true.
templateCache map[string]*template.Template
bufPool *sync.Pool
Options amber.Options
}
var (
_ Engine = (*AmberEngine)(nil)
_ EngineFuncer = (*AmberEngine)(nil)
)
var amberOnce = new(uint32)
// Amber creates and returns a new amber view engine.
// The given "extension" MUST begin with a dot.
//
// Usage:
// Amber("./views", ".amber") or
// Amber(iris.Dir("./views"), ".amber") or
// Amber(embed.FS, ".amber") or Amber(AssetFile(), ".amber") for embedded data.
func Amber(dirOrFS interface{}, extension string) *AmberEngine {
if atomic.LoadUint32(amberOnce) > 0 {
panic("Amber: cannot be registered twice as its internal implementation share the same template functions across instances.")
} else {
atomic.StoreUint32(amberOnce, 1)
}
fileSystem := getFS(dirOrFS)
s := &AmberEngine{
fs: fileSystem,
rootDir: "/",
extension: extension,
templateCache: make(map[string]*template.Template),
Options: amber.Options{
PrettyPrint: false,
LineNumbers: false,
VirtualFilesystem: http.FS(fileSystem),
},
bufPool: &sync.Pool{New: func() interface{} {
return new(bytes.Buffer)
}},
}
builtinFuncs := template.FuncMap{
"render": func(name string, binding interface{}) (template.HTML, error) {
result, err := s.executeTemplateBuf(name, binding)
return template.HTML(result), err
},
}
for k, v := range builtinFuncs {
amber.FuncMap[k] = v
}
return s
}
// RootDir sets the directory to be used as a starting point
// to load templates from the provided file system.
func (s *AmberEngine) RootDir(root string) *AmberEngine {
if s.fs != nil && root != "" && root != "/" && root != "." && root != s.rootDir {
sub, err := fs.Sub(s.fs, s.rootDir)
if err != nil {
panic(err)
}
s.fs = sub // here so the "middleware" can work.
}
s.rootDir = filepath.ToSlash(root)
return s
}
// Name returns the amber engine's name.
func (s *AmberEngine) Name() string {
return "Amber"
}
// Ext returns the file extension which this view engine is responsible to render.
// If the filename extension on ExecuteWriter is empty then this is appended.
func (s *AmberEngine) Ext() string {
return s.extension
}
// Reload if set 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,
// no concurrent access across clients, use it only on development status.
// It's good to be used side by side with the https://github.com/kataras/rizla reloader for go source files.
func (s *AmberEngine) Reload(developmentMode bool) *AmberEngine {
s.reload = developmentMode
return s
}
// SetPrettyPrint if pretty printing is enabled.
// Pretty printing ensures that the output html is properly indented and in human readable form.
// Defaults to false, response is minified.
func (s *AmberEngine) SetPrettyPrint(pretty bool) *AmberEngine {
s.Options.PrettyPrint = true
return s
}
// AddFunc adds the function to the template's function map.
// It is legal to overwrite elements of the default actions:
// - url func(routeName string, args ...string) string
// - urlpath func(routeName string, args ...string) string
// - render func(fullPartialName string) (template.HTML, error).
//
// Note that, Amber does not support functions per template,
// instead it's using the "call" directive so any template-specific
// functions should be passed using `Context.View/ViewData` binding data.
// This method will modify the global amber's FuncMap which considers
// as the "builtin" as this is the only way to actually add a function.
// Note that, if you use more than one amber engine, the functions are shared.
func (s *AmberEngine) AddFunc(funcName string, funcBody interface{}) {
s.rmu.Lock()
amber.FuncMap[funcName] = funcBody
s.rmu.Unlock()
}
// Load parses the templates to the engine.
// It is responsible to add the necessary global functions.
//
// Returns an error if something bad happens, user is responsible to catch it.
func (s *AmberEngine) Load() error {
// If only custom templates should be loaded.
if (s.fs == nil || context.IsNoOpFS(s.fs)) && len(s.templateCache) > 0 {
return nil
}
rootDirName := getRootDirName(s.fs)
return walk(s.fs, "", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info == nil || info.IsDir() {
return nil
}
if s.extension != "" {
if !strings.HasSuffix(path, s.extension) {
return nil
}
}
if s.rootDir == rootDirName {
path = strings.TrimPrefix(path, rootDirName)
path = strings.TrimPrefix(path, "/")
}
contents, err := asset(s.fs, path)
if err != nil {
return fmt.Errorf("%s: %w", path, err)
}
err = s.ParseTemplate(path, contents)
if err != nil {
return fmt.Errorf("%s: %v", path, err)
}
return nil
})
}
// ParseTemplate adds a custom template from text.
// This template parser does not support funcs per template directly.
// Two ways to add a function:
// Globally: Use `AddFunc` or pass them on `View` instead.
// Per Template: Use `Context.ViewData/View`.
func (s *AmberEngine) ParseTemplate(name string, contents []byte) error {
s.rmu.Lock()
defer s.rmu.Unlock()
comp := amber.New()
comp.Options = s.Options
err := comp.ParseData(contents, name)
if err != nil {
return err
}
data, err := comp.CompileString()
if err != nil {
return err
}
name = strings.TrimPrefix(name, "/")
/*
New(...).Funcs(s.builtinFuncs):
This won't work on amber, it loads only amber.FuncMap which is global.
Relative code:
https://github.com/eknkc/amber/blob/cdade1c073850f4ffc70a829e31235ea6892853b/compiler.go#L771-L794
*/
tmpl := template.New(name).Funcs(amber.FuncMap)
_, err = tmpl.Parse(data)
if err == nil {
s.templateCache[name] = tmpl
}
return err
}
func (s *AmberEngine) executeTemplateBuf(name string, binding interface{}) (string, error) {
buf := s.bufPool.Get().(*bytes.Buffer)
buf.Reset()
tmpl := s.fromCache(name)
if tmpl == nil {
s.bufPool.Put(buf)
return "", ErrNotExist{Name: name, IsLayout: false, Data: binding}
}
err := tmpl.ExecuteTemplate(buf, name, binding)
result := strings.TrimSuffix(buf.String(), "\n") // on amber it adds a new line.
s.bufPool.Put(buf)
return result, err
}
func (s *AmberEngine) fromCache(relativeName string) *template.Template {
if s.reload {
s.rmu.RLock()
defer s.rmu.RUnlock()
}
if tmpl, ok := s.templateCache[relativeName]; ok {
return tmpl
}
return nil
}
// ExecuteWriter executes a template and writes its result to the w writer.
// layout here is useless.
func (s *AmberEngine) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
// re-parse the templates if reload is enabled.
if s.reload {
if err := s.Load(); err != nil {
return err
}
}
if tmpl := s.fromCache(filename); tmpl != nil {
return tmpl.Execute(w, bindingData)
}
return ErrNotExist{Name: filename, IsLayout: false, Data: bindingData}
}