mirror of
https://github.com/kataras/iris.git
synced 2025-12-17 09:57:01 +00:00
Add a 'ParseTemplate' method on view engines to manually parse and add a template from a text
examples at: https://github.com/kataras/iris/tree/master/_examples/view/parse-template relative to: https://github.com/kataras/iris/issues/1617
This commit is contained in:
101
view/amber.go
101
view/amber.go
@@ -22,8 +22,9 @@ type AmberEngine struct {
|
||||
reload bool
|
||||
//
|
||||
rmu sync.RWMutex // locks for `ExecuteWiter` when `reload` is true.
|
||||
funcs map[string]interface{}
|
||||
templateCache map[string]*template.Template
|
||||
|
||||
Options amber.Options
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -39,12 +40,17 @@ var (
|
||||
// Amber(iris.Dir("./views"), ".amber") or
|
||||
// Amber(AssetFile(), ".amber") for embedded data.
|
||||
func Amber(fs interface{}, extension string) *AmberEngine {
|
||||
fileSystem := getFS(fs)
|
||||
s := &AmberEngine{
|
||||
fs: getFS(fs),
|
||||
fs: fileSystem,
|
||||
rootDir: "/",
|
||||
extension: extension,
|
||||
templateCache: make(map[string]*template.Template),
|
||||
funcs: make(map[string]interface{}),
|
||||
Options: amber.Options{
|
||||
PrettyPrint: false,
|
||||
LineNumbers: false,
|
||||
VirtualFilesystem: fileSystem,
|
||||
},
|
||||
}
|
||||
|
||||
return s
|
||||
@@ -79,9 +85,16 @@ func (s *AmberEngine) Reload(developmentMode bool) *AmberEngine {
|
||||
// - 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()
|
||||
s.funcs[funcName] = funcBody
|
||||
amber.FuncMap[funcName] = funcBody
|
||||
s.rmu.Unlock()
|
||||
}
|
||||
|
||||
@@ -90,28 +103,6 @@ func (s *AmberEngine) AddFunc(funcName string, funcBody interface{}) {
|
||||
//
|
||||
// Returns an error if something bad happens, user is responsible to catch it.
|
||||
func (s *AmberEngine) Load() error {
|
||||
s.rmu.Lock()
|
||||
defer s.rmu.Unlock()
|
||||
|
||||
// prepare the global amber funcs
|
||||
funcs := template.FuncMap{}
|
||||
|
||||
for k, v := range amber.FuncMap { // add the amber's default funcs
|
||||
funcs[k] = v
|
||||
}
|
||||
|
||||
for k, v := range s.funcs {
|
||||
funcs[k] = v
|
||||
}
|
||||
|
||||
amber.FuncMap = funcs // set the funcs
|
||||
|
||||
opts := amber.Options{
|
||||
PrettyPrint: false,
|
||||
LineNumbers: false,
|
||||
VirtualFilesystem: s.fs,
|
||||
}
|
||||
|
||||
return walk(s.fs, s.rootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil || info.IsDir() {
|
||||
return nil
|
||||
@@ -123,24 +114,66 @@ func (s *AmberEngine) Load() error {
|
||||
}
|
||||
}
|
||||
|
||||
buf, err := asset(s.fs, path)
|
||||
contents, err := asset(s.fs, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
|
||||
name := strings.TrimPrefix(path, "/")
|
||||
|
||||
tmpl, err := amber.CompileData(buf, name, opts)
|
||||
err = s.ParseTemplate(path, contents)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", path, err)
|
||||
return fmt.Errorf("%s: %v", path, err)
|
||||
}
|
||||
|
||||
s.templateCache[name] = tmpl
|
||||
|
||||
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, "/")
|
||||
|
||||
/* Sadly, this does not work, only builtin amber.FuncMap
|
||||
can be executed as function, the rest are compiled as data (prepends a "call"),
|
||||
relative code:
|
||||
https://github.com/eknkc/amber/blob/cdade1c073850f4ffc70a829e31235ea6892853b/compiler.go#L771-L794
|
||||
|
||||
tmpl := template.New(name).Funcs(amber.FuncMap).Funcs(s.funcs)
|
||||
if len(funcs) > 0 {
|
||||
tmpl.Funcs(funcs)
|
||||
}
|
||||
|
||||
We can't add them as binding data of map type
|
||||
because those data can be a struct by the caller and we don't want to messup.
|
||||
*/
|
||||
|
||||
tmpl := template.New(name).Funcs(amber.FuncMap)
|
||||
_, err = tmpl.Parse(data)
|
||||
if err == nil {
|
||||
s.templateCache[name] = tmpl
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *AmberEngine) fromCache(relativeName string) *template.Template {
|
||||
if s.reload {
|
||||
s.rmu.RLock()
|
||||
|
||||
@@ -100,6 +100,7 @@ type DjangoEngine struct {
|
||||
filters map[string]FilterFunction
|
||||
// globals share context fields between templates.
|
||||
globals map[string]interface{}
|
||||
Set *pongo2.TemplateSet
|
||||
templateCache map[string]*pongo2.Template
|
||||
}
|
||||
|
||||
@@ -204,12 +205,6 @@ func (s *DjangoEngine) RegisterTag(tagName string, fn TagParser) error {
|
||||
//
|
||||
// Returns an error if something bad happens, user is responsible to catch it.
|
||||
func (s *DjangoEngine) Load() error {
|
||||
set := pongo2.NewSet("", &tDjangoAssetLoader{fs: s.fs, rootDir: s.rootDir})
|
||||
set.Globals = getPongoContext(s.globals)
|
||||
|
||||
s.rmu.Lock()
|
||||
defer s.rmu.Unlock()
|
||||
|
||||
return walk(s.fs, s.rootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil || info.IsDir() {
|
||||
return nil
|
||||
@@ -221,17 +216,39 @@ func (s *DjangoEngine) Load() error {
|
||||
}
|
||||
}
|
||||
|
||||
buf, err := asset(s.fs, path)
|
||||
contents, err := asset(s.fs, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := strings.TrimPrefix(path, "/")
|
||||
s.templateCache[name], err = set.FromBytes(buf)
|
||||
return err
|
||||
return s.ParseTemplate(path, contents)
|
||||
})
|
||||
}
|
||||
|
||||
// ParseTemplate adds a custom template from text.
|
||||
// This parser does not support funcs per template. Use the `AddFunc` instead.
|
||||
func (s *DjangoEngine) ParseTemplate(name string, contents []byte) error {
|
||||
s.rmu.Lock()
|
||||
defer s.rmu.Unlock()
|
||||
|
||||
s.initSet()
|
||||
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
tmpl, err := s.Set.FromBytes(contents)
|
||||
if err == nil {
|
||||
s.templateCache[name] = tmpl
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *DjangoEngine) initSet() { // protected by the caller.
|
||||
if s.Set == nil {
|
||||
s.Set = pongo2.NewSet("", &tDjangoAssetLoader{fs: s.fs, rootDir: s.rootDir})
|
||||
s.Set.Globals = getPongoContext(s.globals)
|
||||
}
|
||||
}
|
||||
|
||||
// getPongoContext returns the pongo2.Context from map[string]interface{} or from pongo2.Context, used internaly
|
||||
func getPongoContext(templateData interface{}) pongo2.Context {
|
||||
if templateData == nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -24,7 +25,7 @@ type HandlebarsEngine struct {
|
||||
// parser configuration
|
||||
layout string
|
||||
rmu sync.RWMutex
|
||||
helpers map[string]interface{}
|
||||
funcs template.FuncMap
|
||||
templateCache map[string]*raymond.Template
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ func Handlebars(fs interface{}, extension string) *HandlebarsEngine {
|
||||
rootDir: "/",
|
||||
extension: extension,
|
||||
templateCache: make(map[string]*raymond.Template),
|
||||
helpers: make(map[string]interface{}),
|
||||
funcs: make(template.FuncMap), // global
|
||||
}
|
||||
|
||||
// register the render helper here
|
||||
@@ -94,14 +95,21 @@ func (s *HandlebarsEngine) Layout(layoutFile string) *HandlebarsEngine {
|
||||
return s
|
||||
}
|
||||
|
||||
// AddFunc adds the function to the template's function map.
|
||||
// AddFunc adds a function to the templates.
|
||||
// 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) (raymond.HTML, error).
|
||||
func (s *HandlebarsEngine) AddFunc(funcName string, funcBody interface{}) {
|
||||
s.rmu.Lock()
|
||||
s.helpers[funcName] = funcBody
|
||||
s.funcs[funcName] = funcBody
|
||||
s.rmu.Unlock()
|
||||
}
|
||||
|
||||
// AddGlobalFunc registers a global template function for all Handlebars view engines.
|
||||
func (s *HandlebarsEngine) AddGlobalFunc(funcName string, funcBody interface{}) {
|
||||
s.rmu.Lock()
|
||||
raymond.RegisterHelper(funcName, funcBody)
|
||||
s.rmu.Unlock()
|
||||
}
|
||||
|
||||
@@ -110,14 +118,6 @@ func (s *HandlebarsEngine) AddFunc(funcName string, funcBody interface{}) {
|
||||
//
|
||||
// Returns an error if something bad happens, user is responsible to catch it.
|
||||
func (s *HandlebarsEngine) Load() error {
|
||||
s.rmu.Lock()
|
||||
defer s.rmu.Unlock()
|
||||
|
||||
// register the global helpers on the first load
|
||||
if len(s.templateCache) == 0 && s.helpers != nil {
|
||||
raymond.RegisterHelpers(s.helpers)
|
||||
}
|
||||
|
||||
return walk(s.fs, s.rootDir, func(path string, info os.FileInfo, _ error) error {
|
||||
if info == nil || info.IsDir() {
|
||||
return nil
|
||||
@@ -129,22 +129,37 @@ func (s *HandlebarsEngine) Load() error {
|
||||
}
|
||||
}
|
||||
|
||||
buf, err := asset(s.fs, path)
|
||||
contents, err := asset(s.fs, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := strings.TrimPrefix(path, "/")
|
||||
tmpl, err := raymond.Parse(string(buf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.templateCache[name] = tmpl
|
||||
|
||||
return nil
|
||||
return s.ParseTemplate(path, string(contents), nil)
|
||||
})
|
||||
}
|
||||
|
||||
// ParseTemplate adds a custom template from text.
|
||||
func (s *HandlebarsEngine) ParseTemplate(name string, contents string, funcs template.FuncMap) error {
|
||||
s.rmu.Lock()
|
||||
defer s.rmu.Unlock()
|
||||
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
tmpl, err := raymond.Parse(contents)
|
||||
if err == nil {
|
||||
// Add functions for this template.
|
||||
for k, v := range s.funcs {
|
||||
tmpl.RegisterHelper(k, v)
|
||||
}
|
||||
|
||||
for k, v := range funcs {
|
||||
tmpl.RegisterHelper(k, v)
|
||||
}
|
||||
|
||||
s.templateCache[name] = tmpl
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *HandlebarsEngine) fromCache(relativeName string) *raymond.Template {
|
||||
if s.reload {
|
||||
s.rmu.RLock()
|
||||
@@ -201,7 +216,7 @@ func (s *HandlebarsEngine) ExecuteWriter(w io.Writer, filename string, layout st
|
||||
context = make(map[string]interface{}, 1)
|
||||
}
|
||||
// I'm implemented the {{ yield }} as with the rest of template engines, so this is not inneed for iris, but the user can do that manually if want
|
||||
// there is no performanrce different: raymond.RegisterPartialTemplate(name, tmpl)
|
||||
// there is no performance cost: raymond.RegisterPartialTemplate(name, tmpl)
|
||||
context["yield"] = raymond.SafeString(contents)
|
||||
}
|
||||
|
||||
|
||||
26
view/html.go
26
view/html.go
@@ -235,18 +235,8 @@ func (s *HTMLEngine) Load() error {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HTMLEngine) initRootTmpl() { // protected by the caller.
|
||||
if s.Templates == nil {
|
||||
// the root template should be the same,
|
||||
// no matter how many reloads as the
|
||||
// following unexported fields cannot be modified.
|
||||
s.Templates = template.New(s.rootDir)
|
||||
s.Templates.Delims(s.left, s.right)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseTemplate adds a custom template to the root template.
|
||||
func (s *HTMLEngine) ParseTemplate(name string, contents []byte, funcMap template.FuncMap) (err error) {
|
||||
func (s *HTMLEngine) ParseTemplate(name string, contents []byte, funcs template.FuncMap) (err error) {
|
||||
s.rmu.Lock()
|
||||
defer s.rmu.Unlock()
|
||||
|
||||
@@ -268,13 +258,23 @@ func (s *HTMLEngine) ParseTemplate(name string, contents []byte, funcMap templat
|
||||
}
|
||||
|
||||
tmpl.Funcs(emptyFuncs).Funcs(s.funcs)
|
||||
if len(funcMap) > 0 {
|
||||
tmpl.Funcs(funcMap) // custom for this template.
|
||||
if len(funcs) > 0 {
|
||||
tmpl.Funcs(funcs) // custom for this template.
|
||||
}
|
||||
_, err = tmpl.Parse(text)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HTMLEngine) initRootTmpl() { // protected by the caller.
|
||||
if s.Templates == nil {
|
||||
// the root template should be the same,
|
||||
// no matter how many reloads as the
|
||||
// following unexported fields cannot be modified.
|
||||
s.Templates = template.New(s.rootDir)
|
||||
s.Templates.Delims(s.left, s.right)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTMLEngine) executeTemplateBuf(name string, binding interface{}) (*bytes.Buffer, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := s.Templates.ExecuteTemplate(buf, name, binding)
|
||||
|
||||
35
view/jet.go
35
view/jet.go
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
|
||||
@@ -28,6 +29,7 @@ type JetEngine struct {
|
||||
// The Set is the `*jet.Set`, exported to offer any custom capabilities that jet users may want.
|
||||
// Available after `Load`.
|
||||
Set *jet.Set
|
||||
mu sync.Mutex
|
||||
|
||||
// Note that global vars and functions are set in a single spot on the jet parser.
|
||||
// If AddFunc or AddVar called before `Load` then these will be set here to be used via `Load` and clear.
|
||||
@@ -212,14 +214,7 @@ func (l *jetLoader) Exists(name string) (string, bool) {
|
||||
|
||||
// Load should load the templates from a physical system directory or by an embedded one (assets/go-bindata).
|
||||
func (s *JetEngine) Load() error {
|
||||
s.Set = jet.NewHTMLSetLoader(s.loader)
|
||||
s.Set.SetDevelopmentMode(s.developmentMode)
|
||||
|
||||
if s.vars != nil {
|
||||
for key, value := range s.vars {
|
||||
s.Set.AddGlobal(key, value)
|
||||
}
|
||||
}
|
||||
s.initSet()
|
||||
|
||||
// Note that, unlike the rest of template engines implementations,
|
||||
// we don't call the Set.GetTemplate to parse the templates,
|
||||
@@ -228,6 +223,30 @@ func (s *JetEngine) Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseTemplate accepts a name and contnets to parse and cache a template.
|
||||
// This parser does not support funcs per template. Use the `AddFunc` instead.
|
||||
func (s *JetEngine) ParseTemplate(name string, contents string) error {
|
||||
s.initSet()
|
||||
|
||||
_, err := s.Set.LoadTemplate(name, contents)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *JetEngine) initSet() {
|
||||
s.mu.Lock()
|
||||
if s.Set == nil {
|
||||
s.Set = jet.NewHTMLSetLoader(s.loader)
|
||||
s.Set.SetDevelopmentMode(s.developmentMode)
|
||||
|
||||
if s.vars != nil {
|
||||
for key, value := range s.vars {
|
||||
s.Set.AddGlobal(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
type (
|
||||
// JetRuntimeVars is a type alias for `jet.VarMap`.
|
||||
// Can be used at `AddJetRuntimeVars/JetEngine.AddRuntimeVars`
|
||||
|
||||
Reference in New Issue
Block a user