Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement template loader #107

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ generator.
- [Status](#status)
- [Drops](#drops)
- [Value Types](#value-types)
- [Template Store](#template-store)
- [References](#references)
- [Contributing](#contributing)
- [Contributors](#contributors)
Expand Down Expand Up @@ -152,6 +153,25 @@ Any Go value can be used as a variable value. These values have special meaning:
- An instance of `yaml.MapSlice` acts as a map. It implements `m.key`,
`m[key]`, and `m.size`.

### Template Store

The template store allows for usage of varying template storage implementations (embedded file system, database, service, etc). In order to use:

1. Create a struct that implements ITemplateStore
```go
type ITemplateStore interface {
ReadTemplate(templatename string) ([]byte, error)
}
```
1. Register with the engine
```go
engine.RegisterTemplateStore()
```

`FileTemplateStore` is the default mechanism for backwards compatability.

Refer to [example](./docs/TemplateStoreExample.md) for an example implementation.

### References

- [Shopify.github.io/liquid](https://shopify.github.io/liquid)
Expand Down
54 changes: 54 additions & 0 deletions docs/TemplateStoreExample.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Template Store Example

This document describes the implementation of an `ITemplateStore` that uses an embedded file system as its storage type.

Add a go file to your project with configuration properties and the ReadTemplate() implementation

```go
package your_package_name

import (
"embed"
"fmt"
)

type EmbeddedFileSystemTemplateStore struct {
Folder embed.FS
RootDir string
}

// implementation of ITemplateProvider
func (tl *EmbeddedFileSystemTemplateStore) ReadTemplate(filename string) ([]byte, error) {

fileName := fmt.Sprintf("%v/%v", tl.RootDir, filename)
templateFile, _ := tl.Folder.ReadFile(fileName)

return templateFile, nil
}

```
initialize your embedded folder. for details on go embedded package see [embed](https://pkg.go.dev/embed)

```go

//go:embed all:templates
var folder embed.FS

```
create store and register with engine

```go
// use the embedded file system loader for now.
embedFileSystemTemplateStore := &your_package_name.EmbeddedFileSystemTemplateStore{
Folder: folder,
RootDir: "templates",
}

//create engine
engine := liquid.NewEngine()

//register with the engine
engine.RegisterTemplateStore(embedFileSystemTemplateStore)

//ready to go
```
4 changes: 4 additions & 0 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ func (e *Engine) RegisterTag(name string, td Renderer) {
})
}

func (e *Engine) RegisterTemplateStore(templateStore render.ITemplateStore) {
e.cfg.TemplateStore = templateStore
}

// StrictVariables causes the renderer to error when the template contains an undefined variable.
func (e *Engine) StrictVariables() {
e.cfg.StrictVariables = true
Expand Down
22 changes: 22 additions & 0 deletions engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package liquid
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
Expand Down Expand Up @@ -155,3 +156,24 @@ func TestEngine_ParseTemplateAndCache(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "Foo, Bar", string(result))
}

type MockTemplateStore struct{}

func (tl *MockTemplateStore) ReadTemplate(filename string) ([]byte, error) {
template := []byte(fmt.Sprintf("Message Text: {{ message.Text }} from: %v.", filename))
return template, nil
}

func Test_template_store(t *testing.T) {
template := []byte(`{% include "template.liquid" %}`)
mockstore := &MockTemplateStore{}
params := map[string]any{
"message": testStruct{
Text: "filename",
},
}
engine := NewEngine()
engine.RegisterTemplateStore(mockstore)
out, _ := engine.ParseAndRenderString(string(template), params)
require.Equal(t, "Message Text: filename from: template.liquid.", out)
}
9 changes: 8 additions & 1 deletion render/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Config struct {
grammar
Cache map[string][]byte
StrictVariables bool
TemplateStore ITemplateStore
}

type grammar struct {
Expand All @@ -18,10 +19,16 @@ type grammar struct {
}

// NewConfig creates a new Settings.
// TemplateStore is initialized to a FileTemplateStore for backwards compatibility
func NewConfig() Config {
g := grammar{
tags: map[string]TagCompiler{},
blockDefs: map[string]*blockSyntax{},
}
return Config{Config: parser.NewConfig(g), grammar: g, Cache: map[string][]byte{}}
return Config{
Config: parser.NewConfig(g),
grammar: g,
Cache: map[string][]byte{},
TemplateStore: &FileTemplateStore{},
}
}
6 changes: 5 additions & 1 deletion render/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type Context interface {
WrapError(err error) Error
}

type ITemplateStore interface {
ReadTemplate(templatename string) ([]byte, error)
}

type rendererContext struct {
ctx nodeContext
node *TagNode
Expand Down Expand Up @@ -145,7 +149,7 @@ func (c rendererContext) RenderChildren(w io.Writer) Error {
}

func (c rendererContext) RenderFile(filename string, b map[string]any) (string, error) {
source, err := os.ReadFile(filename)
source, err := c.ctx.config.TemplateStore.ReadTemplate(filename)
if err != nil && os.IsNotExist(err) {
// Is it cached?
if cval, ok := c.ctx.config.Cache[filename]; ok {
Expand Down
12 changes: 12 additions & 0 deletions render/file_template_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package render

import (
"os"
)

type FileTemplateStore struct{}

func (tl *FileTemplateStore) ReadTemplate(filename string) ([]byte, error) {
source, err := os.ReadFile(filename)
return source, err
}
7 changes: 4 additions & 3 deletions values/drop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,21 @@ func TestDrop_Resolve_race(t *testing.T) {

func BenchmarkDrop_Resolve_1(b *testing.B) {
d := ValueOf(testDrop{1})
for n := 0; n < b.N; n++ {

for range b.N {
_ = d.Int()
}
}

func BenchmarkDrop_Resolve_2(b *testing.B) {
for n := 0; n < b.N; n++ {
for range b.N {
d := ValueOf(testDrop{1})
_ = d.Int()
}
}

func BenchmarkDrop_Resolve_3(b *testing.B) {
for n := 0; n < b.N; n++ {
for range b.N {
d := ValueOf(testDrop{1})
values := make(chan int, 10)
for i := cap(values); i > 0; i-- {
Expand Down