Permalink
Browse files

Added static-flag for extends/include-tags (see template-tests for us…

…age) which if used renders all included/extended templates on startup (instead of dynamically). This improves the execution speed significantly.
  • Loading branch information...
1 parent edbf06a commit f2e4a784157ae5e2d22b87f5f81b099c190e7c4d @flosch committed Sep 7, 2012
Showing with 137 additions and 42 deletions.
  1. +2 −0 expr.go
  2. +80 −35 tags.go
  3. +12 −0 template.go
  4. +4 −0 template_examples/index2.html
  5. +4 −0 template_examples/index3.html
  6. +35 −7 template_test.go
View
@@ -404,6 +404,8 @@ func (e *expr) String() string {
}
func (e *expr) evalValue(ctx *Context) (interface{}, error) {
+ // Check ctx for nil
+
// Execute expression
var value interface{} = e.root
View
115 tags.go
@@ -10,6 +10,7 @@ import (
type TagHandler struct {
Execute func(*string, *executionContext, *Context) (*string, error)
Ignore func(*string, *executionContext) error
+ Prepare func(*tagNode, *Template) error
}
var Tags = map[string]*TagHandler{
@@ -20,8 +21,8 @@ var Tags = map[string]*TagHandler{
"endfor": nil,
"block": &TagHandler{Execute: tagBlock}, // Needs no Ignore-function because nested-blocks aren't allowed
"endblock": nil,
- "extends": &TagHandler{Execute: tagExtends},
- "include": &TagHandler{Execute: tagInclude},
+ "extends": &TagHandler{Execute: tagExtends, Prepare: tagExtendsPrepare},
+ "include": &TagHandler{Execute: tagInclude, Prepare: tagIncludePrepare},
"trim": &TagHandler{Execute: tagTrim, Ignore: tagTrimIgnore},
"endtrim": nil,
"remove": &TagHandler{Execute: tagRemove, Ignore: tagRemoveIgnore},
@@ -693,40 +694,84 @@ func tagRemoveIgnore(args *string, execCtx *executionContext) error {
return nil
}
-func tagExtends(args *string, execCtx *executionContext, ctx *Context) (*string, error) {
- // Extends executes the base template and passes the blocks via Context
+func createBaseTplForExtendInclude(args string, tpl *Template, ctx *Context) (*Template, error) {
+ // Skip an optional static flag at the beginning
+ if strings.HasPrefix(args, "static ") {
+ args = args[len("static "):]
+ }
// Example: {% extends "base.html" abc=<expr> ghi=<expr> ... %}
- _args := strings.Split(*args, " ")
+ _args := strings.Split(args, " ")
if len(_args) <= 0 {
return nil, errors.New("Please provide at least a filename to extend from.")
}
e, err := newExpr(&_args[0])
if err != nil {
return nil, err
}
- name, err := e.evalString(ctx)
+ name, err := e.evalString(ctx) // TODO: nil? does it work?
if err != nil {
return nil, err
}
//raw_context := _args[1:] // TODO
+ if strings.TrimSpace(*name) == "" {
+ return nil, errors.New("Please provide a propper template filename (empty or an expression evaluating to an empty string is not allowed).")
+ }
// Create new template
- if execCtx.template.locator == nil {
+ if tpl.locator == nil {
panic(fmt.Sprintf("Please provide a template locator to lookup template '%v'.", *name))
}
- base_tpl_content, err := execCtx.template.locator(name)
+ base_tpl_content, err := tpl.locator(name)
if err != nil {
return nil, err
}
// TODO: Do the pre-rendering (FromString) in the parent's FromString(), just do the execution here.
- base_tpl, err := FromString(*name, base_tpl_content, execCtx.template.locator)
+ base_tpl, err := FromString(*name, base_tpl_content, tpl.locator)
if err != nil {
return nil, err
}
+ return base_tpl, nil
+}
+
+func tagExtendsPrepare(tn *tagNode, tpl *Template) error {
+ // Only prepare, if args starts with "static "
+ if !strings.HasPrefix(tn.tagargs, "static ") {
+ return nil
+ }
+
+ // In preparation-phase we have no Context, so create an empty one.
+ base_tpl, err := createBaseTplForExtendInclude(tn.tagargs, tpl, &Context{})
+ if err != nil {
+ return err
+ }
+
+ // Save base_tpl
+ tpl.cache[fmt.Sprintf("extends_%s", tn.tagargs)] = base_tpl
+
+ return nil
+}
+
+func tagExtends(args *string, execCtx *executionContext, ctx *Context) (*string, error) {
+ // Extends executes the base template and passes the blocks via Context
+
+ // Example: {% extends "base.html" abc=<expr> ghi=<expr> ... %}
+ var base_tpl *Template
+ _base_tpl, has_precached := execCtx.template.cache[fmt.Sprintf("extends_%s", *args)]
+ if has_precached {
+ base_tpl = _base_tpl.(*Template)
+ } else {
+ // Get dynamic
+ _base_tpl, err := createBaseTplForExtendInclude(*args, execCtx.template, ctx)
+ if err != nil {
+ return nil, err
+ }
+ base_tpl = _base_tpl
+ }
+
// Execute every 'block' and store it's result as "block_%s" in the internal Context
for {
node, err := execCtx.ignoreUntilAnyTagNode("block")
@@ -747,38 +792,38 @@ func tagExtends(args *string, execCtx *executionContext, ctx *Context) (*string,
return base_tpl.execute(ctx, newExecutionContext(base_tpl, &execCtx.internal_context))
}
-func tagInclude(args *string, execCtx *executionContext, ctx *Context) (*string, error) {
- // Includes a template and executes it
-
- // Example: {% include "base.html" abc=<expr> ghi=<expr> ... %}
- _args := strings.Split(*args, " ")
- if len(_args) <= 0 {
- return nil, errors.New("Please provide at least a filename to extend from.")
- }
- e, err := newExpr(&_args[0])
- if err != nil {
- return nil, err
+func tagIncludePrepare(tn *tagNode, tpl *Template) error {
+ // Only prepare, if args starts with "static "
+ if !strings.HasPrefix(tn.tagargs, "static ") {
+ return nil
}
- name, err := e.evalString(ctx)
+
+ // In preparation-phase we have no Context, so create an empty one.
+ base_tpl, err := createBaseTplForExtendInclude(tn.tagargs, tpl, &Context{})
if err != nil {
- return nil, err
+ return err
}
- //raw_context := _args[1:] // TODO
- // Create new template
- if execCtx.template.locator == nil {
- panic(fmt.Sprintf("Please provide a template locator to lookup template '%v'.", *name))
- }
+ // Save base_tpl
+ tpl.cache[fmt.Sprintf("include_%s", tn.tagargs)] = base_tpl
- base_tpl_content, err := execCtx.template.locator(name)
- if err != nil {
- return nil, err
- }
+ return nil
+}
- // TODO: Do the pre-rendering (FromString) in the parent's FromString(), just do the execution here.
- base_tpl, err := FromString(*name, base_tpl_content, execCtx.template.locator)
- if err != nil {
- return nil, err
+func tagInclude(args *string, execCtx *executionContext, ctx *Context) (*string, error) {
+ // Includes a template and executes it
+
+ var base_tpl *Template
+ _base_tpl, has_precached := execCtx.template.cache[fmt.Sprintf("include_%s", *args)]
+ if has_precached {
+ base_tpl = _base_tpl.(*Template)
+ } else {
+ // Get dynamic
+ _base_tpl, err := createBaseTplForExtendInclude(*args, execCtx.template, ctx)
+ if err != nil {
+ return nil, err
+ }
+ base_tpl = _base_tpl
}
return base_tpl.Execute(ctx)
View
@@ -80,6 +80,9 @@ type Template struct {
autosafe bool
nodes []node
locator templateLocator
+
+ // Static content (doesn't change with execution)
+ cache map[string]interface{}
}
type stateFunc func(*Template) stateFunc
@@ -328,6 +331,14 @@ func addTagNode(tpl *Template) error {
tpl.start = tpl.pos
tpl.length = 0
tpl.nodes = append(tpl.nodes, tn)
+
+ if tn.taghandler != nil && tn.taghandler.Prepare != nil {
+ // OK, let's prepare this tag (e. g. pre-cache templates to extend)
+ if err := tn.taghandler.Prepare(tn, tpl); err != nil {
+ return errors.New(fmt.Sprintf("Error during preparation of tag '%s': %s", tagname, err))
+ }
+ }
+
return nil
}
@@ -449,6 +460,7 @@ func newTemplate(name string, tplstr *string, locator templateLocator) (*Templat
nodes: make([]node, 0, 250),
autosafe: true,
locator: locator,
+ cache: make(map[string]interface{}),
}
return tpl, nil
@@ -0,0 +1,4 @@
+{% extends static "generic/base1.html" %}
+
+{% block title %}My index{% endblock %}
+
@@ -0,0 +1,4 @@
+{% extends static "generic/base1_nono.html" %}
+
+{% block title %}My index{% endblock %}
+
View
@@ -346,15 +346,30 @@ var tags_tests = []test{
// Block/Extends
{"{% extends \"base\" %} This doesn't show up {% block name %}Florian{% endblock %}", "Hello Florian!", nil, ""},
- {"{% extends tpl_name %} This doesn't show up {% block name %}Florian{% endblock %}", "Hello Florian!", Context{"tpl_name": "base"}, ""},
+ {"{% extends foobar %} This doesn't show up {% block name %}Florian{% endblock %}", "Hello Florian!", Context{"foobar": "base"}, ""},
+ {"{% extends foobar %} This doesn't show up {% block name %}Florian{% endblock %}", "Hello Florian!", nil, "Please provide a propper template filename"},
{"{% extends \"base\" %} This doesn't show up", "Hello Josh!", nil, ""},
{"{% extends \"base2\" %} This doesn't show up {% block name %}Florian{% endblock %}", "", nil, "Could not find the template"},
+ // Static extend (template will be pre-cached at startup and not dynamically rendered)
+ // This improves speed significantly
+ {"{% extends static \"base\" %} This doesn't show up {% block name %}Florian{% endblock %}", "Hello Florian!", nil, ""},
+ {"{% extends static foobar %} This doesn't show up {% block name %}Florian{% endblock %}", "Hello Florian!", nil, "Please provide a propper template filename"},
+ {"{% extends static \"base\" %} This doesn't show up", "Hello Josh!", nil, ""},
+ {"{% extends static \"base2\" %} This doesn't show up {% block name %}Florian{% endblock %}", "", nil, "Could not find the template"},
+
// Include
{"{% include \"greetings\" %} How are you today?", "Hello Flo! How are you today?", Context{"name": "flo"}, ""},
{"{% include tpl_name %} How are you today?", "Hello Flo! How are you today?", Context{"name": "flo", "tpl_name": "greetings"}, ""},
{"{% include \"foobar\" %} This and that", "", nil, "Could not find the template"},
{"{% include \"greetings_with_errors\" %} This and that", "", nil, "[Parsing error: greetings_with_errors] [Line 1, Column 27] Filter 'notexistent' not found"},
+
+ // Static include (see comments for static extend above)
+ {"{% include static \"greetings\" %} How are you today?", "Hello Flo! How are you today?", Context{"name": "flo"}, ""},
+ {"{% include static tpl_name %} How are you today?", "Hello Flo! How are you today?", Context{"name": "flo", "tpl_name": "greetings"}, "Please provide a propper template filename"},
+ {"{% include static tpl_name %} How are you today?", "Hello Flo! How are you today?", nil, "Please provide a propper template filename"},
+ {"{% include static \"foobar\" %} This and that", "", nil, "Could not find the template"},
+ {"{% include static \"greetings_with_errors\" %} This and that", "", nil, "[Parsing error: greetings_with_errors] [Line 1, Column 27] Filter 'notexistent' not found"},
// Custom tag..
// TODO
@@ -370,7 +385,11 @@ var file_tests = []test{
// General
{"template_examples/index1.html", "", Context{"basename": "generic/base-notexistent.html"}, "Could not find the template"},
{"template_examples/index1.html", "<html><head><title>Myindex</title></head><body></body></html>", Context{"basename": "generic/base1.html"}, ""},
- {"template_examples/index1.html", "<html><head><title>Myindex</title></head><body></body></html>", nil, "Could not find the template"},
+ {"template_examples/index1.html", "<html><head><title>Myindex</title></head><body></body></html>", nil, "Please provide a propper template filename"},
+
+ // Static template caching
+ {"template_examples/index2.html", "<html><head><title>Myindex</title></head><body></body></html>", nil, ""},
+ {"template_examples/index3.html", "", nil, "Could not find the template"},
}
var base1 = "Hello {% block name %}Josh{% endblock %}!"
@@ -480,7 +499,16 @@ func TestFromFile(t *testing.T) {
tpl, err := FromFile(name, nil)
if err != nil {
- t.Fatalf("Template test file not found: %s", name)
+ if test.err != "" {
+ if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(test.err)) {
+ // Err found which is expected
+ continue
+ }
+ t.Errorf("File-Test '%s' FAILED (was expecting '%s' in error msg): %v", test, test.err, err)
+ continue
+ }
+ t.Errorf("File-Test '%s' FAILED: %v", test, err)
+ continue
}
var out *string
if test.ctx != nil {
@@ -494,18 +522,18 @@ func TestFromFile(t *testing.T) {
// Err found which is expected
continue
}
- t.Errorf("FileTest '%s' FAILED (was expecting '%s' in error msg): %v", test.tpl, test.err, err)
+ t.Errorf("File-Test '%s' FAILED (was expecting '%s' in error msg): %v", test, test.err, err)
continue
}
- t.Errorf("File-Test '%s' FAILED: %v", test.tpl, err)
+ t.Errorf("File-Test '%s' FAILED: %v", test, err)
continue
}
if test.err != "" {
- t.Errorf("File-Test '%s' SUCCEEDED, but FAIL ('%s' in error msg) was EXPECTED; got output: '%s'", test.tpl, test.err, *out)
+ t.Errorf("File-Test '%s' SUCCEEDED, but FAIL ('%s' in error msg) was EXPECTED; got output: '%s'", test, test.err, *out)
continue
}
if *out != test.output {
- t.Errorf("File-Test '%s' FAILED; got='%s' should='%s'", test.tpl, *out, test.output)
+ t.Errorf("File-Test '%s' FAILED; got='%s' should='%s'", test, *out, test.output)
continue
}
}

0 comments on commit f2e4a78

Please sign in to comment.