cloudykit / jet Goto Github PK
View Code? Open in Web Editor NEWJet template engine
License: Apache License 2.0
Jet template engine
License: Apache License 2.0
It would be great to have a debug mode that disables template caching (and could also print out debug statements about template blocks found etc.) so as to not require constantly restarting/recompiling the app while developing.
Is that something you could add?
Hi,
nice work!
I would like to include your template engine in my benchmark. Could your please open a pull request and submit your templates.
goTemplateBenchmark
thanks.
Firstly, congratulations, nice work!
I had a quick skim though the code and saw reflection being used in a couple of places. Do you think performance could be optimised further without reflection?
This is specifically targeted towards the strings.Replace
default func that takes an integer as the last argument to indicate the maximum number of replacements that should happen. For one, this looks weird in a template and we can also default this to -1 to indicate an unlimited number of replacements. This is easily done using jet.Func
s.
Adding this will give ability for users to create fast functions, and with this we can remove the len builtin, and implement trans as a default func.
ex:
set.AddGlobal("len",func(a JetArguments) reflect.Value {
a.RequireNumOfArguments(1,1)
slice := a.GetKind(0,reflect.Slice,reflect.Array,reflect.Map,reflect.String)
return reflect.Value(slice.Len())
})
Some template frameworks (at least in Java) replaces non existent variables with blank values. Is it possible to do something like this in jet?
My use case is that there is a common html snippet which renders content based on some variables.
If variables are not set it should assume those values as blank and continue executing. Right now Jet is breaking and expecting me to set those variables albeit empty everywhere.
Go code:
...
func theaterPage(client *gin.Context) {
channel := client.Param("channel") // is just a string like "Left4Bot"
view, err := views.GetTemplate("theater.tpl.html")
if err != nil {
return
}
view.Execute(client.Writer, jet.VarMap{
"ChannelName": reflect.ValueOf(channel),
}, nil)
}
My block:
{{block embedPlayer(channel="toby3d", autoplay=false)}}
<iframe src="//www.hitbox.tv/#!/embed/{{ channel }}?autoplay={{ autoplay }}" width="360" height="640" frameborder="0" allowfullscreen></iframe>
{{end}}
My template:
{{ block body() }}
<main>
{{ yield embedPlayer(channel=.ChannelName, autoplay=true) }}
</main>
{{ end }}
And this does not work. How can I implement a variable in a block parameter?
{{.myvar}} is OK but "blahblah...{{.myvar}}blah..." not working, rendering stops.
There's a snippet on wiki page:
templateName := "home.jet"
t, err := View.GetTemplate(templateName)
if err != nil {
// template could not be loaded
}
var w bytes.Buffer // needs to conform to io.Writer interface (like gin's context.Writer for example)
vars := map[string]interface{}
if err = t.Execute(&w, vars, nil); err != nil {
// error when executing template
}
which causes syntax error:
cannot use map[string]interface {} literal (type map[string]interface {}) as type jet.VarMap in argument to tpl.Execute
Fix:
-vars := map[string]interface{}
+vars := map[string]reflect.Value{}
or
-vars := map[string]interface{}
+vars := jet.VarMap{}
I wonder -- was it a design change? I kinda like map[string]interface{}
better than map[string]reflect.Value
, since I can easily pass my structs as interface{}
without having to convert them via reflect.ValueOf()
.
{{if ...}}...{{end}} or {{...}} will leave space in the output.
go1.6+ template like {{- ...}} or {{... -}} is OK.
Thanks!
It would be nice to know which parts of the rendered template took the longest. Would it be hard to add instrumentation on the cached representation so one could fetch that after executing the template? Would be nice for web frameworks or optimizing a template for speed. Twig (template engine used in Symfony, PHP) has something like this:
[actually part of their web developer debug toolbar available while developing]
I want to do something like this:
<!-- file: "layout.jet" -->
<!DOCTYPE html>
<html>
<head>
<title>{{yield title()}}</title>
</head>
<body>
<div class="menu">
<menu {{if mnu=="m1"}} class="active"{{end}}>Menu #1</menu>
<menu {{if mnu=="m2"}} class="active"{{end}}>Menu #2</menu>
<menu {{if mnu=="m3"}} class="active"{{end}}>Menu #3</menu>
</div>
{{yield body()}}
</body>
</html>
<!-- file: "home.jet" -->
{{extends "layout.jet"}}
{{ mnu := "m1" }}
{{block title()}}My title{{end}}
{{block body()}}
<main>
This content will be yielded in the layout above.
</main>
{{end}}
But I got runtime error message :
Jet Runtime Error("layout.jet":8): identifier "mnu" is not available in the current scope map[]
if I declare mnu variable in layout.jet file, the page will be rendered as I expected.
but how can I set the value of mnu variable in view file?
I tried {{ mnu := yield menuValueBlock() }} in layout file but failed.
{{msg}}Only text to be translated{{end}}
<button>{{trans: "Submit" }}</button>
refs #3
// Func function implementing this type are called directly, which is faster than calling through reflect.
// if a function is being called many times in the execution of a template, you may consider implement
// a wrapper to that func implementing a Func
type Func func(Arguments) reflect.Value
This adds support for parsing, execute and discard the template
I use a template variable to send a function, how to call it in the template?
func Full() string {
return "ok"
}
data := make(map[string]interface{})
data["Full"]= Full
https://github.com/golang/go/blob/master/src/html/template/template.go#L351
func (t *Template) Delims(left, right string) *Template
Can it be realized?
// Ranger a value implementing a ranger interface is able to iterate on his value
// and can be used directly in a range statement
type Ranger interface {
Range() (reflect.Value, reflect.Value, bool)
}
Hey there
Is it possible to introspect Jet templates once their parsed, or before their parsed? Ideally I'd like to be able to pull out which variables are used in a template before execution, so I can decrease lookup time by not loading unnecessary data. The use case here is per-user template execution for emails. Users have KV pairs stored in a database, and when rendering a template it'd be great if I don't have to pull them all in.
Thanks!
I always get error 500 if I try execute template which contains block with name content()
like that:
{{yield content()}}
...
{{block content()}} {{end}}
But after change name all is okay. Tested in enabled development mode.
When I try to build my project with Jet template engine I get an error:
$ GOOS=linux GOARCH=arm go build -i -o rixvox
# github.com/CloudyKit/jet
..\..\CloudyKit\jet\default.go:56: constant 99999999999 overflows int
Move eval tests to files inside ./testData/eval and move parser tests files to ./testData/parser
Taking in example
RunJetTest(t, data, nil, "actionNode_AddIntString", `{{ 2+"1" }}`, "3")
RunJetTest(t, data, nil, "actionNode_AddStringInt", `{{ "1"+2 }}`, "12")
The first test the left operand is a number and the right is a string then the right operand is converted to a number and the expression is evaluated as add expression.
The second test the left operand is a string and the right is a number the expression is evaluated as a string concatenation.
I propose a breaking change in the template language, adding a operator for string concatenation and let numeric operators to work only with numbers.
Like php $val.$val2.
We should follow the example of the larger Go ecosystem and pin the supported Go versions to the latest two – that's 1.8 and 1.9 right now.
If use 'msg' as template variable name, will appear this error:
unexpected token <msg> on operand
Support for internationalization can be done by adding a new token type in the lexer, and an evaluator that would call the translator.Translate passed to the template.ExecuteI18n.
Also add a builtin function trans, a type Translator interface{ Translate(v string,i ...interface{}) error }
I'm trying to imitate some of the approaches other frameworks take to provide a common library of reusable parts for forms. The {{import "form_theme_bootstrap.jet" }}
and the {{yield}}
directive are good building blocks to achieve that. The problem arrises when trying to pass local variables to use when yielding, for example, an input-field
block in the sense that it's not possible (at least I don't see a way).
Let me explain with some code:
<!-- file: "form_theme_bootstrap.jet" -->
{{block input-field}}
<div class="{{container_class}} {{if has_error}}invalid{{else}}valid{{end}}">
<input id="{{name}}" name="{{name}}" value="{{value}}" />
{{if has_error}}
<div class="form_error">{{error}}</div>
{{end}}
</div>
{{end}}
<!-- file: "user_form_edit.jet" -->
{{import "form_theme_bootstrap.jet"}}
<form>
<!-- not sure how passing a hash/map of local variables would work -->
{{yield input-field { name: "firstname", value: .Firstname, has_error: errs.HasErrorFor("Firstname"), error: errs.ErrorFor("Firstname"), container_class: "" } }}
</form>
Do you see what I'm trying to do? Having a way to construct a map on the fly in the template would be really great, that way I could use that as the context for the yield.
Thanks again for all the hard work and this great library!
jet is very nice so I will use jet on my product.
I try render raw html defined template.HTML but render escaped html.
source: master...Konboi:master
I work whith gin and want connect in project templates engine CloudyKit/jet.
When you try to connect i have error becouse jet use dont standart type for templates (
if I am not mistaken *jet.Templates)
How to connect CloudyKit/jet to web framework gin?
Possible add multiple template directory or add template file?
I don't see any options.
var View = jet.NewHTMLSet("./templates"))
Eg:
View.AddDir("./modules/group/views")
Or:
View.AddTemplateFile("./modules/group/views/add.html")
View.AddTemplateFile("./modules/group/views/delete.html")
View.AddTemplateFile("./modules/group/views/list.html")
View.AddTemplateFile("./modules/group/views/update.html")
OR:
fileName:="./modules/group/views/update.html"
content,err:=ioutil.ReadFile(fileName)
if err!=nil{
View.AddTemplate(fileName,content)
}
├── modules
│ ├── config
│ │ ├── config.go
│ │ └── init.go
│ ├── group
│ │ ├── acl.go
│ │ ├── controller.go
│ │ ├── init.go
│ │ ├── locale
│ │ │ ├── en-US.yaml
│ │ │ └── tr-TR.yaml
│ │ ├── locale.go
│ │ ├── models.go
│ │ └── views
│ │ ├── add.html
│ │ ├── delete.html
│ │ ├── list.html
│ │ └── update.html
├── templates
│ ├── a.html
│ ├── a.js
│ ├── admin
│ │ └── menu
│ │ └── index.gohtml
│ ├── default
│ │ └── layouts
│ │ └── layout.gohtml
│ ├── index.gohtml
│ └── v1
│ └── layouts
│ └── layout.tpl
This is my first time trying Jet.
Layout and template created :
<!-- file: "views/layouts/application.jet" -->
<!DOCTYPE html>
<html>
<head></head>
<body>
{{yield body()}}
</body>
</html>
<!-- file: "views/home.jet" -->
{{extends "layouts/application.jet"}}
{{block body()}}
<main>
This content will be yielded in the layout above.
</main>
{{end}}
Echo middleware :
package middleware
import (
"fmt"
"io"
"github.com/CloudyKit/jet"
"github.com/labstack/echo"
)
// Template type.
type Template struct {
View *jet.Set
}
// Render satisfies the Echo.Renderer interface.
func (r *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
t, err := r.View.GetTemplate(name)
if err != nil {
panic(fmt.Errorf("Fatal error template file: %s", err))
}
vars, ok := data.(jet.VarMap)
if !ok { // data passed wasn't a var map
vars = jet.VarMap{}
}
err = t.Execute(w, vars, nil)
if err != nil {
// render an error response?
// log the error?
// up to you
}
return nil
}
Main package :
package main
import (
"path/filepath"
"github.com/CloudyKit/jet"
"github.com/facebookgo/grace/gracehttp"
"github.com/my/project/app/controllers"
middlewares "github.com/my/project/app/middleware"
"github.com/labstack/echo"
)
func main() {
// Echo instance
e := echo.New()
// Template middleware
view := jet.NewHTMLSet(filepath.Join("./app", "views"))
view.SetDevelopmentMode(true)
e.Renderer = &middlewares.Template{View: view}
// Authentication
e.GET("/login", controllers.LoginIndex)
// Start server
e.Server.Addr = ":1323"
e.Logger.Fatal(gracehttp.Serve(e.Server))
}
Endpoint handler :
package controllers
import (
"github.com/CloudyKit/jet"
"github.com/labstack/echo"
)
// LoginIndex handle login view.
func LoginIndex(c echo.Context) error {
vars := jet.VarMap{}
vars.Set("test", true)
return c.Render(200, "welcome.jet", vars)
}
Result :
template: welcome.jet:2: unexpected token <extends> on operand goroutine 57 [running]:
github.com/my/project/vendor/github.com/labstack/echo/middleware.RecoverWithConfig.func1.1.1(0x9f5278, 0x1000, 0x15470000, 0xc60fe0, 0xc420276150)
/home/my-user/.gvm/pkgsets/go1.8.3/global/src/github.com/my/project/vendor/github.com/labstack/echo/middleware/recover.go:75 +0x134
panic(0x9382c0, 0xc42027a9d0)
/home/my-user/.gvm/gos/go1.8.3/src/runtime/panic.go:489 +0x2cf
github.com/my/project/app/middleware.(*Template).Render(0xc42000ebf8, 0xc51fe0, 0xc420276d90, 0x9d8412, 0xb, 0x970040, 0xc42021aed0, 0xc60fe0, 0xc420276150, 0xc42027a93a, ...)
/home/my-user/.gvm/pkgsets/go1.8.3/global/src/github.com/my/project/app/middleware/template.go:20 +0x198
github.com/my/project/vendor/github.com/labstack/echo.(*context).Render(0xc420276150, 0xc8, 0x9d8412, 0xb, 0x970040, 0xc42021aed0, 0xc87f90, 0x1c)
/home/my-user/.gvm/pkgsets/go1.8.3/global/src/github.com/my/project/vendor/github.com/labstack/echo/context.go:369 +0xcb
github.com/my/project/app/controllers.LoginIndex(0xc60fe0, 0xc420276150, 0xc42028a748, 0xc42021aea0)
/home/my-user/.gvm/pkgsets/go1.8.3/global/src/github.com/my/project/app/controllers/auth.go:12
Are there something missing in my implementation?
This would simplify template loading, by default, defaultExtensions would be .html.jet .jet. then when trying to load a template "dashboard/main" Jet would first check for "dashboard/main", then "dashboard/main.html.jet" and lastly "dashboard/main.jet" none found then return an error.
this also need to work with relative paths #16
@annismckenzie, what do you think about this feature ?
I want to cache templates in redis for an hour, but jet itself caches templates permanently, is there any way to go about this?
I am planning to use jet for generating Html email content. How do I read all the rendered Html into a String. All the tests I've found so far point to writing it to Http response writer.
// SafeWriter escapee func, func implementing this type will write direct in the writer skipping the escape
// faze, use this type to create special types of escapee funcs
type SafeWriter func(io.Writer, []byte)
{{if myslice}} or {{if len(myslice)>0}}...{{end}} not working, have to use {{if isset(len(myslice)>0)}}...{{end}}, can it be simple just like {{if mystring}}?
Hey guys,
this might not be related to Jet directly but asking other experienced developers doesn't hurt.
How would you approach inlining the CSS within email templates?
Say, I have a set of Jet email templates and a common .css file. During the template execution, I'd like to be able to inline all the CSS into the HTML tags.
Is there anything in Jet that would help me do it?
Or would you suggest executing the Jet template first and then iterate on the resulting HTML with tools like https://inliner.cm/ or https://templates.mailchimp.com/resources/inline-css/ ?
Thanks for your replies
Hi. Thanks for your great template engine! Is it possible to use a variable or function result in the extends, import, or includes statements?
Not quite sure how should this be used? Thanks!
P.S. I mean examples of logic connection. How can you use jet along with gin?
Adding for tracking
IndexExpr ::= expression '[' expression ']'
SliceExpr ::= expression '[' expression':' expression (':' expression)? ']'
{{if x>y}}blahblah...{{end}}
emit error: unexpected bad character U+003C '<' in call expression.
Is this syntax not supported?
Hi guys,
so sorry if I'm asking stupid question
but I really need your help, because I'm new in golang world
wondering how can I use this template engine with iris framework
or at least with httprouter
hope you want answer my question,
thanks!
I have a function in the var map (due to it's dependency on the request itself). It was done like this:
vars.SetFunc("VURL", func(a jet.Arguments) reflect.Value {})
html.html:
{{yield bodytemplate()}}
---
page.html:
{{extends "html.html"}}
{{import "utilities.html"}}
{{block bodytemplate()}}
{{yield body()}}
{{end}}
---
index.html:
{{extends "page.html"}}
{{block body()}}
{{yield video("id")}}
{{end}}
---
utilities.html:
{{block video(url)}}
{{video := VURL(url)}}
<video src="{{video}}">
{{end}}
When it is called in {{block body()}}, it works, when it is called in {{block video(url)}} it gets the following error:
Error #01: Jet Runtime Error("utilities.html":25): identifier "VURL" is not available in the current scope map[]
I can't tell from the docs how to propagate the context (or not overwrite by accident).
I added some tests code in the TestEvalIndexExpression test:
issues:
a) It seems you can't do map["key1"]["key2"] (you get unexpected "[")
b) Couldn't nested lookups be supported when the nested interface{} value is a map? ("indexing is not supported in value type interface")
code:
vars := make(VarMap)
vars.Set("nested", map[string]interface{}{"nested": map[string]interface{}{"nested": map[string]interface{}{"nested": map[string]interface{}{"name": "value", "strings": []string{"hello"}, "arr": []interface{}{"hello"}}}}})
RunJetTest(t, vars, nil, "IndexExpressionMap_6", `{{nested.nested.nested.nested.name}}`, "value")
RunJetTest(t, vars, nil, "IndexExpressionMap_7", `{{nested.nested.nested.nested.strings[0]}}`, "hello")
RunJetTest(t, vars, nil, "IndexExpressionMap_8", `{{nested.nested.nested.nested.arr[0]}}`, "hello")
RunJetTest(t, vars, nil, "IndexExpressionMap_9", `{{nested["nested"].nested.nested.name}}`, "value")
RunJetTest(t, vars, nil, "IndexExpressionMap_10", `{{nested.nested.nested["nested"].name}}`, "value")
RunJetTest(t, vars, nil, "IndexExpressionMap_11", `{{nested.nested.nested["nested"]["strings"][0]}}`, "hello")
RunJetTest(t, vars, nil, "IndexExpressionMap_12", `{{nested.nested.nested["nested"]["arr"][0]}}`, "hello")
RunJetTest(t, vars, nil, "IndexExpressionMap_13", `{{nested["nested"].nested["nested"].name}}`, "value")
RunJetTest(t, vars, nil, "IndexExpressionMap_14", `{{nested["nested"]["nested"].nested.name}}`, "value"
output
=== RUN JetTest(IndexExpressionMap_7)
--- FAIL: JetTest(IndexExpressionMap_7) (0.00s)
eval_test.go:127: Eval error: "Jet Runtime Error(\"IndexExpressionMap_7\":0): indexing is not supported in value type interface" executing IndexExpressionMap_7
=== RUN JetTest(IndexExpressionMap_8)
--- FAIL: JetTest(IndexExpressionMap_8) (0.00s)
eval_test.go:127: Eval error: "Jet Runtime Error(\"IndexExpressionMap_8\":0): indexing is not supported in value type interface" executing IndexExpressionMap_8
=== RUN JetTest(IndexExpressionMap_9)
--- PASS: JetTest(IndexExpressionMap_9) (0.00s)
=== RUN JetTest(IndexExpressionMap_10)
--- FAIL: JetTest(IndexExpressionMap_10) (0.00s)
eval_test.go:127: Eval error: "Jet Runtime Error(\"IndexExpressionMap_10\":0): indexing is not supported in value type interface" executing IndexExpressionMap_10
=== RUN JetTest(IndexExpressionMap_13)
--- FAIL: JetTest(IndexExpressionMap_13) (0.00s)
eval_test.go:127: Eval error: "Jet Runtime Error(\"IndexExpressionMap_13\":0): indexing is not supported in value type interface" executing IndexExpressionMap_13
=== RUN JetTest(IndexExpressionStruct_1)
--- PASS: JetTest(IndexExpressionStruct_1) (0.00s)
=== RUN JetTest(IndexExpressionStruct_2)
--- PASS: JetTest(IndexExpressionStruct_2) (0.00s)
--- FAIL: TestEvalIndexExpression (0.00s)
eval_test.go:111: Parsing error: template: IndexExpressionMap_11:1: unexpected "[" in command IndexExpressionMap_11 {{nested.nested.nested["nested"]["strings"][0]}}
eval_test.go:111: Parsing error: template: IndexExpressionMap_12:1: unexpected "[" in command IndexExpressionMap_12 {{nested.nested.nested["nested"]["arr"][0]}}
eval_test.go:111: Parsing error: template: IndexExpressionMap_14:1: unexpected "[" in command IndexExpressionMap_14 {{nested["nested"]["nested"].nested.name}
// Renderer any resulting value from an expression in an action that implements this
// interface will not be printed, instead, we will invoke his Render() method which will be
// responsible to render his self
type Renderer interface {
Render(*Runtime)
}
On https://github.com/CloudyKit/jet/wiki#getting-started
The comment on the line of code that says:
var View = jet.NewHTMLSet("./views")) // relative path to the Go file where this code is located
Should actually say // relative to the current working directory from where the code is run
or something along those lines because jet.NewHTMLSet()
doesn't traverse up the directory tree, only down (from what I tested), so stuff like jet.NewHTMLSet("./../views")
won't work.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.