mirror of
https://github.com/danog/liquid.git
synced 2024-11-30 05:48:56 +01:00
Complete #4 case…else
This commit is contained in:
parent
5a12245d9b
commit
26bdd09897
@ -31,7 +31,7 @@ The [feature parity board](https://github.com/osteele/liquid/projects/1) lists d
|
||||
In brief, these aren't implemented:
|
||||
|
||||
- The `cycle` and `tablerow` tags
|
||||
- `{% case %}…{% else %}` and `{% when a or b %}`
|
||||
- `{% when a or b %}`
|
||||
- The `sort_natural`, `url_decode`, and `url_encode` filters
|
||||
- Loop ranges `{% for a in 1...10 %}`
|
||||
- Error modes
|
||||
|
@ -1,8 +1,8 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/osteele/liquid/parser"
|
||||
)
|
||||
@ -14,20 +14,20 @@ type BlockCompiler func(BlockNode) (func(io.Writer, Context) error, error)
|
||||
type blockSyntax struct {
|
||||
name string
|
||||
isClauseTag, isEndTag bool
|
||||
syntaxModel *blockSyntax
|
||||
parent *blockSyntax
|
||||
startName string // for an end tag, the name of the correspondign start tag
|
||||
parents map[string]bool // if non-nil, must be an immediate clause of one of these
|
||||
parser BlockCompiler
|
||||
}
|
||||
|
||||
func (s *blockSyntax) CanHaveParent(parent parser.BlockSyntax) bool {
|
||||
if parent == nil {
|
||||
return false
|
||||
switch {
|
||||
case s.isClauseTag:
|
||||
return parent != nil && s.parents[parent.TagName()]
|
||||
case s.isEndTag:
|
||||
return parent != nil && parent.TagName() == s.startName
|
||||
default:
|
||||
return true
|
||||
}
|
||||
p := parent.(*blockSyntax)
|
||||
if !s.isEndTag && p.syntaxModel != nil {
|
||||
p = p.syntaxModel
|
||||
}
|
||||
return s.parent == p
|
||||
}
|
||||
|
||||
func (s *blockSyntax) IsBlock() bool { return true }
|
||||
@ -36,15 +36,19 @@ func (s *blockSyntax) IsBlockStart() bool { return !s.isClauseTag && !s.isEndT
|
||||
func (s *blockSyntax) IsClause() bool { return s.isClauseTag }
|
||||
func (s *blockSyntax) RequiresParent() bool { return s.isClauseTag || s.isEndTag }
|
||||
|
||||
func (s *blockSyntax) ParentTags() []string {
|
||||
if s.parent == nil {
|
||||
return []string{}
|
||||
func (s *blockSyntax) ParentTags() (parents []string) {
|
||||
for k := range s.parents {
|
||||
parents = append(parents, k)
|
||||
}
|
||||
return []string{s.parent.name}
|
||||
sort.Strings(parents)
|
||||
return
|
||||
}
|
||||
func (s *blockSyntax) TagName() string { return s.name }
|
||||
|
||||
func (g grammar) addBlockDef(ct *blockSyntax) {
|
||||
if g.blockDefs[ct.name] != nil {
|
||||
panic("duplicate definition of " + ct.name)
|
||||
}
|
||||
g.blockDefs[ct.name] = ct
|
||||
}
|
||||
|
||||
@ -68,26 +72,36 @@ type blockDefBuilder struct {
|
||||
func (g grammar) AddBlock(name string) blockDefBuilder { // nolint: golint
|
||||
ct := &blockSyntax{name: name}
|
||||
g.addBlockDef(ct)
|
||||
g.addBlockDef(&blockSyntax{name: "end" + name, isEndTag: true, parent: ct})
|
||||
g.addBlockDef(&blockSyntax{name: "end" + name, isEndTag: true, startName: name})
|
||||
return blockDefBuilder{g, ct}
|
||||
}
|
||||
|
||||
// Clause tells the parser that the named tag can appear immediately between this tag and its end tag,
|
||||
// so long as it is not nested within any other control tag.
|
||||
func (b blockDefBuilder) Clause(name string) blockDefBuilder {
|
||||
b.addBlockDef(&blockSyntax{name: name, isClauseTag: true, parent: b.tag})
|
||||
if b.blockDefs[name] == nil {
|
||||
b.addBlockDef(&blockSyntax{name: name, isClauseTag: true})
|
||||
}
|
||||
c := b.blockDefs[name]
|
||||
if !c.isClauseTag {
|
||||
panic(name + " has already been defined as a non-clause")
|
||||
}
|
||||
if len(c.parents) == 0 {
|
||||
c.parents = make(map[string]bool)
|
||||
}
|
||||
c.parents[b.tag.name] = true
|
||||
return b
|
||||
}
|
||||
|
||||
// SameSyntaxAs tells the parser that this tag has the same syntax as the named tag.
|
||||
func (b blockDefBuilder) SameSyntaxAs(name string) blockDefBuilder {
|
||||
rt := b.blockDefs[name]
|
||||
if rt == nil {
|
||||
panic(fmt.Errorf("undefined: %s", name))
|
||||
}
|
||||
b.tag.syntaxModel = rt
|
||||
return b
|
||||
}
|
||||
// func (b blockDefBuilder) SameSyntaxAs(name string) blockDefBuilder {
|
||||
// rt := b.blockDefs[name]
|
||||
// if rt == nil {
|
||||
// panic(fmt.Errorf("undefined: %s", name))
|
||||
// }
|
||||
// b.tag.syntaxModel = rt
|
||||
// return b
|
||||
// }
|
||||
|
||||
// Compiler sets the parser for a control tag definition.
|
||||
func (b blockDefBuilder) Compiler(fn BlockCompiler) {
|
||||
|
@ -20,7 +20,7 @@ func AddStandardTags(c render.Config) {
|
||||
c.AddTag("break", breakTag)
|
||||
c.AddTag("continue", continueTag)
|
||||
c.AddBlock("capture").Compiler(captureTagParser)
|
||||
c.AddBlock("case").Clause("when").Compiler(caseTagParser)
|
||||
c.AddBlock("case").Clause("when").Clause("else").Compiler(caseTagParser)
|
||||
c.AddBlock("comment")
|
||||
c.AddBlock("for").Compiler(loopTagParser)
|
||||
c.AddBlock("if").Clause("else").Clause("elsif").Compiler(ifTagParser(true))
|
||||
@ -57,29 +57,40 @@ func caseTagParser(node render.BlockNode) (func(io.Writer, render.Context) error
|
||||
return nil, err
|
||||
}
|
||||
type caseRec struct {
|
||||
expr expression.Expression
|
||||
// expr expression.Expression
|
||||
test func(interface{}, render.Context) (bool, error)
|
||||
node *render.BlockNode
|
||||
}
|
||||
cases := []caseRec{}
|
||||
for _, clause := range node.Clauses {
|
||||
bfn, err := expression.Parse(clause.Args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
testFn := func(interface{}, render.Context) (bool, error) { return true, nil }
|
||||
if clause.Token.Name == "when" {
|
||||
clauseExpr, err := expression.Parse(clause.Args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
testFn = func(sel interface{}, ctx render.Context) (bool, error) {
|
||||
value, err := ctx.Evaluate(clauseExpr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return evaluator.Equal(sel, value), nil
|
||||
}
|
||||
}
|
||||
cases = append(cases, caseRec{bfn, clause})
|
||||
cases = append(cases, caseRec{testFn, clause})
|
||||
}
|
||||
return func(w io.Writer, ctx render.Context) error {
|
||||
value, err := ctx.Evaluate(expr)
|
||||
sel, err := ctx.Evaluate(expr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, branch := range cases {
|
||||
b, err := ctx.Evaluate(branch.expr)
|
||||
for _, clause := range cases {
|
||||
f, err := clause.test(sel, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if evaluator.Equal(value, b) {
|
||||
return ctx.RenderChild(w, branch.node)
|
||||
if f {
|
||||
return ctx.RenderChild(w, clause.node)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -10,48 +10,49 @@ import (
|
||||
)
|
||||
|
||||
var parseErrorTests = []struct{ in, expected string }{
|
||||
{"{%unknown_tag%}", "unknown tag"},
|
||||
{"{%if syntax error%}", "unterminated if block"},
|
||||
{"{% unknown_tag %}", "unknown tag"},
|
||||
{"{% if syntax error %}", "unterminated if block"},
|
||||
// TODO once expression parsing is moved to template parse stage
|
||||
// {"{%if syntax error%}{%endif%}", "parse error"},
|
||||
// {"{%for a in ar unknown%}{{a}} {%endfor%}", "TODO"},
|
||||
// {"{% if syntax error %}{% endif %}", "parse error"},
|
||||
// {"{% for a in ar unknown %}{{ a }} {% endfor %}", "TODO"},
|
||||
}
|
||||
|
||||
var tagTests = []struct{ in, expected string }{
|
||||
// variables
|
||||
{`{%assign av = 1%}{{av}}`, "1"},
|
||||
{`{%assign av = obj.a%}{{av}}`, "1"},
|
||||
{`{%capture x%}captured{%endcapture%}{{x}}`, "captured"},
|
||||
{`{% assign av = 1 %}{{ av }}`, "1"},
|
||||
{`{% assign av = obj.a %}{{ av }}`, "1"},
|
||||
{`{% capture x %}captured{% endcapture %}{{ x }}`, "captured"},
|
||||
|
||||
// TODO test whether this requires matching interior tags
|
||||
{`{%comment%}{{a}}{%unknown%}{%endcomment%}`, ""},
|
||||
{`{% comment %}{{ a }}{% unknown %}{% endcomment %}`, ""},
|
||||
|
||||
// conditionals
|
||||
{`{%case 1%}{%when 1%}a{%when 2%}b{%endcase%}`, "a"},
|
||||
{`{%case 2%}{%when 1%}a{%when 2%}b{%endcase%}`, "b"},
|
||||
{`{%case 3%}{%when 1%}a{%when 2%}b{%endcase%}`, ""},
|
||||
// {`{%case 2%}{%when 1%}a{%else 2%}b{%endcase%}`, "captured"},
|
||||
{`{% case 1 %}{% when 1 %}a{% when 2 %}b{% endcase %}`, "a"},
|
||||
{`{% case 2 %}{% when 1 %}a{% when 2 %}b{% endcase %}`, "b"},
|
||||
{`{% case 3 %}{% when 1 %}a{% when 2 %}b{% endcase %}`, ""},
|
||||
{`{% case 1 %}{% when 1 %}a{% else %}b{% endcase %}`, "a"},
|
||||
{`{% case 2 %}{% when 1 %}a{% else %}b{% endcase %}`, "b"},
|
||||
|
||||
{`{%if true%}true{%endif%}`, "true"},
|
||||
{`{%if false%}false{%endif%}`, ""},
|
||||
{`{%if 0%}true{%endif%}`, "true"},
|
||||
{`{%if 1%}true{%endif%}`, "true"},
|
||||
{`{%if x%}true{%endif%}`, "true"},
|
||||
{`{%if y%}true{%endif%}`, ""},
|
||||
{`{%if true%}true{%endif%}`, "true"},
|
||||
{`{%if false%}false{%endif%}`, ""},
|
||||
{`{%if true%}true{%else%}false{%endif%}`, "true"},
|
||||
{`{%if false%}false{%else%}true{%endif%}`, "true"},
|
||||
{`{%if true%}0{%elsif true%}1{%else%}2{%endif%}`, "0"},
|
||||
{`{%if false%}0{%elsif true%}1{%else%}2{%endif%}`, "1"},
|
||||
{`{%if false%}0{%elsif false%}1{%else%}2{%endif%}`, "2"},
|
||||
{`{% if true %}true{% endif %}`, "true"},
|
||||
{`{% if false %}false{% endif %}`, ""},
|
||||
{`{% if 0 %}true{% endif %}`, "true"},
|
||||
{`{% if 1 %}true{% endif %}`, "true"},
|
||||
{`{% if x %}true{% endif %}`, "true"},
|
||||
{`{% if y %}true{% endif %}`, ""},
|
||||
{`{% if true %}true{% endif %}`, "true"},
|
||||
{`{% if false %}false{% endif %}`, ""},
|
||||
{`{% if true %}true{% else %}false{% endif %}`, "true"},
|
||||
{`{% if false %}false{% else %}true{% endif %}`, "true"},
|
||||
{`{% if true %}0{% elsif true %}1{% else %}2{% endif %}`, "0"},
|
||||
{`{% if false %}0{% elsif true %}1{% else %}2{% endif %}`, "1"},
|
||||
{`{% if false %}0{% elsif false %}1{% else %}2{% endif %}`, "2"},
|
||||
|
||||
{`{%unless true%}false{%endunless%}`, ""},
|
||||
{`{%unless false%}true{%endunless%}`, "true"},
|
||||
{`{% unless true %}false{% endunless %}`, ""},
|
||||
{`{% unless false %}true{% endunless %}`, "true"},
|
||||
|
||||
// TODO test whether this requires matching interior tags
|
||||
{`pre{%raw%}{{a}}{%unknown%}{%endraw%}post`, "pre{{a}}{%unknown%}post"},
|
||||
{`pre{%raw%}{%if false%}anyway-{%endraw%}post`, "pre{%if false%}anyway-post"},
|
||||
{`pre{% raw %}{{ a }}{% unknown %}{% endraw %}post`, "pre{{ a }}{% unknown %}post"},
|
||||
{`pre{% raw %}{% if false %}anyway-{% endraw %}post`, "pre{% if false %}anyway-post"},
|
||||
}
|
||||
|
||||
var tagTestBindings = map[string]interface{}{
|
||||
|
Loading…
Reference in New Issue
Block a user