diff --git a/engine.go b/engine.go index d143364..bcc1295 100644 --- a/engine.go +++ b/engine.go @@ -110,3 +110,19 @@ func (e *Engine) Delims(objectLeft, objectRight, tagLeft, tagRight string) *Engi e.cfg.Delims = []string{objectLeft, objectRight, tagLeft, tagRight} return e } + +// ParseTemplateAndCache is the same as ParseTemplateLocation, except that the +// source location is used for error reporting and for the {% include %} tag. +// If parsing is successful, provided source is then cached, and can be retrieved +// by {% include %} tags, as long as there is not a real file in the provided path. +// +// The path and line number are used for error reporting. +// The path is also the reference for relative pathnames in the {% include %} tag. +func (e *Engine) ParseTemplateAndCache(source []byte, path string, line int) (*Template, SourceError) { + t, err := e.ParseTemplateLocation(source, path, line) + if err != nil { + return t, err + } + e.cfg.Cache[path] = source + return t, err +} diff --git a/engine_test.go b/engine_test.go index 4a56258..c3a6971 100644 --- a/engine_test.go +++ b/engine_test.go @@ -92,3 +92,19 @@ func BenchmarkEngine_Parse(b *testing.B) { engine.ParseTemplate(s) } } + +func TestEngine_ParseTemplateAndCache(t *testing.T) { + // Given two templates... + templateA := []byte("Foo") + templateB := []byte(`{% include "template_a.html" %}, Bar`) + + // Cache the first + eng := NewEngine() + _, err := eng.ParseTemplateAndCache(templateA, "template_a.html", 1) + require.NoError(t, err) + + // ...and execute the second. + result, err := eng.ParseAndRender(templateB, Bindings{}) + require.NoError(t, err) + require.Equal(t, string(result), "Foo, Bar") +} diff --git a/render/config.go b/render/config.go index ab66bf2..65ec55b 100644 --- a/render/config.go +++ b/render/config.go @@ -8,6 +8,7 @@ import ( type Config struct { parser.Config grammar + Cache map[string][]byte } type grammar struct { @@ -21,5 +22,5 @@ func NewConfig() Config { tags: map[string]TagCompiler{}, blockDefs: map[string]*blockSyntax{}, } - return Config{parser.NewConfig(g), g} + return Config{Config: parser.NewConfig(g), grammar: g, Cache: map[string][]byte{}} } diff --git a/render/context.go b/render/context.go index 0931924..bae220a 100644 --- a/render/context.go +++ b/render/context.go @@ -4,6 +4,7 @@ import ( "bytes" "io" "io/ioutil" + "os" "strings" "github.com/osteele/liquid/expressions" @@ -111,7 +112,14 @@ func (c rendererContext) RenderChildren(w io.Writer) Error { func (c rendererContext) RenderFile(filename string, b map[string]interface{}) (string, error) { source, err := ioutil.ReadFile(filename) - if err != nil { + if err != nil && os.IsNotExist(err) { + // Is it cached? + if cval, ok := c.ctx.config.Cache[filename]; ok { + source = cval + } else { + return "", err + } + } else if err != nil { return "", err } root, err := c.ctx.config.Compile(string(source), c.node.SourceLoc) diff --git a/tags/include_tag_test.go b/tags/include_tag_test.go index 52647ab..cb329e8 100644 --- a/tags/include_tag_test.go +++ b/tags/include_tag_test.go @@ -58,3 +58,18 @@ func TestIncludeTag_file_not_found_error(t *testing.T) { require.Error(t, err) require.True(t, os.IsNotExist(err.Cause())) } + +func TestIncludeTag_cached_value_handling(t *testing.T) { + config := render.NewConfig() + // foo.html does not exist on testdata. + config.Cache["testdata/foo.html"] = []byte("bar") + loc := parser.SourceLoc{Pathname: "testdata/include_source.html", LineNo: 1} + AddStandardTags(config) + + root, err := config.Compile(`{% include "foo.html" %}`, loc) + require.NoError(t, err) + buf := new(bytes.Buffer) + err = render.Render(root, buf, includeTestBindings, config) + require.NoError(t, err) + require.Equal(t, "bar", strings.TrimSpace(buf.String())) +}