diff --git a/README.md b/README.md index 0e58539..7061648 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cmd/liquid/main.go b/cmd/liquid/main.go index 74ec7ae..2151290 100644 --- a/cmd/liquid/main.go +++ b/cmd/liquid/main.go @@ -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() { @@ -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 { @@ -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 } diff --git a/cmd/liquid/main_test.go b/cmd/liquid/main_test.go index aba0753..565a697 100644 --- a/cmd/liquid/main_test.go +++ b/cmd/liquid/main_test.go @@ -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 @@ -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) diff --git a/engine.go b/engine.go index bcc1295..69f38f4 100644 --- a/engine.go +++ b/engine.go @@ -66,6 +66,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) diff --git a/render/config.go b/render/config.go index 65ec55b..afc8f51 100644 --- a/render/config.go +++ b/render/config.go @@ -8,7 +8,8 @@ import ( type Config struct { parser.Config grammar - Cache map[string][]byte + Cache map[string][]byte + StrictVariables bool } type grammar struct { diff --git a/render/render.go b/render/render.go index 3e8b812..c072eb4 100644 --- a/render/render.go +++ b/render/render.go @@ -2,6 +2,7 @@ package render import ( + "errors" "fmt" "io" "reflect" @@ -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 } diff --git a/render/render_test.go b/render/render_test.go index db8d334..57b1b62 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -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"}, } @@ -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 {