From a9b2aa00043711b5bf17217dc48e43025f1b2480 Mon Sep 17 00:00:00 2001 From: Cody Krieger Date: Sat, 5 Oct 2024 15:28:07 -0700 Subject: [PATCH] implement support for else clauses in for loops. fixes osteele/liquid#46 --- tags/iteration_tags.go | 45 ++++++++++++++++++++++++------------- tags/iteration_tags_test.go | 3 +++ tags/standard_tags.go | 2 +- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/tags/iteration_tags.go b/tags/iteration_tags.go index 702e443..80c9ed0 100644 --- a/tags/iteration_tags.go +++ b/tags/iteration_tags.go @@ -67,7 +67,34 @@ func loopTagCompiler(node render.BlockNode) (func(io.Writer, render.Context) err if err != nil { return nil, err } - return loopRenderer{stmt.Loop, node.Name}.render, nil + + return func(w io.Writer, ctx render.Context) error { + // loop modifiers + val, err := ctx.Evaluate(stmt.Loop.Expr) + if err != nil { + return err + } + + iter := makeIterator(val) + if iter == nil { + return nil + } + + iter, err = applyLoopModifiers(stmt.Loop, ctx, iter) + if err != nil { + return err + } + + if len(node.Clauses) > 1 { + return fmt.Errorf("for loops accept at most one else clause") + } + + if iter.Len() == 0 && len(node.Clauses) == 1 && node.Clauses[0].Name == "else" { + return ctx.RenderBlock(w, node.Clauses[0]) + } + + return loopRenderer{stmt.Loop, node.Name}.render(iter, w, ctx) + }, nil } type loopRenderer struct { @@ -75,21 +102,7 @@ type loopRenderer struct { tagName string } -func (loop loopRenderer) render(w io.Writer, ctx render.Context) error { - // loop modifiers - val, err := ctx.Evaluate(loop.Expr) - if err != nil { - return err - } - iter := makeIterator(val) - if iter == nil { - return nil - } - iter, err = applyLoopModifiers(loop.Loop, ctx, iter) - if err != nil { - return err - } - +func (loop loopRenderer) render(iter iterable, w io.Writer, ctx render.Context) error { // loop decorator decorator, err := makeLoopDecorator(loop, ctx) if err != nil { diff --git a/tags/iteration_tags_test.go b/tags/iteration_tags_test.go index b91dc6c..7b81a87 100644 --- a/tags/iteration_tags_test.go +++ b/tags/iteration_tags_test.go @@ -17,6 +17,7 @@ import ( var iterationTests = []struct{ in, expected string }{ {`{% for a in array %}{{ a }} {% endfor %}`, "first second third "}, + {`{% for a in array %}{{ a }} {% else %}else{% endfor %}`, "first second third "}, {`{% for a in nil %}{{ a }}.{% endfor %}`, ""}, {`{% for a in false %}{{ a }}.{% endfor %}`, ""}, {`{% for a in 2 %}{{ a }}.{% endfor %}`, ""}, @@ -37,6 +38,7 @@ var iterationTests = []struct{ in, expected string }{ {`{% for a in array offset: loopmods["offset"] %}{{ a }}.{% endfor %}`, "second.third."}, {`{% for a in array reversed limit: 1 %}{{ a }}.{% endfor %}`, "third."}, {`{% for a in array limit: 0 %}{{ a }}.{% endfor %}`, ""}, + {`{% for a in array limit: 0 %}{{ a }}.{% else %}ELSE{% endfor %}`, "ELSE"}, {`{% for a in array offset: 3 %}{{ a }}.{% endfor %}`, ""}, {`{% for a in array offset: 10 %}{{ a }}.{% endfor %}`, ""}, // TODO investigate how these combine; does it depend on the order? @@ -126,6 +128,7 @@ var iterationErrorTests = []struct{ in, expected string }{ {`{% cycle 'a', 'b' %}`, "cycle must be within a forloop"}, {`{% for a in array | undefined_filter %}{% endfor %}`, "undefined filter"}, {`{% for a in array %}{{ a | undefined_filter }}{% endfor %}`, "undefined filter"}, + {`{% for a in array %}{% else %}{% else %}{% endfor %}`, "for loops accept at most one else clause"}, } var iterationTestBindings = map[string]interface{}{ diff --git a/tags/standard_tags.go b/tags/standard_tags.go index 76fcf78..b45b654 100644 --- a/tags/standard_tags.go +++ b/tags/standard_tags.go @@ -22,7 +22,7 @@ func AddStandardTags(c render.Config) { c.AddBlock("capture").Compiler(captureTagCompiler) c.AddBlock("case").Clause("when").Clause("else").Compiler(caseTagCompiler) c.AddBlock("comment") - c.AddBlock("for").Compiler(loopTagCompiler) + c.AddBlock("for").Clause("else").Compiler(loopTagCompiler) c.AddBlock("if").Clause("else").Clause("elsif").Compiler(ifTagCompiler(true)) c.AddBlock("raw") c.AddBlock("tablerow").Compiler(loopTagCompiler)