mirror of
https://github.com/danog/liquid.git
synced 2024-11-30 10:39:01 +01:00
More filters
This commit is contained in:
parent
607f4f413f
commit
910d4b25cb
@ -30,6 +30,8 @@ var evaluatorTests = []struct {
|
||||
{`fruits.last`, "plums"},
|
||||
{`empty_list.first`, nil},
|
||||
{`empty_list.last`, nil},
|
||||
{`"abc".size`, 3},
|
||||
{`fruits.size`, 4},
|
||||
|
||||
// Indices
|
||||
{"ar[1]", "second"},
|
||||
@ -71,7 +73,7 @@ var evaluatorTests = []struct {
|
||||
var evaluatorTestContext = NewContext(map[string]interface{}{
|
||||
"n": 123,
|
||||
"ar": []string{"first", "second", "third"},
|
||||
"empty_list": map[string]interface{}{},
|
||||
"empty_list": []interface{}{},
|
||||
"fruits": []string{"apples", "oranges", "peaches", "plums"},
|
||||
"obj": map[string]interface{}{
|
||||
"a": "first",
|
||||
|
@ -17,6 +17,12 @@ func makeObjectPropertyEvaluator(obj func(Context) interface{}, attr string) fun
|
||||
return ref.Index(0).Interface()
|
||||
case "last":
|
||||
return ref.Index(ref.Len() - 1).Interface()
|
||||
case "size":
|
||||
return ref.Len()
|
||||
}
|
||||
case reflect.String:
|
||||
if attr == "size" {
|
||||
return ref.Len()
|
||||
}
|
||||
case reflect.Map:
|
||||
value := ref.MapIndex(reflect.ValueOf(attr))
|
||||
|
@ -32,12 +32,21 @@ var filterTests = []struct {
|
||||
// {`"now" | date: "%Y-%m-%d %H:%M"`, "2017-06-28 13:27"},
|
||||
|
||||
// list filters
|
||||
// site.pages | map: 'category' | compact | join "," %}
|
||||
// TODO sort_natural, uniq
|
||||
{`pages | map: 'category' | join`, "business, celebrities, <nil>, lifestyle, sports, <nil>, technology"},
|
||||
{`pages | map: 'category' | compact | join`, "business, celebrities, lifestyle, sports, technology"},
|
||||
{`"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}]`},
|
||||
{`fruits | reverse | join: ", "`, "plums, peaches, oranges, apples"},
|
||||
// map, slice, sort_natural, size, uniq
|
||||
{`fruits | first`, "apples"},
|
||||
{`fruits | last`, "plums"},
|
||||
{`empty_list | first`, nil},
|
||||
{`empty_list | last`, nil},
|
||||
|
||||
// sequence filters
|
||||
{`"Ground control to Major Tom." | size`, 28},
|
||||
{`"apples, oranges, peaches, plums" | split: ", " | size`, 4},
|
||||
|
||||
// string filters
|
||||
{`"Take my protein pills and put my helmet on" | replace: "my", "your"`, "Take your protein pills and put your helmet on"},
|
||||
@ -54,13 +63,17 @@ var filterTests = []struct {
|
||||
{`"apples, oranges, and bananas" | prepend: "Some fruit: "`, "Some fruit: apples, oranges, and bananas"},
|
||||
{`"I strained to see the train through the rain" | remove: "rain"`, "I sted to see the t through the "},
|
||||
{`"I strained to see the train through the rain" | remove_first: "rain"`, "I sted to see the train through the rain"},
|
||||
{`"Liquid" | slice: 0`, "L"},
|
||||
{`"Liquid" | slice: 2`, "q"},
|
||||
{`"Liquid" | slice: 2, 5`, "quid"},
|
||||
{`"Liquid" | slice: -3, 2`, "ui"},
|
||||
{`"Ground control to Major Tom." | truncate: 20`, "Ground control to..."},
|
||||
{`"Ground control to Major Tom." | truncate: 25, ", and so on"`, "Ground control, and so on"},
|
||||
{`"Ground control to Major Tom." | truncate: 20, ""`, "Ground control to Ma"},
|
||||
// TODO escape, newline_to_br, strip_html, strip_newlines, truncatewords, url_decode, url_encode
|
||||
// {`"Have you read 'James & the Giant Peach'?" | escape`, ""},
|
||||
// {`"1 < 2 & 3" | escape_once`, ""},
|
||||
// {`"1 < 2 & 3" | escape_once`, ""},
|
||||
// newline_to_br,strip_html, strip_newlines, truncatewords, // url_decode, url_encode
|
||||
|
||||
// number filters
|
||||
{`-17 | abs`, 17},
|
||||
@ -79,7 +92,7 @@ var filterTests = []struct {
|
||||
{`1.2 | floor`, 1},
|
||||
{`2.0 | floor`, 2},
|
||||
{`183.357 | floor`, 183},
|
||||
// minus, modulo, plus, round,times
|
||||
// TODO divided_by, minus, modulo, plus, round,times
|
||||
|
||||
// Jekyll extensions; added here for convenient testing
|
||||
// TODO add this just to the test environment
|
||||
@ -100,19 +113,19 @@ var filterTestContext = expressions.NewContext(map[string]interface{}{
|
||||
"article": map[string]interface{}{
|
||||
"published_at": timeMustParse("2015-07-17T15:04:05Z"),
|
||||
},
|
||||
"empty_list": map[string]interface{}{},
|
||||
"empty_list": []interface{}{},
|
||||
"fruits": []string{"apples", "oranges", "peaches", "plums"},
|
||||
"obj": map[string]interface{}{
|
||||
"a": 1,
|
||||
},
|
||||
"pages": []map[string]interface{}{
|
||||
{"category": "business"},
|
||||
{"category": "celebrities"},
|
||||
{},
|
||||
{"category": "lifestyle"},
|
||||
{"category": "sports"},
|
||||
{},
|
||||
{"category": "technology"},
|
||||
{"name": "page 1", "category": "business"},
|
||||
{"name": "page 2", "category": "celebrities"},
|
||||
{"name": "page 3"},
|
||||
{"name": "page 4", "category": "lifestyle"},
|
||||
{"name": "page 5", "category": "sports"},
|
||||
{"name": "page 6"},
|
||||
{"name": "page 7", "category": "technology"},
|
||||
},
|
||||
"sort_prop": []map[string]interface{}{
|
||||
{"weight": 1},
|
||||
@ -128,10 +141,14 @@ var filterTestContext = expressions.NewContext(map[string]interface{}{
|
||||
|
||||
func TestFilters(t *testing.T) {
|
||||
for i, test := range filterTests {
|
||||
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
|
||||
value, err := expressions.EvaluateExpr(test.in, filterTestContext)
|
||||
require.NoErrorf(t, err, test.in)
|
||||
expected := test.expected
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
value = float64(v)
|
||||
}
|
||||
switch ex := expected.(type) {
|
||||
case int:
|
||||
expected = float64(ex)
|
||||
|
@ -34,15 +34,48 @@ func DefineStandardFilters() {
|
||||
})
|
||||
|
||||
// lists
|
||||
expressions.DefineFilter("compact", func(values []interface{}) interface{} {
|
||||
out := []interface{}{}
|
||||
for _, value := range values {
|
||||
if value != nil {
|
||||
out = append(out, value)
|
||||
}
|
||||
}
|
||||
return out
|
||||
})
|
||||
expressions.DefineFilter("join", joinFilter)
|
||||
expressions.DefineFilter("map", func(values []map[string]interface{}, key string) interface{} {
|
||||
out := []interface{}{}
|
||||
for _, obj := range values {
|
||||
out = append(out, obj[key])
|
||||
}
|
||||
return out
|
||||
})
|
||||
expressions.DefineFilter("reverse", reverseFilter)
|
||||
expressions.DefineFilter("sort", sortFilter)
|
||||
// https://shopify.github.io/liquid/ does not demonstrate first and last as filters,
|
||||
// but https://help.shopify.com/themes/liquid/filters/array-filters does
|
||||
expressions.DefineFilter("first", func(values []interface{}) interface{} {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return values[0]
|
||||
})
|
||||
expressions.DefineFilter("last", func(values []interface{}) interface{} {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return values[len(values)-1]
|
||||
})
|
||||
|
||||
// numbers
|
||||
expressions.DefineFilter("abs", math.Abs)
|
||||
expressions.DefineFilter("ceil", math.Ceil)
|
||||
expressions.DefineFilter("floor", math.Floor)
|
||||
|
||||
// sequences
|
||||
expressions.DefineFilter("size", generics.Length)
|
||||
|
||||
// strings
|
||||
expressions.DefineFilter("append", func(s, suffix string) string {
|
||||
return s + suffix
|
||||
@ -76,6 +109,22 @@ func DefineStandardFilters() {
|
||||
expressions.DefineFilter("replace_first", func(s, old, new string) string {
|
||||
return strings.Replace(s, old, new, 1)
|
||||
})
|
||||
expressions.DefineFilter("slice", func(s string, start int, length interface{}) string {
|
||||
n, ok := length.(int)
|
||||
if !ok {
|
||||
n = 1
|
||||
}
|
||||
if start < 0 {
|
||||
start = len(s) + start
|
||||
}
|
||||
if start >= len(s) {
|
||||
return ""
|
||||
}
|
||||
if start+n > len(s) {
|
||||
return s[start:]
|
||||
}
|
||||
return s[start : start+n]
|
||||
})
|
||||
expressions.DefineFilter("split", splitFilter)
|
||||
expressions.DefineFilter("strip", strings.TrimSpace)
|
||||
expressions.DefineFilter("lstrip", func(s string) string {
|
||||
|
@ -34,3 +34,15 @@ func IsEmpty(value interface{}) bool {
|
||||
func IsTrue(value interface{}) bool {
|
||||
return value != nil && value != false
|
||||
}
|
||||
|
||||
// Length returns the length of a string or array. In keeping with Liquid semantics,
|
||||
// and contra Go, it does not return the size of a map.
|
||||
func Length(value interface{}) int {
|
||||
ref := reflect.ValueOf(value)
|
||||
switch ref.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
return ref.Len();
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
@ -88,3 +88,9 @@ func TestLess(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLength(t *testing.T) {
|
||||
require.Equal(t, 3, Length([]int{1, 2, 3}))
|
||||
require.Equal(t, 3, Length("abc"))
|
||||
require.Equal(t, 0, Length(map[string]int{"a": 1}))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user