1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-30 06:59:03 +01:00

Move filters to own package

This commit is contained in:
Oliver Steele 2017-06-27 12:06:24 -04:00
parent 83503a149f
commit 4189f03261
7 changed files with 174 additions and 122 deletions

View File

@ -46,7 +46,7 @@ func (ctx Context) RenderASTSequence(w io.Writer, seq []ASTNode) error {
// Render evaluates an AST node and writes the result to an io.Writer.
func (n *ASTControlTag) Render(w io.Writer, ctx Context) error {
cd, ok := FindControlDefinition(n.Tag)
if !ok {
if !ok || cd.action == nil {
return fmt.Errorf("unimplemented tag: %s", n.Tag)
}
f := cd.action(*n)

View File

@ -22,65 +22,6 @@ var renderTests = []struct{ in, expected string }{
{"{{ar[1]}}", "second"},
}
var filterTests = []struct{ in, expected string }{
// Jekyll extensions
{`{{ obj | inspect }}`, `{"a":1}`},
// filters
// {{ product_price | default: 2.99 }}
// list filters
// {{ site.pages | map: 'category' | compact | join "," %}
// {% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}{{ my_array.first }}
{`{{"John, Paul, George, Ringo" | split: ", " | join: " and "}}`, "John and Paul and George and Ringo"},
{`{{ animals | sort | join: ", " }}`, "Sally Snake, giraffe, octopus, zebra"},
{`{{ sort_prop | sort: "weight" | inspect }}`, `[{"weight":null},{"weight":1},{"weight":3},{"weight":5}]`},
// last, map, slice, sort_natural, reverse, size, uniq
// string filters
// {{ "/my/fancy/url" | append: ".html" }}
// {% assign filename = "/index.html" %}{{ "website.com" | append: filename }}
// {{ "title" | capitalize }}
// {{ "my great title" | capitalize }}
// {{ "Parker Moore" | downcase }}
// {{ "Have you read 'James & the Giant Peach'?" | escape }}
// {{ "1 < 2 & 3" | escape_once }}
// {{ "1 &lt; 2 &amp; 3" | escape_once }}
// lstrip, newline_to_br, prepend, remove, remove_first, replace, replace_first
// rstrip, split, strip, strip_html, strip_newlines, truncate, truncatewords, upcase
// url_decode, url_encode
// number filters
// {{ -17 | abs }}
// {{ 4 | abs }}
// {{ "-19.86" | abs }}
// {{ 1.2 | ceil }}
// {{ 2.0 | ceil }}
// {{ 183.357 | ceil }}
// {{ "3.5" | ceil }}
// {{ 16 | divided_by: 4 }}
// {{ 5 | divided_by: 3 }}
// {{ 20 | divided_by: 7.0 }}
// {{ 1.2 | floor }}
// {{ 2.0 | floor }}
// {{ 183.357 | floor }}
// minus, modulo, plus, round,times
// date filters
// {{ article.published_at | date: "%a, %b %d, %y" }}
// {{ article.published_at | date: "%Y" }}
// {{ "March 14, 2016" | date: "%b %d, %y" }}
// {{ "now" | date: "%Y-%m-%d %H:%M" }
}
var renderTestContext = Context{map[string]interface{}{
"x": 123,
"obj": map[string]interface{}{
@ -135,17 +76,3 @@ func TestRender(t *testing.T) {
})
}
}
func TestFilters(t *testing.T) {
for i, test := range filterTests {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
tokens := Scan(test.in, "")
ast, err := Parse(tokens)
require.NoErrorf(t, err, test.in)
buf := new(bytes.Buffer)
err = ast.Render(buf, renderTestContext)
require.NoErrorf(t, err, test.in)
require.Equalf(t, test.expected, buf.String(), test.in)
})
}
}

View File

@ -9,8 +9,16 @@ import (
"io"
"github.com/osteele/liquid/chunks"
"github.com/osteele/liquid/filters"
"github.com/osteele/liquid/tags"
)
// TODO move the filters and tags from globals to the engine
func init() {
tags.DefineStandardTags()
filters.DefineStandardFilters()
}
// Engine parses template source into renderable text.
//
// In the future, it will be configured with additional tags, filters, and the {%include%} search path.

View File

@ -1,10 +1,8 @@
package expressions
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/osteele/liquid/errors"
"github.com/osteele/liquid/generics"
@ -16,53 +14,8 @@ func (e InterpreterError) Error() string { return string(e) }
type valueFn func(Context) interface{}
func joinFilter(in []interface{}, sep interface{}) interface{} {
a := make([]string, len(in))
s := ", "
if sep != nil {
s = fmt.Sprint(sep)
}
for i, x := range in {
a[i] = fmt.Sprint(x)
}
return strings.Join(a, s)
}
func sortFilter(in []interface{}, key interface{}) []interface{} {
out := make([]interface{}, len(in))
for i, v := range in {
out[i] = v
}
if key == nil {
generics.Sort(out)
} else {
generics.SortByProperty(out, key.(string))
}
return out
}
func splitFilter(in, sep string) interface{} {
return strings.Split(in, sep)
}
var filters = map[string]interface{}{}
func init() {
DefineStandardFilters()
}
func DefineStandardFilters() {
// lists
DefineFilter("join", joinFilter)
DefineFilter("sort", sortFilter)
// strings
DefineFilter("split", splitFilter)
// Jekyll
DefineFilter("inspect", json.Marshal)
}
func DefineFilter(name string, fn interface{}) {
rf := reflect.ValueOf(fn)
switch {

110
filters/filter_test.go Normal file
View File

@ -0,0 +1,110 @@
package filters
import (
"fmt"
"testing"
"github.com/osteele/liquid/expressions"
"github.com/stretchr/testify/require"
)
func init() {
DefineStandardFilters()
}
var filterTests = []struct{ in, expected string }{
// Jekyll extensions
{`obj | inspect`, `{"a":1}`},
// filters
// product_price | default: 2.99 }}
// list filters
// site.pages | map: 'category' | compact | join "," %}
// {% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}my_array.first }}
{`"John, Paul, George, Ringo" | split: ", " | join: " and "`, "John and Paul and George and Ringo"},
{`animals | sort | join: ", "`, "Sally Snake, giraffe, octopus, zebra"},
{`sort_prop | sort: "weight" | inspect`, `[{"weight":null},{"weight":1},{"weight":3},{"weight":5}]`},
// last, map, slice, sort_natural, reverse, size, uniq
// string filters
// "/my/fancy/url" | append: ".html"
// {% assign filename = "/index.html" %}"website.com" | append: filename
// "title" | capitalize
// "my great title" | capitalize
// "Parker Moore" | downcase
// "Have you read 'James & the Giant Peach'?" | escape
// "1 < 2 & 3" | escape_once
// "1 &lt; 2 &amp; 3" | escape_once
// lstrip, newline_to_br, prepend, remove, remove_first, replace, replace_first
// rstrip, split, strip, strip_html, strip_newlines, truncate, truncatewords, upcase
// url_decode, url_encode
// number filters
// -17 | abs
// 4 | abs
// "-19.86" | abs
// 1.2 | ceil
// 2.0 | ceil
// 183.357 | ceil
// "3.5" | ceil
// 16 | divided_by: 4
// 5 | divided_by: 3
// 20 | divided_by: 7.0
// 1.2 | floor
// 2.0 | floor
// 183.357 | floor
// minus, modulo, plus, round,times
// date filters
// article.published_at | date: "%a, %b %d, %y"
// article.published_at | date: "%Y"
// "March 14, 2016" | date: "%b %d, %y"
// "now" | date: "%Y-%m-%d %H:%M" }
}
var filterTestContext = expressions.NewContext(map[string]interface{}{
"x": 123,
"obj": map[string]interface{}{
"a": 1,
},
"animals": []string{"zebra", "octopus", "giraffe", "Sally Snake"},
"pages": []map[string]interface{}{
{"category": "business"},
{"category": "celebrities"},
{},
{"category": "lifestyle"},
{"category": "sports"},
{},
{"category": "technology"},
},
"sort_prop": []map[string]interface{}{
{"weight": 1},
{"weight": 5},
{"weight": 3},
{"weight": nil},
},
"ar": []string{"first", "second", "third"},
"page": map[string]interface{}{
"title": "Introduction",
},
})
func TestFilters(t *testing.T) {
for i, test := range filterTests {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
val, err := expressions.EvaluateExpr(test.in, filterTestContext)
require.NoErrorf(t, err, test.in)
actual := fmt.Sprintf("%s", val)
require.Equalf(t, test.expected, actual, test.in)
})
}
}

53
filters/filters.go Normal file
View File

@ -0,0 +1,53 @@
// The filters package defines the standard Liquid filters.
package filters
import (
"encoding/json"
"fmt"
"strings"
"github.com/osteele/liquid/expressions"
"github.com/osteele/liquid/generics"
)
// DefineStandardFilters defines the standard Liquid filters.
func DefineStandardFilters() {
// lists
expressions.DefineFilter("join", joinFilter)
expressions.DefineFilter("sort", sortFilter)
// strings
expressions.DefineFilter("split", splitFilter)
// Jekyll
expressions.DefineFilter("inspect", json.Marshal)
}
func joinFilter(in []interface{}, sep interface{}) interface{} {
a := make([]string, len(in))
s := ", "
if sep != nil {
s = fmt.Sprint(sep)
}
for i, x := range in {
a[i] = fmt.Sprint(x)
}
return strings.Join(a, s)
}
func sortFilter(in []interface{}, key interface{}) []interface{} {
out := make([]interface{}, len(in))
for i, v := range in {
out[i] = v
}
if key == nil {
generics.Sort(out)
} else {
generics.SortByProperty(out, key.(string))
}
return out
}
func splitFilter(in, sep string) interface{} {
return strings.Split(in, sep)
}

View File

@ -1,3 +1,4 @@
// The tags package defines the standard Liquid tags.
package tags
import (
@ -6,6 +7,7 @@ import (
"github.com/osteele/liquid/chunks"
)
// DefineStandardTags defines the standard Liquid tags.
func DefineStandardTags() {
loopTags := []string{"break", "continue", "cycle"}
chunks.DefineControlTag("comment")
@ -17,7 +19,6 @@ func DefineStandardTags() {
chunks.DefineControlTag("capture")
}
func ifTagAction(polarity bool) func(chunks.ASTControlTag) func(io.Writer, chunks.Context) error {
return func(node chunks.ASTControlTag) func(io.Writer, chunks.Context) error {
expr, err := chunks.MakeExpressionValueFn(node.Args)