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:
parent
83503a149f
commit
4189f03261
@ -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)
|
||||
|
@ -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 < 2 & 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
110
filters/filter_test.go
Normal 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 < 2 & 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
53
filters/filters.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user