Skip to content

Commit

Permalink
feat: stop enforcing spaces after '{%' and before '%}' (#35) (#40)
Browse files Browse the repository at this point in the history
* reduce strictness of space checks

* test flexible white space handling of parsers

* add missing endtempl spacing
  • Loading branch information
a-h authored Mar 19, 2022
1 parent 2a47eaf commit 46bc285
Show file tree
Hide file tree
Showing 25 changed files with 564 additions and 61 deletions.
8 changes: 5 additions & 3 deletions parser/calltemplateparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@ func newCallTemplateExpressionParser() callTemplateExpressionParser {
return callTemplateExpressionParser{}
}

var callTemplateExpressionStartParser = parse.Or(parse.String("{%! "), parse.String("{%!"))

type callTemplateExpressionParser struct{}

func (p callTemplateExpressionParser) Parse(pi parse.Input) parse.Result {
var r CallTemplateExpression

// Check the prefix first.
prefixResult := parse.String("{%! ")(pi)
prefixResult := callTemplateExpressionStartParser(pi)
if !prefixResult.Success {
return prefixResult
}

// Once we have a prefix, we must have an expression that returns a template, followed by a tagEnd.
from := NewPositionFromInput(pi)
pr := parse.StringUntil(parse.Or(tagEnd, newLine))(pi)
pr := parse.StringUntil(parse.Or(expressionEnd, newLine))(pi)
if pr.Error != nil && pr.Error != io.EOF {
return pr
}
Expand All @@ -36,7 +38,7 @@ func (p callTemplateExpressionParser) Parse(pi parse.Input) parse.Result {

// Eat " %}".
from = NewPositionFromInput(pi)
if te := tagEnd(pi); !te.Success {
if te := expressionEnd(pi); !te.Success {
return parse.Failure("callTemplateExpressionParser", newParseError("call: unterminated (missing closing ' %}')", from, NewPositionFromInput(pi)))
}

Expand Down
42 changes: 42 additions & 0 deletions parser/calltemplateparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,48 @@ func TestCallTemplateExpressionParser(t *testing.T) {
},
},
},
{
name: "call: simple, missing start space",
input: `{%!Other(p.Test) %}`,
expected: CallTemplateExpression{
Expression: Expression{
Value: "Other(p.Test)",
Range: Range{
From: Position{
Index: 3,
Line: 1,
Col: 3,
},
To: Position{
Index: 16,
Line: 1,
Col: 16,
},
},
},
},
},
{
name: "call: simple, missing start and end space",
input: `{%!Other(p.Test)%}`,
expected: CallTemplateExpression{
Expression: Expression{
Value: "Other(p.Test)",
Range: Range{
From: Position{
Index: 3,
Line: 1,
Col: 3,
},
To: Position{
Index: 16,
Line: 1,
Col: 16,
},
},
},
},
},
}
for _, tt := range tests {
tt := tt
Expand Down
10 changes: 6 additions & 4 deletions parser/cssparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func newCSSParser() cssParser {
type cssParser struct {
}

var endCssParser = parse.String("{% endcss %}") // {% endcss %}
var endCssParser = createEndParser("endcss") // {% endcss %}

func (p cssParser) Parse(pi parse.Input) parse.Result {
r := CSSTemplate{
Expand Down Expand Up @@ -84,6 +84,8 @@ func newCSSExpressionParser() cssExpressionParser {
type cssExpressionParser struct {
}

var cssExpressionStartParser = createStartParser("css")

var cssExpressionNameParser = parse.All(parse.WithStringConcatCombiner,
parse.Letter,
parse.Many(parse.WithStringConcatCombiner, 0, 1000, parse.Any(parse.Letter, parse.ZeroToNine)),
Expand All @@ -93,7 +95,7 @@ func (p cssExpressionParser) Parse(pi parse.Input) parse.Result {
var r cssExpression

// Check the prefix first.
prefixResult := parse.String("{% css ")(pi)
prefixResult := cssExpressionStartParser(pi)
if !prefixResult.Success {
return prefixResult
}
Expand Down Expand Up @@ -134,8 +136,8 @@ func (p cssExpressionParser) Parse(pi parse.Input) parse.Result {

// Eat ") %}".
from = NewPositionFromInput(pi)
if lb := parse.String(") %}")(pi); !lb.Success {
return parse.Failure("cssExpressionParser", newParseError("css expression: unterminated (missing ' %}')", from, NewPositionFromInput(pi)))
if lb := expressionFuncEnd(pi); !lb.Success {
return parse.Failure("cssExpressionParser", newParseError("css expression: unterminated (missing ') %}')", from, NewPositionFromInput(pi)))
}

// Expect a newline.
Expand Down
23 changes: 23 additions & 0 deletions parser/cssparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,29 @@ func TestCSSParser(t *testing.T) {
Properties: []CSSProperty{},
},
},
{
name: "css: without spaces",
input: `{%css Name()%}
{% endcss %}`,
expected: CSSTemplate{
Name: Expression{
Value: "Name",
Range: Range{
From: Position{
Index: 6,
Line: 1,
Col: 6,
},
To: Position{
Index: 10,
Line: 1,
Col: 10,
},
},
},
Properties: []CSSProperty{},
},
},
{
name: "css: single constant property",
input: `{% css Name() %}
Expand Down
4 changes: 3 additions & 1 deletion parser/doctypeparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ func newDocTypeParser() docTypeParser {
type docTypeParser struct {
}

var doctypeStartParser = parse.StringInsensitive("<!doctype ")

func (p docTypeParser) Parse(pi parse.Input) parse.Result {
var r DocType

from := NewPositionFromInput(pi)
dtr := parse.StringInsensitive("<!doctype ")(pi)
dtr := doctypeStartParser(pi)
if dtr.Error != nil && dtr.Error != io.EOF {
return dtr
}
Expand Down
13 changes: 8 additions & 5 deletions parser/elementparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ func newBoolExpressionAttributeParser() boolExpressionAttributeParser {
return boolExpressionAttributeParser{}
}

var boolExpressionStart = parse.Any(parse.String("?={%= "), parse.String("?={%="))

type boolExpressionAttributeParser struct {
}

Expand All @@ -160,14 +162,15 @@ func (p boolExpressionAttributeParser) Parse(pi parse.Input) parse.Result {
}
r.Name = pr.Item.(string)

if pr = parse.String("?={%= ")(pi); !pr.Success {
// Check whether this is a boolean expression attribute.
if pr = boolExpressionStart(pi); !pr.Success {
rewind(pi, start)
return pr
}

// Once we've seen a expression prefix, read until the tag end.
from = NewPositionFromInput(pi)
pr = parse.StringUntil(tagEnd)(pi)
pr = parse.StringUntil(expressionEnd)(pi)
if pr.Error != nil && pr.Error != io.EOF {
return parse.Failure("boolExpressionAttributeParser", fmt.Errorf("boolExpressionAttributeParser: failed to read until tag end: %w", pr.Error))
}
Expand All @@ -181,7 +184,7 @@ func (p boolExpressionAttributeParser) Parse(pi parse.Input) parse.Result {
r.Expression = NewExpression(pr.Item.(string), from, to)

// Eat the tag end.
if te := tagEnd(pi); !te.Success {
if te := expressionEnd(pi); !te.Success {
return parse.Failure("boolExpressionAttributeParser", newParseError("could not terminate boolean expression", from, NewPositionFromInput(pi)))
}

Expand Down Expand Up @@ -220,7 +223,7 @@ func (p expressionAttributeParser) Parse(pi parse.Input) parse.Result {

// Once we've seen a expression prefix, read until the tag end.
from = NewPositionFromInput(pi)
pr = parse.StringUntil(tagEnd)(pi)
pr = parse.StringUntil(expressionEnd)(pi)
if pr.Error != nil && pr.Error != io.EOF {
return parse.Failure("expressionAttributeParser", fmt.Errorf("expressionAttributeParser: failed to read until tag end: %w", pr.Error))
}
Expand All @@ -234,7 +237,7 @@ func (p expressionAttributeParser) Parse(pi parse.Input) parse.Result {
r.Expression = NewExpression(pr.Item.(string), from, to)

// Eat the tag end.
if te := tagEnd(pi); !te.Success {
if te := expressionEnd(pi); !te.Success {
return parse.Failure("expressionAttributeParser", newParseError("could not terminate string expression", from, NewPositionFromInput(pi)))
}

Expand Down
23 changes: 23 additions & 0 deletions parser/elementparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,29 @@ func TestAttributeParser(t *testing.T) {
},
},
},
{
name: "boolean expression attribute without spaces",
input: ` noshade?={%=true%}"`,
parser: newBoolExpressionAttributeParser().Parse,
expected: BoolExpressionAttribute{
Name: "noshade",
Expression: Expression{
Value: "true",
Range: Range{
From: Position{
Index: 13,
Line: 1,
Col: 13,
},
To: Position{
Index: 17,
Line: 1,
Col: 17,
},
},
},
},
},
{
name: "attribute parsing handles boolean expression attributes",
input: ` noshade?={%= true %}`,
Expand Down
11 changes: 6 additions & 5 deletions parser/forexpressionparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ func newForExpressionParser() forExpressionParser {
type forExpressionParser struct {
}

var forExpressionStartParser = createStartParser("for")

func (p forExpressionParser) Parse(pi parse.Input) parse.Result {
var r ForExpression

// Check the prefix first.
blockPrefix := "for "
prefixResult := parse.String("{% " + blockPrefix)(pi)
prefixResult := forExpressionStartParser(pi)
if !prefixResult.Success {
return prefixResult
}

// Once we've had "{% for ", we're expecting a loop Go expression, followed by a tagEnd.
from := NewPositionFromInput(pi)
pr := parse.StringUntil(parse.Or(tagEnd, newLine))(pi)
pr := parse.StringUntil(parse.Or(expressionEnd, newLine))(pi)
if pr.Error != nil && pr.Error != io.EOF {
return pr
}
Expand All @@ -38,7 +39,7 @@ func (p forExpressionParser) Parse(pi parse.Input) parse.Result {

// Eat " %}".
from = NewPositionFromInput(pi)
if te := tagEnd(pi); !te.Success {
if te := expressionEnd(pi); !te.Success {
return parse.Failure("forExpressionParser", newParseError("for: unterminated (missing closing ' %}')", from, NewPositionFromInput(pi)))
}

Expand Down Expand Up @@ -73,4 +74,4 @@ func (p forExpressionParser) Parse(pi parse.Input) parse.Result {
return parse.Success("for", r, nil)
}

var endForParser = parse.String("{% endfor %}")
var endForParser = createEndParser("endfor") // {% endfor %}
52 changes: 52 additions & 0 deletions parser/forexpressionparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,58 @@ func TestForExpressionParser(t *testing.T) {
},
},
},
{
name: "for: simple, without spaces",
input: `{%for _, item := range p.Items%}
<div>{%= item %}</div>
{% endfor %}`,
expected: ForExpression{
Expression: Expression{
Value: `_, item := range p.Items`,
Range: Range{
From: Position{
Index: 6,
Line: 1,
Col: 6,
},
To: Position{

Index: 30,
Line: 1,
Col: 30,
},
},
},
Children: []Node{
Whitespace{Value: "\t\t\t\t\t"},
Element{
Name: "div",
Attributes: []Attribute{},
Children: []Node{
StringExpression{
Expression: Expression{
Value: `item`,
Range: Range{
From: Position{
Index: 47,
Line: 2,
Col: 14,
},
To: Position{

Index: 51,
Line: 2,
Col: 18,
},
},
},
},
},
},
Whitespace{Value: "\n\t\t\t\t"},
},
},
},
}
for _, tt := range tests {
tt := tt
Expand Down
14 changes: 8 additions & 6 deletions parser/ifexpressionparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ func newIfExpressionParser() ifExpressionParser {
type ifExpressionParser struct {
}

var ifExpressionStartParser = createStartParser("if")

func (p ifExpressionParser) asChildren(parts []interface{}) (result interface{}, ok bool) {
if len(parts) == 0 {
return []Node{}, true
Expand All @@ -25,15 +27,14 @@ func (p ifExpressionParser) Parse(pi parse.Input) parse.Result {
var r IfExpression

// Check the prefix first.
blockPrefix := "if "
prefixResult := parse.String("{% " + blockPrefix)(pi)
prefixResult := ifExpressionStartParser(pi)
if !prefixResult.Success {
return prefixResult
}

// Once we've got a prefix, we must have the if expression, followed by a tagEnd.
from := NewPositionFromInput(pi)
pr := parse.StringUntil(parse.Or(tagEnd, newLine))(pi)
pr := parse.StringUntil(parse.Or(expressionEnd, newLine))(pi)
if pr.Error != nil && pr.Error != io.EOF {
return pr
}
Expand All @@ -45,7 +46,7 @@ func (p ifExpressionParser) Parse(pi parse.Input) parse.Result {

// Eat " %}".
from = NewPositionFromInput(pi)
if te := tagEnd(pi); !te.Success {
if te := expressionEnd(pi); !te.Success {
return parse.Failure("ifExpressionParser", newParseError("if: unterminated (missing closing ' %}')", from, NewPositionFromInput(pi)))
}

Expand Down Expand Up @@ -98,9 +99,10 @@ func (p elseExpressionParser) asElseExpression(parts []interface{}) (result inte

func (p elseExpressionParser) Parse(pi parse.Input) parse.Result {
return parse.All(p.asElseExpression,
parse.String("{% else %}"),
endElseParser,
newTemplateNodeParser(endIfParser).Parse, // else contents
)(pi)
}

var endIfParser = parse.String("{% endif %}")
var endElseParser = createEndParser("else") // {% else %}
var endIfParser = createEndParser("endif") // {% endif %}
Loading

0 comments on commit 46bc285

Please sign in to comment.