From 903acb8d2edc5472097fb84629e5499ff8454f95 Mon Sep 17 00:00:00 2001 From: Oliver Steele Date: Fri, 7 Jul 2017 05:41:37 -0400 Subject: [PATCH] Split package render->parser --- README.md | 20 ++++-- engine.go | 4 +- evaluator/call.go | 2 - expression/filters.go | 3 + expression/filters_test.go | 4 +- filters/filter_test.go | 2 +- filters/filters.go | 84 +++++++++++++------------- liquid.go | 7 ++- liquid_test.go | 2 - {render => parser}/ast.go | 6 +- {render => parser}/chunk.go | 2 +- {render => parser}/chunktype_string.go | 2 +- parser/config.go | 15 +++++ {render => parser}/grammar.go | 4 +- {render => parser}/parser.go | 8 +-- {render => parser}/parser_test.go | 40 +++++++----- {render => parser}/scanner.go | 2 +- {render => parser}/scanner_test.go | 2 +- render/blocks.go | 6 +- render/compiler.go | 26 ++++---- render/config.go | 20 +++--- render/context.go | 2 +- render/node_context.go | 7 ++- render/nodes.go | 9 +-- render/render.go | 13 ++-- template.go | 8 +-- 26 files changed, 174 insertions(+), 126 deletions(-) rename {render => parser}/ast.go (92%) rename {render => parser}/chunk.go (98%) rename {render => parser}/chunktype_string.go (96%) create mode 100644 parser/config.go rename {render => parser}/grammar.go (87%) rename {render => parser}/parser.go (95%) rename {render => parser}/parser_test.go (53%) rename {render => parser}/scanner.go (98%) rename {render => parser}/scanner_test.go (99%) diff --git a/README.md b/README.md index 0bbf0d5..06be842 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # Go Liquid Template Parser -[![Build Status](https://travis-ci.org/osteele/liquid.svg?branch=master)](https://travis-ci.org/osteele/liquid) -[![Go Report Card](https://goreportcard.com/badge/github.com/osteele/liquid)](https://goreportcard.com/report/github.com/osteele/liquid) -[![GoDoc](https://godoc.org/github.com/osteele/liquid?status.svg)](http://godoc.org/github.com/osteele/liquid) -[![Coverage Status](https://coveralls.io/repos/github/osteele/liquid/badge.svg?branch=master)](https://coveralls.io/github/osteele/liquid?branch=master) + [![][travis-svg]][travis-url] [![][coveralls-svg]][coveralls-url] [![][go-report-card-svg]][go-report-card-url] [![][godoc-svg]][godoc-url] [![][license-svg]][license-url] > “Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.” – Philip Greenspun @@ -127,3 +124,18 @@ The [original Liquid engine](https://shopify.github.io/liquid), of course, for t ## License MIT License + +[coveralls-url]: https://coveralls.io/r/osteele/liquid?branch=master +[coveralls-svg]: https://img.shields.io/coveralls/osteele/liquid.svg?branch=master + +[godoc-url]: https://godoc.org/github.com/osteele/liquid +[godoc-svg]: https://godoc.org/github.com/osteele/liquid?status.svg + +[license-url]: https://github.com/osteele/liquid/blob/master/LICENSE +[license-svg]: https://img.shields.io/badge/license-MIT-blue.svg + +[go-report-card-url]: https://goreportcard.com/report/github.com/osteele/liquid +[go-report-card-svg]: https://goreportcard.com/badge/github.com/osteele/liquid + +[travis-url]: https://travis-ci.org/osteele/liquid +[travis-svg]: https://img.shields.io/travis/osteele/liquid.svg?branch=master diff --git a/engine.go b/engine.go index 1665328..62cd70f 100644 --- a/engine.go +++ b/engine.go @@ -13,7 +13,7 @@ type engine struct{ settings render.Config } // NewEngine returns a new template engine. func NewEngine() Engine { e := engine{render.NewConfig()} - filters.AddStandardFilters(e.settings.Config) + filters.AddStandardFilters(&e.settings.Config.Config) tags.AddStandardTags(e.settings) return e } @@ -57,7 +57,7 @@ func (e engine) ParseTemplate(text []byte) (Template, error) { if err != nil { return nil, err } - return &template{ast, e.settings}, nil + return &template{ast, &e.settings}, nil } // ParseAndRender is in the Engine interface. diff --git a/evaluator/call.go b/evaluator/call.go index 68cd073..aaf05db 100644 --- a/evaluator/call.go +++ b/evaluator/call.go @@ -1,7 +1,6 @@ package evaluator import ( - "fmt" "reflect" ) @@ -15,7 +14,6 @@ func Call(fn reflect.Value, args []interface{}) (interface{}, error) { if len(outs) > 1 && outs[1].Interface() != nil { switch e := outs[1].Interface().(type) { case error: - fmt.Println("error") return nil, e default: panic(e) diff --git a/expression/filters.go b/expression/filters.go index de2fdba..965a87b 100644 --- a/expression/filters.go +++ b/expression/filters.go @@ -44,6 +44,9 @@ func (d *filterDictionary) AddFilter(name string, fn interface{}) { // case rf.Type().Out(1).Implements(…): // panic(typeError("a filter's second output must be type error")) } + if len(d.filters) == 0 { + d.filters = make(map[string]interface{}) + } d.filters[name] = fn } diff --git a/expression/filters_test.go b/expression/filters_test.go index b48ab1c..6814142 100644 --- a/expression/filters_test.go +++ b/expression/filters_test.go @@ -9,7 +9,6 @@ import ( func TestContext_runFilter(t *testing.T) { cfg := NewConfig() - ctx := NewContext(map[string]interface{}{"x": 10}, cfg) constant := func(value interface{}) valueFn { return func(Context) interface{} { return value } } @@ -19,6 +18,7 @@ func TestContext_runFilter(t *testing.T) { cfg.AddFilter("f1", func(s string) string { return "<" + s + ">" }) + ctx := NewContext(map[string]interface{}{"x": 10}, cfg) out := ctx.ApplyFilter("f1", receiver, []valueFn{}) require.Equal(t, "", out) @@ -26,6 +26,7 @@ func TestContext_runFilter(t *testing.T) { cfg.AddFilter("with_arg", func(a, b string) string { return fmt.Sprintf("(%s, %s)", a, b) }) + ctx = NewContext(map[string]interface{}{"x": 10}, cfg) out = ctx.ApplyFilter("with_arg", receiver, []valueFn{constant("arg")}) require.Equal(t, "(self, arg)", out) @@ -43,6 +44,7 @@ func TestContext_runFilter(t *testing.T) { } return fmt.Sprintf("(%v, %v)", a, value), nil }) + ctx = NewContext(map[string]interface{}{"x": 10}, cfg) out = ctx.ApplyFilter("closure", receiver, []valueFn{constant("x |add: y")}) require.Equal(t, "(self, 11)", out) } diff --git a/filters/filter_test.go b/filters/filter_test.go index 642f8a0..1fd40cf 100644 --- a/filters/filter_test.go +++ b/filters/filter_test.go @@ -161,7 +161,7 @@ var filterTestBindings = map[string]interface{}{ func TestFilters(t *testing.T) { settings := expression.NewConfig() - AddStandardFilters(settings) + AddStandardFilters(&settings) context := expression.NewContext(filterTestBindings, settings) for i, test := range filterTests { diff --git a/filters/filters.go b/filters/filters.go index 7fc9937..049cb86 100644 --- a/filters/filters.go +++ b/filters/filters.go @@ -19,9 +19,9 @@ import ( ) // AddStandardFilters defines the standard Liquid filters. -func AddStandardFilters(settings expression.Config) { // nolint: gocyclo +func AddStandardFilters(cfg *expression.Config) { // nolint: gocyclo // values - settings.AddFilter("default", func(value, defaultValue interface{}) interface{} { + cfg.AddFilter("default", func(value, defaultValue interface{}) interface{} { if value == nil || value == false || evaluator.IsEmpty(value) { value = defaultValue } @@ -29,7 +29,7 @@ func AddStandardFilters(settings expression.Config) { // nolint: gocyclo }) // dates - settings.AddFilter("date", func(t time.Time, format interface{}) (string, error) { + cfg.AddFilter("date", func(t time.Time, format interface{}) (string, error) { form, ok := format.(string) if !ok { form = "%a, %b %d, %y" @@ -40,7 +40,7 @@ func AddStandardFilters(settings expression.Config) { // nolint: gocyclo }) // arrays - settings.AddFilter("compact", func(array []interface{}) interface{} { + cfg.AddFilter("compact", func(array []interface{}) interface{} { out := []interface{}{} for _, item := range array { if item != nil { @@ -49,25 +49,25 @@ func AddStandardFilters(settings expression.Config) { // nolint: gocyclo } return out }) - settings.AddFilter("join", joinFilter) - settings.AddFilter("map", func(array []map[string]interface{}, key string) interface{} { + cfg.AddFilter("join", joinFilter) + cfg.AddFilter("map", func(array []map[string]interface{}, key string) interface{} { out := []interface{}{} for _, obj := range array { out = append(out, obj[key]) } return out }) - settings.AddFilter("reverse", reverseFilter) - settings.AddFilter("sort", sortFilter) + cfg.AddFilter("reverse", reverseFilter) + cfg.AddFilter("sort", sortFilter) // https://shopify.github.io/liquid/ does not demonstrate first and last as filters, // but https://help.shopify.com/themes/liquid/filters/array-filters does - settings.AddFilter("first", func(array []interface{}) interface{} { + cfg.AddFilter("first", func(array []interface{}) interface{} { if len(array) == 0 { return nil } return array[0] }) - settings.AddFilter("last", func(array []interface{}) interface{} { + cfg.AddFilter("last", func(array []interface{}) interface{} { if len(array) == 0 { return nil } @@ -75,20 +75,20 @@ func AddStandardFilters(settings expression.Config) { // nolint: gocyclo }) // numbers - settings.AddFilter("abs", math.Abs) - settings.AddFilter("ceil", math.Ceil) - settings.AddFilter("floor", math.Floor) - settings.AddFilter("modulo", math.Mod) - settings.AddFilter("minus", func(a, b float64) float64 { + cfg.AddFilter("abs", math.Abs) + cfg.AddFilter("ceil", math.Ceil) + cfg.AddFilter("floor", math.Floor) + cfg.AddFilter("modulo", math.Mod) + cfg.AddFilter("minus", func(a, b float64) float64 { return a - b }) - settings.AddFilter("plus", func(a, b float64) float64 { + cfg.AddFilter("plus", func(a, b float64) float64 { return a + b }) - settings.AddFilter("times", func(a, b float64) float64 { + cfg.AddFilter("times", func(a, b float64) float64 { return a * b }) - settings.AddFilter("divided_by", func(a float64, b interface{}) interface{} { + cfg.AddFilter("divided_by", func(a float64, b interface{}) interface{} { switch bt := b.(type) { case int, int16, int32, int64: return int(a) / bt.(int) @@ -98,7 +98,7 @@ func AddStandardFilters(settings expression.Config) { // nolint: gocyclo return nil } }) - settings.AddFilter("round", func(n float64, places interface{}) float64 { + cfg.AddFilter("round", func(n float64, places interface{}) float64 { pl, ok := places.(int) if !ok { pl = 0 @@ -108,45 +108,45 @@ func AddStandardFilters(settings expression.Config) { // nolint: gocyclo }) // sequences - settings.AddFilter("size", evaluator.Length) + cfg.AddFilter("size", evaluator.Length) // strings - settings.AddFilter("append", func(s, suffix string) string { + cfg.AddFilter("append", func(s, suffix string) string { return s + suffix }) - settings.AddFilter("capitalize", func(s, suffix string) string { + cfg.AddFilter("capitalize", func(s, suffix string) string { if len(s) < 1 { return s } return strings.ToUpper(s[:1]) + s[1:] }) - settings.AddFilter("downcase", func(s, suffix string) string { + cfg.AddFilter("downcase", func(s, suffix string) string { return strings.ToLower(s) }) - settings.AddFilter("escape", html.EscapeString) - settings.AddFilter("escape_once", func(s, suffix string) string { + cfg.AddFilter("escape", html.EscapeString) + cfg.AddFilter("escape_once", func(s, suffix string) string { return html.EscapeString(html.UnescapeString(s)) }) // TODO test case for this - settings.AddFilter("newline_to_br", func(s string) string { + cfg.AddFilter("newline_to_br", func(s string) string { return strings.Replace(s, "\n", "
", -1) }) - settings.AddFilter("prepend", func(s, prefix string) string { + cfg.AddFilter("prepend", func(s, prefix string) string { return prefix + s }) - settings.AddFilter("remove", func(s, old string) string { + cfg.AddFilter("remove", func(s, old string) string { return strings.Replace(s, old, "", -1) }) - settings.AddFilter("remove_first", func(s, old string) string { + cfg.AddFilter("remove_first", func(s, old string) string { return strings.Replace(s, old, "", 1) }) - settings.AddFilter("replace", func(s, old, new string) string { + cfg.AddFilter("replace", func(s, old, new string) string { return strings.Replace(s, old, new, -1) }) - settings.AddFilter("replace_first", func(s, old, new string) string { + cfg.AddFilter("replace_first", func(s, old, new string) string { return strings.Replace(s, old, new, 1) }) - settings.AddFilter("slice", func(s string, start int, length interface{}) string { + cfg.AddFilter("slice", func(s string, start int, length interface{}) string { // runes aren't bytes; don't use slice n, ok := length.(int) if !ok { @@ -158,23 +158,23 @@ func AddStandardFilters(settings expression.Config) { // nolint: gocyclo p := regexp.MustCompile(fmt.Sprintf(`^.{%d}(.{0,%d}).*$`, start, n)) return p.ReplaceAllString(s, "$1") }) - settings.AddFilter("split", splitFilter) - settings.AddFilter("strip_html", func(s string) string { + cfg.AddFilter("split", splitFilter) + cfg.AddFilter("strip_html", func(s string) string { // TODO this probably isn't sufficient return regexp.MustCompile(`<.*?>`).ReplaceAllString(s, "") }) // TODO test case for this - settings.AddFilter("strip_newlines", func(s string) string { + cfg.AddFilter("strip_newlines", func(s string) string { return strings.Replace(s, "\n", "", -1) }) - settings.AddFilter("strip", strings.TrimSpace) - settings.AddFilter("lstrip", func(s string) string { + cfg.AddFilter("strip", strings.TrimSpace) + cfg.AddFilter("lstrip", func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) }) - settings.AddFilter("rstrip", func(s string) string { + cfg.AddFilter("rstrip", func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) }) - settings.AddFilter("truncate", func(s string, n int, ellipsis interface{}) string { + cfg.AddFilter("truncate", func(s string, n int, ellipsis interface{}) string { // runes aren't bytes; don't use slice el, ok := ellipsis.(string) if !ok { @@ -183,20 +183,20 @@ func AddStandardFilters(settings expression.Config) { // nolint: gocyclo p := regexp.MustCompile(fmt.Sprintf(`^(.{%d})..{%d,}`, n-len(el), len(el))) return p.ReplaceAllString(s, `$1`+el) }) - settings.AddFilter("upcase", func(s, suffix string) string { + cfg.AddFilter("upcase", func(s, suffix string) string { return strings.ToUpper(s) }) // debugging extensions // inspect is from Jekyll - settings.AddFilter("inspect", func(value interface{}) string { + cfg.AddFilter("inspect", func(value interface{}) string { s, err := json.Marshal(value) if err != nil { return fmt.Sprintf("%#v", value) } return string(s) }) - settings.AddFilter("type", func(value interface{}) string { + cfg.AddFilter("type", func(value interface{}) string { return reflect.TypeOf(value).String() }) } diff --git a/liquid.go b/liquid.go index da6352b..0f55cdb 100644 --- a/liquid.go +++ b/liquid.go @@ -3,7 +3,7 @@ Package liquid is a pure Go implementation of Shopify Liquid templates, for use See the project README https://github.com/osteele/liquid for additional information and implementation status. -Note that the API for this package is not frozen. It is *especiallY* likely that subpackage APIs will +Note that the API for this package is not frozen. It is *especially* likely that subpackage APIs will change drastically. Don't use anything except from a subpackage except render.Context. */ package liquid @@ -11,6 +11,7 @@ package liquid import ( "github.com/osteele/liquid/evaluator" "github.com/osteele/liquid/expression" + "github.com/osteele/liquid/parser" "github.com/osteele/liquid/render" ) @@ -71,9 +72,9 @@ func IsTemplateError(err error) bool { return true case expression.ParseError: return true - case render.CompilationError: + case parser.ParseError: return true - case render.ParseError: + case render.CompilationError: return true case render.Error: return true diff --git a/liquid_test.go b/liquid_test.go index 104328f..ae9465d 100644 --- a/liquid_test.go +++ b/liquid_test.go @@ -1,7 +1,6 @@ package liquid import ( - "fmt" "testing" "github.com/stretchr/testify/require" @@ -9,7 +8,6 @@ import ( func TestIsTemplateError(t *testing.T) { _, err := NewEngine().ParseAndRenderString("{{ syntax error }}", emptyBindings) - fmt.Printf("%T", err) require.True(t, IsTemplateError(err)) _, err = NewEngine().ParseAndRenderString("{% if %}", emptyBindings) require.True(t, IsTemplateError(err)) diff --git a/render/ast.go b/parser/ast.go similarity index 92% rename from render/ast.go rename to parser/ast.go index 93ccbdf..512a076 100644 --- a/render/ast.go +++ b/parser/ast.go @@ -1,4 +1,4 @@ -package render +package parser import ( "github.com/osteele/liquid/expression" @@ -17,7 +17,7 @@ type ASTBlock struct { // ASTRaw holds the text between the start and end of a raw tag. type ASTRaw struct { - slices []string + Slices []string } // ASTTag is a tag. @@ -33,7 +33,7 @@ type ASTText struct { // ASTObject is an {{ object }} object. type ASTObject struct { Chunk - expr expression.Expression + Expr expression.Expression } // ASTSeq is a sequence of nodes. diff --git a/render/chunk.go b/parser/chunk.go similarity index 98% rename from render/chunk.go rename to parser/chunk.go index 994203b..566ff32 100644 --- a/render/chunk.go +++ b/parser/chunk.go @@ -1,4 +1,4 @@ -package render +package parser import "fmt" diff --git a/render/chunktype_string.go b/parser/chunktype_string.go similarity index 96% rename from render/chunktype_string.go rename to parser/chunktype_string.go index c26478d..a1d1165 100644 --- a/render/chunktype_string.go +++ b/parser/chunktype_string.go @@ -1,6 +1,6 @@ // Code generated by "stringer -type=ChunkType"; DO NOT EDIT. -package render +package parser import "fmt" diff --git a/parser/config.go b/parser/config.go new file mode 100644 index 0000000..706dbd2 --- /dev/null +++ b/parser/config.go @@ -0,0 +1,15 @@ +package parser + +import "github.com/osteele/liquid/expression" + +// // Config holds configuration information for parsing and rendering. +type Config struct { + expression.Config + // Filename string + Grammar Grammar +} + +// NewConfig creates a new Settings. +func NewConfig() Config { + return Config{Config: expression.NewConfig()} +} diff --git a/render/grammar.go b/parser/grammar.go similarity index 87% rename from render/grammar.go rename to parser/grammar.go index 7849508..e328a77 100644 --- a/render/grammar.go +++ b/parser/grammar.go @@ -1,4 +1,4 @@ -package render +package parser // Grammar supplies the parser with syntax information about blocks. type Grammar interface { @@ -18,4 +18,4 @@ type BlockSyntax interface { } // Grammar returns a configuration's grammar. -func (c *Config) Grammar() Grammar { return c } +// func (c *Config) Grammar() Grammar { return c } diff --git a/render/parser.go b/parser/parser.go similarity index 95% rename from render/parser.go rename to parser/parser.go index 715860b..31bb195 100644 --- a/render/parser.go +++ b/parser/parser.go @@ -1,4 +1,4 @@ -package render +package parser import ( "fmt" @@ -31,7 +31,7 @@ func (c Config) parseChunks(chunks []Chunk) (ASTNode, error) { // nolint: gocycl ap *[]ASTNode } var ( - g = c.Grammar() + g = c.Grammar root = &ASTSeq{} // root of AST; will be returned ap = &root.Children // newly-constructed nodes are appended here sd BlockSyntax // current block syntax definition @@ -54,7 +54,7 @@ func (c Config) parseChunks(chunks []Chunk) (ASTNode, error) { // nolint: gocycl if ch.Type == TagChunkType && ch.Name == "endraw" { inRaw = false } else { - rawTag.slices = append(rawTag.slices, ch.Source) + rawTag.Slices = append(rawTag.Slices, ch.Source) } case ch.Type == ObjChunkType: expr, err := expression.Parse(ch.Args) @@ -99,7 +99,7 @@ func (c Config) parseChunks(chunks []Chunk) (ASTNode, error) { // nolint: gocycl } pop() default: - panic("unexpected block type") + panic(fmt.Errorf("block type %q", ch.Name)) } } else { *ap = append(*ap, &ASTTag{ch}) diff --git a/render/parser_test.go b/parser/parser_test.go similarity index 53% rename from render/parser_test.go rename to parser/parser_test.go index 991876f..8854929 100644 --- a/render/parser_test.go +++ b/parser/parser_test.go @@ -1,21 +1,33 @@ -package render +package parser import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/require" ) -func addParserTestTags(s Config) { - s.AddBlock("case").Branch("when") - s.AddBlock("comment") - s.AddBlock("for").Governs([]string{"break"}) - s.AddBlock("if").Branch("else").Branch("elsif") - s.AddBlock("unless").SameSyntaxAs("if") - s.AddBlock("raw") +type grammarFake struct{} +type blockSyntaxFake string + +func (g grammarFake) BlockSyntax(w string) (BlockSyntax, bool) { + return blockSyntaxFake(w), true } +func (g blockSyntaxFake) IsBlock() bool { return true } +func (g blockSyntaxFake) CanHaveParent(p BlockSyntax) bool { + return string(g) == "end"+p.TagName() || (g == "else" && p.TagName() == "if") +} +func (g blockSyntaxFake) IsBlockEnd() bool { return strings.HasPrefix(string(g), "end") } +func (g blockSyntaxFake) IsBlockStart() bool { + return g == "for" || g == "if" || g == "unless" +} +func (g blockSyntaxFake) IsBranch() bool { return g == "else" } +func (g blockSyntaxFake) ParentTags() []string { return []string{"unless"} } +func (g blockSyntaxFake) RequiresParent() bool { return g == "else" || g.IsBlockEnd() } +func (g blockSyntaxFake) TagName() string { return string(g) } + var parseErrorTests = []struct{ in, expected string }{ {"{% if test %}", "unterminated if block"}, {"{% if test %}{% endunless %}", "not inside unless"}, @@ -28,7 +40,7 @@ var parserTests = []struct{ in string }{ {`{% for item in list %}{% endfor %}`}, {`{% if test %}{% else %}{% endif %}`}, {`{% if test %}{% if test %}{% endif %}{% endif %}`}, - {`{% unless test %}{% else %}{% endunless %}`}, + {`{% unless test %}{% endunless %}`}, {`{% for item in list %}{% if test %}{% else %}{% endif %}{% endfor %}`}, {`{% if true %}{% raw %}{% endraw %}{% endif %}`}, @@ -37,11 +49,10 @@ var parserTests = []struct{ in string }{ } func TestParseErrors(t *testing.T) { - settings := NewConfig() - addParserTestTags(settings) + cfg := Config{Grammar:grammarFake{}} for i, test := range parseErrorTests { t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) { - _, err := settings.Parse(test.in) + _, err := cfg.Parse(test.in) require.Errorf(t, err, test.in) require.Containsf(t, err.Error(), test.expected, test.in) }) @@ -49,11 +60,10 @@ func TestParseErrors(t *testing.T) { } func TestParser(t *testing.T) { - settings := NewConfig() - addParserTestTags(settings) + cfg := Config{Grammar:grammarFake{}} for i, test := range parserTests { t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) { - _, err := settings.Parse(test.in) + _, err := cfg.Parse(test.in) require.NoError(t, err, test.in) }) } diff --git a/render/scanner.go b/parser/scanner.go similarity index 98% rename from render/scanner.go rename to parser/scanner.go index 8474444..f5a6b2a 100644 --- a/render/scanner.go +++ b/parser/scanner.go @@ -1,4 +1,4 @@ -package render +package parser import ( "regexp" diff --git a/render/scanner_test.go b/parser/scanner_test.go similarity index 99% rename from render/scanner_test.go rename to parser/scanner_test.go index 5ae4d91..c9e8b1e 100644 --- a/render/scanner_test.go +++ b/parser/scanner_test.go @@ -1,4 +1,4 @@ -package render +package parser import ( "fmt" diff --git a/render/blocks.go b/render/blocks.go index 959c7ac..2fb4d9e 100644 --- a/render/blocks.go +++ b/render/blocks.go @@ -3,6 +3,8 @@ package render import ( "fmt" "io" + + "github.com/osteele/liquid/parser" ) // BlockCompiler builds a renderer for the tag instance. @@ -17,7 +19,7 @@ type blockSyntax struct { parser BlockCompiler } -func (c *blockSyntax) CanHaveParent(parent BlockSyntax) bool { +func (c *blockSyntax) CanHaveParent(parent parser.BlockSyntax) bool { if parent == nil { return false } @@ -52,7 +54,7 @@ func (c Config) findBlockDef(name string) (*blockSyntax, bool) { } // BlockSyntax is part of the Grammar interface. -func (c Config) BlockSyntax(name string) (BlockSyntax, bool) { +func (c Config) BlockSyntax(name string) (parser.BlockSyntax, bool) { ct, found := c.blockDefs[name] return ct, found } diff --git a/render/compiler.go b/render/compiler.go index 5890a2a..adfb46d 100644 --- a/render/compiler.go +++ b/render/compiler.go @@ -2,6 +2,8 @@ package render import ( "fmt" + + "github.com/osteele/liquid/parser" ) // A CompilationError is a parse error during template compilation. @@ -14,7 +16,7 @@ func compilationErrorf(format string, a ...interface{}) CompilationError { } // Compile parses a source template. It returns an AST root, that can be evaluated. -func (c Config) Compile(source string) (ASTNode, error) { +func (c Config) Compile(source string) (parser.ASTNode, error) { root, err := c.Parse(source) if err != nil { return nil, err @@ -23,9 +25,9 @@ func (c Config) Compile(source string) (ASTNode, error) { } // nolint: gocyclo -func (c Config) compileNode(n ASTNode) (Node, error) { +func (c Config) compileNode(n parser.ASTNode) (Node, error) { switch n := n.(type) { - case *ASTBlock: + case *parser.ASTBlock: body, err := c.compileNodes(n.Body) if err != nil { return nil, err @@ -52,15 +54,15 @@ func (c Config) compileNode(n ASTNode) (Node, error) { node.renderer = r } return &node, nil - case *ASTRaw: - return &RawNode{n.slices}, nil - case *ASTSeq: + case *parser.ASTRaw: + return &RawNode{n.Slices}, nil + case *parser.ASTSeq: children, err := c.compileNodes(n.Children) if err != nil { return nil, err } return &SeqNode{children}, nil - case *ASTTag: + case *parser.ASTTag: if td, ok := c.FindTagDefinition(n.Name); ok { f, err := td(n.Args) if err != nil { @@ -69,16 +71,16 @@ func (c Config) compileNode(n ASTNode) (Node, error) { return &TagNode{n.Chunk, f}, nil } return nil, compilationErrorf("unknown tag: %s", n.Name) - case *ASTText: + case *parser.ASTText: return &TextNode{n.Chunk}, nil - case *ASTObject: - return &ObjectNode{n.Chunk, n.expr}, nil + case *parser.ASTObject: + return &ObjectNode{n.Chunk, n.Expr}, nil default: panic(fmt.Errorf("un-compilable node type %T", n)) } } -func (c Config) compileBlocks(blocks []*ASTBlock) ([]*BlockNode, error) { +func (c Config) compileBlocks(blocks []*parser.ASTBlock) ([]*BlockNode, error) { out := make([]*BlockNode, 0, len(blocks)) for _, child := range blocks { compiled, err := c.compileNode(child) @@ -90,7 +92,7 @@ func (c Config) compileBlocks(blocks []*ASTBlock) ([]*BlockNode, error) { return out, nil } -func (c Config) compileNodes(nodes []ASTNode) ([]Node, error) { +func (c Config) compileNodes(nodes []parser.ASTNode) ([]Node, error) { out := make([]Node, 0, len(nodes)) for _, child := range nodes { compiled, err := c.compileNode(child) diff --git a/render/config.go b/render/config.go index 379f279..609e8de 100644 --- a/render/config.go +++ b/render/config.go @@ -1,11 +1,12 @@ package render -import "github.com/osteele/liquid/expression" +import ( + "github.com/osteele/liquid/parser" +) // Config holds configuration information for parsing and rendering. type Config struct { - // ExpressionConfig expression.Config - expression.Config + parser.Config Filename string tags map[string]TagCompiler blockDefs map[string]*blockSyntax @@ -13,15 +14,16 @@ type Config struct { // NewConfig creates a new Settings. func NewConfig() Config { - s := Config{ - Config: expression.NewConfig(), + c := Config{ + // Config: parser.NewConfig(), tags: map[string]TagCompiler{}, blockDefs: map[string]*blockSyntax{}, } - return s + c.Grammar = c + return c } // AddFilter adds a filter to settings. -func (s Config) AddFilter(name string, fn interface{}) { - s.Config.AddFilter(name, fn) -} +// func (s Config) AddFilter(name string, fn interface{}) { +// s.Config.AddFilter(name, fn) +// } diff --git a/render/context.go b/render/context.go index bcfc994..1813694 100644 --- a/render/context.go +++ b/render/context.go @@ -55,7 +55,7 @@ func (c renderContext) EvaluateString(source string) (out interface{}, err error } } }() - return expression.EvaluateString(source, expression.NewContext(c.ctx.bindings, c.ctx.config.Config)) + return expression.EvaluateString(source, expression.NewContext(c.ctx.bindings, c.ctx.config.Config.Config)) } // Get gets a variable value within an evaluation context. diff --git a/render/node_context.go b/render/node_context.go index 74e4624..d888a59 100644 --- a/render/node_context.go +++ b/render/node_context.go @@ -11,14 +11,15 @@ type nodeContext struct { } // newNodeContext creates a new evaluation context. -func newNodeContext(scope map[string]interface{}, s Config) nodeContext { +func newNodeContext(scope map[string]interface{}, c Config) nodeContext { // The assign tag modifies the scope, so make a copy first. // TODO this isn't really the right place for this. vars := map[string]interface{}{} for k, v := range scope { vars[k] = v } - return nodeContext{vars, s} + // fmt.Println("new", c.Config) + return nodeContext{vars, c} } // Clone makes a copy of a context, with copied bindings. @@ -43,5 +44,5 @@ func (c nodeContext) Evaluate(expr expression.Expression) (out interface{}, err } } }() - return expr.Evaluate(expression.NewContext(c.bindings, c.config.Config)) + return expr.Evaluate(expression.NewContext(c.bindings, c.config.Config.Config)) } diff --git a/render/nodes.go b/render/nodes.go index eafc02e..b3b8ae2 100644 --- a/render/nodes.go +++ b/render/nodes.go @@ -4,6 +4,7 @@ import ( "io" "github.com/osteele/liquid/expression" + "github.com/osteele/liquid/parser" ) // Node is a node of the render tree. @@ -12,7 +13,7 @@ type Node interface { // BlockNode represents a {% tag %}…{% endtag %}. type BlockNode struct { - Chunk + parser.Chunk renderer func(io.Writer, Context) error Body []Node Branches []*BlockNode @@ -25,18 +26,18 @@ type RawNode struct { // TagNode renders itself via a render function that is created during parsing. type TagNode struct { - Chunk + parser.Chunk renderer func(io.Writer, Context) error } // TextNode is a text chunk, that is rendered verbatim. type TextNode struct { - Chunk + parser.Chunk } // ObjectNode is an {{ object }} object. type ObjectNode struct { - Chunk + parser.Chunk expr expression.Expression } diff --git a/render/render.go b/render/render.go index 6a7ca0c..e429feb 100644 --- a/render/render.go +++ b/render/render.go @@ -20,8 +20,9 @@ func Errorf(format string, a ...interface{}) Error { } // Render renders the render tree. -func Render(node Node, w io.Writer, b map[string]interface{}, c Config) error { - return renderNode(node, w, newNodeContext(b, c)) +func Render(node Node, w io.Writer, vars map[string]interface{}, c Config) error { + // fmt.Println("render", c) + return renderNode(node, w, newNodeContext(vars, c)) } func renderNode(node Node, w io.Writer, ctx nodeContext) error { // nolint: gocyclo @@ -31,11 +32,11 @@ func renderNode(node Node, w io.Writer, ctx nodeContext) error { // nolint: gocy case *BlockNode: cd, ok := ctx.config.findBlockDef(n.Name) if !ok || cd.parser == nil { - return parseErrorf("unknown tag: %s", n.Name) + return Errorf("unknown tag: %s", n.Name) } renderer := n.renderer if renderer == nil { - panic(parseErrorf("unset renderer for %v", n)) + panic(Errorf("unset renderer for %v", n)) } return renderer(w, renderContext{ctx, nil, n}) case *RawNode: @@ -48,7 +49,7 @@ func renderNode(node Node, w io.Writer, ctx nodeContext) error { // nolint: gocy case *ObjectNode: value, err := ctx.Evaluate(n.expr) if err != nil { - return parseErrorf("%s in %s", err, n.Source) + return Errorf("%s in %s", err, n.Source) } return writeObject(value, w) case *SeqNode: @@ -61,7 +62,7 @@ func renderNode(node Node, w io.Writer, ctx nodeContext) error { // nolint: gocy _, err := w.Write([]byte(n.Source)) return err default: - panic(parseErrorf("unknown node type %T", node)) + panic(Errorf("unknown node type %T", node)) } return nil } diff --git a/template.go b/template.go index ed7f8c2..42171aa 100644 --- a/template.go +++ b/template.go @@ -7,14 +7,14 @@ import ( ) type template struct { - ast render.ASTNode - config render.Config + root render.Node + config *render.Config } // Render executes the template within the bindings environment. -func (t *template) Render(b Bindings) ([]byte, error) { +func (t *template) Render(vars Bindings) ([]byte, error) { buf := new(bytes.Buffer) - err := render.Render(t.ast, buf, b, t.config) + err := render.Render(t.root, buf, vars, *t.config) if err != nil { return nil, err }