1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-26 21:14:45 +01:00

Add error line number

This commit is contained in:
Oliver Steele 2017-07-09 11:49:24 -04:00
parent 69d26a21ad
commit 4472b1594c
7 changed files with 25 additions and 15 deletions

View File

@ -5,10 +5,12 @@ import "github.com/osteele/liquid/expression"
// A Config holds configuration information for parsing and rendering.
type Config struct {
expression.Config
Grammar Grammar
Grammar Grammar
Filename string
LineNo int
}
// NewConfig creates a parser Config.
func NewConfig() Config {
return Config{}
return Config{LineNo: 1}
}

View File

@ -18,7 +18,7 @@ func parseErrorf(format string, a ...interface{}) ParseError {
// Parse parses a source template. It returns an AST root, that can be compiled and evaluated.
func (c Config) Parse(source string) (ASTNode, error) {
tokens := Scan(source, "")
tokens := Scan(source, c.Filename, c.LineNo)
return c.parseTokens(tokens)
}

View File

@ -8,12 +8,12 @@ import (
var tokenMatcher = regexp.MustCompile(`{{\s*(.+?)\s*}}|{%\s*(\w+)(?:\s+((?:[^%]|%[^}])+?))?\s*%}`)
// Scan breaks a string into a sequence of Tokens.
func Scan(data string, pathname string) []Token {
func Scan(data string, pathname string, firstLine int) []Token {
// TODO error on unterminated {{ and {%
// TODO probably an error when a tag contains a {{ or {%, at least outside of a string
var (
p, pe = 0, len(data)
si = SourceInfo{pathname, 1}
si = SourceInfo{pathname, firstLine}
out = make([]Token, 0)
)
for _, m := range tokenMatcher.FindAllStringSubmatchIndex(data, -1) {

View File

@ -22,45 +22,46 @@ var scannerCountTests = []struct {
{`{{ expr arg }}{{ expr arg }}`, 2},
}
func TestChunkScanner(t *testing.T) {
tokens := Scan("12", "")
func TestScan(t *testing.T) {
scan := func(src string) []Token { return Scan(src, "", 1) }
tokens := scan("12")
require.NotNil(t, tokens)
require.Len(t, tokens, 1)
require.Equal(t, TextTokenType, tokens[0].Type)
require.Equal(t, "12", tokens[0].Source)
tokens = Scan("{{obj}}", "")
tokens = scan("{{obj}}")
require.NotNil(t, tokens)
require.Len(t, tokens, 1)
require.Equal(t, ObjTokenType, tokens[0].Type)
require.Equal(t, "obj", tokens[0].Args)
tokens = Scan("{{ obj }}", "")
tokens = scan("{{ obj }}")
require.NotNil(t, tokens)
require.Len(t, tokens, 1)
require.Equal(t, ObjTokenType, tokens[0].Type)
require.Equal(t, "obj", tokens[0].Args)
tokens = Scan("{%tag args%}", "")
tokens = scan("{%tag args%}")
require.NotNil(t, tokens)
require.Len(t, tokens, 1)
require.Equal(t, TagTokenType, tokens[0].Type)
require.Equal(t, "tag", tokens[0].Name)
require.Equal(t, "args", tokens[0].Args)
tokens = Scan("{% tag args %}", "")
tokens = scan("{% tag args %}")
require.NotNil(t, tokens)
require.Len(t, tokens, 1)
require.Equal(t, TagTokenType, tokens[0].Type)
require.Equal(t, "tag", tokens[0].Name)
require.Equal(t, "args", tokens[0].Args)
tokens = Scan("pre{% tag args %}mid{{ object }}post", "")
tokens = scan("pre{% tag args %}mid{{ object }}post")
require.Equal(t, `[TextTokenType{"pre"} TagTokenType{Tag:"tag", Args:"args"} TextTokenType{"mid"} ObjTokenType{"object"} TextTokenType{"post"}]`, fmt.Sprint(tokens))
for i, test := range scannerCountTests {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
tokens := Scan(test.in, "")
tokens := scan(test.in)
require.Len(t, tokens, test.len)
})
}

View File

@ -7,7 +7,6 @@ import (
// Config holds configuration information for parsing and rendering.
type Config struct {
parser.Config
Filename string
tags map[string]TagCompiler
blockDefs map[string]*blockSyntax
}

View File

@ -5,6 +5,9 @@ import (
)
// nodeContext provides the evaluation context for rendering the AST.
//
// This type has a clumsy name so that render.Context, in the public API, can
// have a clean name that doesn't stutter.
type nodeContext struct {
bindings map[string]interface{}
config Config
@ -18,7 +21,6 @@ func newNodeContext(scope map[string]interface{}, c Config) nodeContext {
for k, v := range scope {
vars[k] = v
}
// fmt.Println("new", c.Config)
return nodeContext{vars, c}
}

View File

@ -13,6 +13,7 @@ type Template interface {
// RenderString is a convenience wrapper for Render, that has string input and output.
RenderString(Bindings) (string, error)
SetSourcePath(string)
SetSourceLocation(string, int)
}
type template struct {
@ -42,3 +43,8 @@ func (t *template) RenderString(b Bindings) (string, error) {
func (t *template) SetSourcePath(filename string) {
t.config.Filename = filename
}
func (t *template) SetSourceLocation(filename string, lineNo int) {
t.config.Filename = filename
t.config.LineNo = lineNo
}