mirror of
https://github.com/danog/liquid.git
synced 2024-11-27 06:34:45 +01:00
Implement a big chunk of filters
This commit is contained in:
parent
a93848a95e
commit
1630af7664
@ -13,12 +13,15 @@ func init() {
|
||||
DefineStandardFilters()
|
||||
}
|
||||
|
||||
var filterTests = []struct{ in, expected string }{
|
||||
var filterTests = []struct {
|
||||
in string
|
||||
expected interface{}
|
||||
}{
|
||||
// values
|
||||
{`4.99 | default: 2.99`, "4.99"},
|
||||
{`undefined | default: 2.99`, "2.99"},
|
||||
{`false | default: 2.99`, "2.99"},
|
||||
{`empty_list | default: 2.99`, "2.99"},
|
||||
{`4.99 | default: 2.99`, 4.99},
|
||||
{`undefined | default: 2.99`, 2.99},
|
||||
{`false | default: 2.99`, 2.99},
|
||||
{`empty_list | default: 2.99`, 2.99},
|
||||
|
||||
// date filters
|
||||
{`article.published_at | date`, "Fri, Jul 17, 15"},
|
||||
@ -26,54 +29,60 @@ var filterTests = []struct{ in, expected string }{
|
||||
{`article.published_at | date: "%Y"`, "2015"},
|
||||
{`"2017-02-08 19:00:00 -05:00" | date`, "Wed, Feb 08, 17"},
|
||||
{`"March 14, 2016" | date: "%b %d, %y"`, "Mar 14, 16"},
|
||||
// {`"now" | date: "%Y-%m-%d %H:%M" }`, ""},
|
||||
// {`"now" | date: "%Y-%m-%d %H:%M"`, "2017-06-28 13:27"},
|
||||
|
||||
// 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}]`},
|
||||
{`fruits | reverse | join: ", "`, "plums, peaches, oranges, apples"},
|
||||
// last, map, slice, sort_natural, size, uniq
|
||||
// map, slice, sort_natural, 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
|
||||
{`"Take my protein pills and put my helmet on" | replace: "my", "your"`, "Take your protein pills and put your helmet on"},
|
||||
{`"Take my protein pills and put my helmet on" | replace_first: "my", "your"`, "Take your protein pills and put my helmet on"},
|
||||
{`"/my/fancy/url" | append: ".html"`, "/my/fancy/url.html"},
|
||||
{`"website.com" | append: "/index.html"`, "website.com/index.html"},
|
||||
{`"title" | capitalize`, "Title"},
|
||||
{`"my great title" | capitalize`, "My great title"},
|
||||
{`"Parker Moore" | downcase`, "parker moore"},
|
||||
{`"Parker Moore" | upcase`, "PARKER MOORE"},
|
||||
{`" So much room for activities! " | strip`, "So much room for activities!"},
|
||||
{`" So much room for activities! " | lstrip`, "So much room for activities! "},
|
||||
{`" So much room for activities! " | rstrip`, " So much room for activities!"},
|
||||
{`"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"},
|
||||
{`"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"},
|
||||
// {`"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
|
||||
// 4 | abs
|
||||
// "-19.86" | abs
|
||||
{`-17 | abs`, 17},
|
||||
{`4 | abs`, 4},
|
||||
{`"-19.86" | abs`, 19.86},
|
||||
|
||||
// 1.2 | ceil
|
||||
// 2.0 | ceil
|
||||
// 183.357 | ceil
|
||||
// "3.5" | ceil
|
||||
{`1.2 | ceil`, 2},
|
||||
{`2.0 | ceil`, 2},
|
||||
{`183.357 | ceil`, 184},
|
||||
{`"3.5" | ceil`, 4},
|
||||
|
||||
// 16 | divided_by: 4
|
||||
// 5 | divided_by: 3
|
||||
// 20 | divided_by: 7.0
|
||||
// {`16 | divided_by: 4`, 4},
|
||||
// {`5 | divided_by: 3`, 1},
|
||||
// {`20 | divided_by: 7.0`, 123},
|
||||
|
||||
// 1.2 | floor
|
||||
// 2.0 | floor
|
||||
// 183.357 | floor
|
||||
{`1.2 | floor`, 1},
|
||||
{`2.0 | floor`, 2},
|
||||
{`183.357 | floor`, 183},
|
||||
// minus, modulo, plus, round,times
|
||||
|
||||
// Jekyll extensions
|
||||
// Jekyll extensions; added here for convenient testing
|
||||
// TODO add this just to the test environment
|
||||
{`obj | inspect`, `{"a":1}`},
|
||||
}
|
||||
|
||||
@ -122,11 +131,12 @@ func TestFilters(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
||||
value, err := expressions.EvaluateExpr(test.in, filterTestContext)
|
||||
require.NoErrorf(t, err, test.in)
|
||||
actual := fmt.Sprintf("%v", value)
|
||||
if value == nil {
|
||||
actual = ""
|
||||
expected := test.expected
|
||||
switch ex := expected.(type) {
|
||||
case int:
|
||||
expected = float64(ex)
|
||||
}
|
||||
require.Equalf(t, test.expected, actual, test.in)
|
||||
require.Equalf(t, expected, value, test.in)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ package filters
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/leekchan/timeutil"
|
||||
"github.com/osteele/liquid/expressions"
|
||||
@ -36,8 +38,65 @@ func DefineStandardFilters() {
|
||||
expressions.DefineFilter("reverse", reverseFilter)
|
||||
expressions.DefineFilter("sort", sortFilter)
|
||||
|
||||
// numbers
|
||||
expressions.DefineFilter("abs", math.Abs)
|
||||
expressions.DefineFilter("ceil", math.Ceil)
|
||||
expressions.DefineFilter("floor", math.Floor)
|
||||
|
||||
// strings
|
||||
expressions.DefineFilter("append", func(s, suffix string) string {
|
||||
return s + suffix
|
||||
})
|
||||
expressions.DefineFilter("capitalize", func(s, suffix string) string {
|
||||
if len(s) < 1 {
|
||||
return s
|
||||
}
|
||||
return strings.ToUpper(s[:1]) + s[1:]
|
||||
})
|
||||
expressions.DefineFilter("downcase", func(s, suffix string) string {
|
||||
return strings.ToLower(s)
|
||||
})
|
||||
// expressions.DefineFilter("escape", func(s, suffix string) string {
|
||||
// buf := new(bytes.Buffer)
|
||||
// template.HTMLEscape(buf, []byte(s))
|
||||
// return buf.String()
|
||||
// })
|
||||
expressions.DefineFilter("prepend", func(s, prefix string) string {
|
||||
return prefix + s
|
||||
})
|
||||
expressions.DefineFilter("remove", func(s, old string) string {
|
||||
return strings.Replace(s, old, "", -1)
|
||||
})
|
||||
expressions.DefineFilter("remove_first", func(s, old string) string {
|
||||
return strings.Replace(s, old, "", 1)
|
||||
})
|
||||
expressions.DefineFilter("replace", func(s, old, new string) string {
|
||||
return strings.Replace(s, old, new, -1)
|
||||
})
|
||||
expressions.DefineFilter("replace_first", func(s, old, new string) string {
|
||||
return strings.Replace(s, old, new, 1)
|
||||
})
|
||||
expressions.DefineFilter("split", splitFilter)
|
||||
expressions.DefineFilter("strip", strings.TrimSpace)
|
||||
expressions.DefineFilter("lstrip", func(s string) string {
|
||||
return strings.TrimLeftFunc(s, unicode.IsSpace)
|
||||
})
|
||||
expressions.DefineFilter("rstrip", func(s string) string {
|
||||
return strings.TrimRightFunc(s, unicode.IsSpace)
|
||||
})
|
||||
expressions.DefineFilter("truncate", func(s string, n int, ellipsis interface{}) string {
|
||||
el, ok := ellipsis.(string)
|
||||
if !ok {
|
||||
el = "..."
|
||||
}
|
||||
if len(s) > n {
|
||||
s = s[:n-len(el)] + el
|
||||
}
|
||||
return s
|
||||
})
|
||||
expressions.DefineFilter("upcase", func(s, suffix string) string {
|
||||
return strings.ToUpper(s)
|
||||
})
|
||||
|
||||
// Jekyll
|
||||
expressions.DefineFilter("inspect", json.Marshal)
|
||||
|
@ -2,6 +2,7 @@ package generics
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -10,6 +11,8 @@ var timeType = reflect.TypeOf(time.Now())
|
||||
// Convert value to the type. This is a more aggressive conversion, that will
|
||||
// recursively create new map and slice values as necessary. It doesn't
|
||||
// handle circular references.
|
||||
//
|
||||
// TODO It's weird that this takes an interface{} but returns a Value
|
||||
func Convert(value interface{}, t reflect.Type) reflect.Value {
|
||||
r := reflect.ValueOf(value)
|
||||
if r.Type().ConvertibleTo(t) {
|
||||
@ -26,6 +29,18 @@ func Convert(value interface{}, t reflect.Type) reflect.Value {
|
||||
return reflect.ValueOf(v)
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
n, err := strconv.Atoi(value.(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return reflect.ValueOf(n)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
n, err := strconv.ParseFloat(value.(string), 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return reflect.ValueOf(n)
|
||||
case reflect.Slice:
|
||||
if r.Kind() != reflect.Array && r.Kind() != reflect.Slice {
|
||||
break
|
||||
|
@ -2,11 +2,20 @@ package generics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var convertTests = []struct {
|
||||
value, proto, expected interface{}
|
||||
}{
|
||||
{1, 1.0, float64(1)},
|
||||
{"2", 1, int(2)},
|
||||
{"1.2", 1.0, float64(1.2)},
|
||||
}
|
||||
|
||||
var eqTests = []struct {
|
||||
a, b interface{}
|
||||
expected bool
|
||||
@ -52,6 +61,16 @@ var lessTests = []struct {
|
||||
{[]string{"a"}, []string{"a"}, false},
|
||||
}
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
for i, test := range convertTests {
|
||||
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
|
||||
typ := reflect.TypeOf(test.proto)
|
||||
value := Convert(test.value, typ).Interface()
|
||||
require.Equalf(t, test.expected, value, "Convert %#v -> %#v", test.value, test, typ)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
for i, test := range eqTests {
|
||||
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user