Skip to content

Commit

Permalink
Merge pull request #74 from jamesog/strict-variables
Browse files Browse the repository at this point in the history
Add support for strict variables
  • Loading branch information
danog authored Aug 23, 2023
2 parents ed3b1b5 + decc5e5 commit 0bded6a
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 9 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ These features of Shopify Liquid aren't implemented:
}}`. [[Issue #42](https://github.com/osteele/liquid/issues/42)]
- Warn and lax [error modes](https://github.com/shopify/liquid#error-modes).
- Non-strict filters. An undefined filter is currently an error.
- Strict variables. An undefined variable is not an error.

### Drops

Expand Down
20 changes: 13 additions & 7 deletions cmd/liquid/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ import (

// for testing
var (
stderr io.Writer = os.Stderr
stdout io.Writer = os.Stdout
stdin io.Reader = os.Stdin
exit func(int) = os.Exit
env func() []string = os.Environ
bindings map[string]interface{} = map[string]interface{}{}
stderr io.Writer = os.Stderr
stdout io.Writer = os.Stdout
stdin io.Reader = os.Stdin
exit func(int) = os.Exit
env func() []string = os.Environ
bindings map[string]interface{} = map[string]interface{}{}
strictVars bool
)

func main() {
Expand All @@ -41,6 +42,7 @@ func main() {

var bindEnvs bool
cmdLine.BoolVar(&bindEnvs, "env", false, "bind environment variables")
cmdLine.BoolVar(&strictVars, "strict", false, "enable strict variable mode in templates")

err = cmdLine.Parse(os.Args[1:])
if err != nil {
Expand Down Expand Up @@ -86,7 +88,11 @@ func render() error {
return err
}

tpl, err := liquid.NewEngine().ParseTemplate(buf)
e := liquid.NewEngine()
if strictVars {
e.StrictVariables()
}
tpl, err := e.ParseTemplate(buf)
if err != nil {
return err
}
Expand Down
12 changes: 12 additions & 0 deletions cmd/liquid/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func TestMain(t *testing.T) {
main()
require.True(t, envCalled)
require.Equal(t, "Hello, World!", buf.String())
bindings = make(map[string]interface{})

// filename
stdin = os.Stdin
Expand All @@ -72,6 +73,17 @@ func TestMain(t *testing.T) {
exitCode := 0
exit = func(n int) { exitCalled = true; exitCode = n }

// strict variables
stdin = bytes.NewBufferString(src)
buf = &bytes.Buffer{}
stderr = buf
os.Args = []string{"liquid", "--strict"}
main()
require.True(t, exitCalled)
require.Equal(t, 1, exitCode)
require.Equal(t, "Liquid error: undefined variable in {{ TARGET }}\n", buf.String())

exitCode = 0
os.Args = []string{"liquid", "testdata/source.liquid"}
main()
require.Equal(t, 0, exitCode)
Expand Down
5 changes: 5 additions & 0 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ func (e *Engine) RegisterTag(name string, td Renderer) {
})
}

// StrictVariables causes the renderer to error when the template contains an undefined variable.
func (e *Engine) StrictVariables() {
e.cfg.StrictVariables = true
}

// ParseTemplate creates a new Template using the engine configuration.
func (e *Engine) ParseTemplate(source []byte) (*Template, SourceError) {
return newTemplate(&e.cfg, source, "", 0)
Expand Down
3 changes: 2 additions & 1 deletion render/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (
type Config struct {
parser.Config
grammar
Cache map[string][]byte
Cache map[string][]byte
StrictVariables bool
}

type grammar struct {
Expand Down
4 changes: 4 additions & 0 deletions render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package render

import (
"errors"
"fmt"
"io"
"reflect"
Expand Down Expand Up @@ -66,6 +67,9 @@ func (n *ObjectNode) render(w *trimWriter, ctx nodeContext) Error {
if err != nil {
return wrapRenderError(err, n)
}
if value == nil && ctx.config.StrictVariables {
return wrapRenderError(errors.New("undefined variable"), n)
}
if err := wrapRenderError(writeObject(w, value), n); err != nil {
return err
}
Expand Down
37 changes: 37 additions & 0 deletions render/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,23 @@ var renderTests = []struct{ in, out string }{
{`x {%- y -%} z`, "xyz"},
}

var renderStrictTests = []struct{ in, out string }{
// literal representations
{`{{ true }}`, "true"},
{`{{ false }}`, "false"},
{`{{ 12 }}`, "12"},
{`{{ 12.3 }}`, "12.3"},
{`{{ date }}`, "2015-07-17 15:04:05 +0000"},
{`{{ "string" }}`, "string"},
{`{{ array }}`, "firstsecondthird"},

// variables and properties
{`{{ int }}`, "123"},
{`{{ page.title }}`, "Introduction"},
{`{{ array[1] }}`, "second"},
{`{{ invalid }}`, ""},
}

var renderErrorTests = []struct{ in, out string }{
{`{% errblock %}{% enderrblock %}`, "errblock error"},
}
Expand Down Expand Up @@ -111,6 +128,26 @@ func TestRenderErrors(t *testing.T) {
}
}

func TestRenderStrictVariables(t *testing.T) {
cfg := NewConfig()
cfg.StrictVariables = true
addRenderTestTags(cfg)
for i, test := range renderStrictTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
root, err := cfg.Compile(test.in, parser.SourceLoc{})
require.NoErrorf(t, err, test.in)
buf := new(bytes.Buffer)
err = Render(root, buf, renderTestBindings, cfg)
if test.in == `{{ invalid }}` {
require.Errorf(t, err, test.in)
} else {
require.NoErrorf(t, err, test.in)
}
require.Equalf(t, test.out, buf.String(), test.in)
})
}
}

func addRenderTestTags(cfg Config) {
cfg.AddTag("y", func(string) (func(io.Writer, Context) error, error) {
return func(w io.Writer, _ Context) error {
Expand Down

0 comments on commit 0bded6a

Please sign in to comment.