1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-26 21:14:45 +01:00

Implement uniq filter

This commit is contained in:
Oliver Steele 2017-07-07 09:32:24 -04:00
parent 7a2b28cbfd
commit 585cc5df35
4 changed files with 77 additions and 22 deletions

View File

@ -35,7 +35,7 @@ func scanExpression(data string) ([]testSymbol, error) {
return symbols, nil
}
func TestExpressionScanner(t *testing.T) {
func TestLex(t *testing.T) {
ts, err := scanExpression("abc > 123")
require.NoError(t, err)
require.Len(t, ts, 3)
@ -73,5 +73,5 @@ func TestExpressionScanner(t *testing.T) {
require.Equal(t, 2, ts[3].typ.val)
require.Equal(t, 2.3, ts[4].typ.val)
require.Equal(t, "abc", ts[5].typ.val)
require.Equal(t, "abc", ts[7].typ.val)
require.Equal(t, "abc", ts[6].typ.val)
}

View File

@ -32,17 +32,6 @@ func AddStandardFilters(fd FilterDictionary) { // nolint: gocyclo
return value
})
// dates
fd.AddFilter("date", func(t time.Time, format interface{}) (string, error) {
form, ok := format.(string)
if !ok {
form = "%a, %b %d, %y"
}
// TODO %\d*N -> truncated fractional seconds, default 9
form = strings.Replace(form, "%N", "", -1)
return datefmt.Strftime(form, t)
})
// arrays
fd.AddFilter("compact", func(array []interface{}) interface{} {
out := []interface{}{}
@ -77,6 +66,60 @@ func AddStandardFilters(fd FilterDictionary) { // nolint: gocyclo
}
return array[len(array)-1]
})
fd.AddFilter("uniq", func(array []interface{}) []interface{} {
out := []interface{}{}
seenInts := map[int]bool{}
seenStrings := map[string]bool{}
seen := func(item interface{}) bool {
item = evaluator.ToLiquid(item)
switch v := item.(type) {
case int:
if seenInts[v] {
return true
}
seenInts[v] = true
case string:
if seenStrings[v] {
return true
}
seenStrings[v] = true
default:
// switch reflect.TypeOf(item).Kind() {
// case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
// // addr is never dereferenced, and false negatives are okay
// addr := reflect.ValueOf(item).UnsafeAddr()
// if seenAddrs[addr] {
// return true
// }
// seenAddrs[addr] = true
// }
// the O(n^2) case:
for _, v := range out {
if reflect.DeepEqual(item, v) {
return true
}
}
}
return false
}
for _, e := range array {
if !seen(e) {
out = append(out, e)
}
}
return out
})
// dates
fd.AddFilter("date", func(t time.Time, format interface{}) (string, error) {
form, ok := format.(string)
if !ok {
form = "%a, %b %d, %y"
}
// TODO %\d*N -> truncated fractional seconds, default 9
form = strings.Replace(form, "%N", "", -1)
return datefmt.Strftime(form, t)
})
// numbers
fd.AddFilter("abs", math.Abs)
@ -93,9 +136,9 @@ func AddStandardFilters(fd FilterDictionary) { // nolint: gocyclo
return a * b
})
fd.AddFilter("divided_by", func(a float64, b interface{}) interface{} {
switch bt := b.(type) {
switch q := b.(type) {
case int, int16, int32, int64:
return int(a) / bt.(int)
return int(a) / q.(int)
case float32, float64:
return a / b.(float64)
default:

View File

@ -31,6 +31,10 @@ var filterTests = []struct {
{`fruits | last`, "plums"},
{`empty_list | first`, nil},
{`empty_list | last`, nil},
{`empty_list | last`, nil},
{`dup_ints | uniq | join`, "1, 2, 3"},
{`dup_strings | uniq | join`, "one, two, three"},
{`dup_maps | uniq | map: "name" | join`, "m1, m2, m3"},
// date filters
// {`article.published_at | date`, "Fri, Jul 17, 15"},
@ -153,31 +157,39 @@ var filterTestBindings = map[string]interface{}{
{"weight": 3},
{"weight": nil},
},
"ar": []string{"first", "second", "third"},
"page": map[string]interface{}{
"title": "Introduction",
},
"dup_ints": []int{1, 2, 1, 3},
"dup_strings": []string{"one", "two", "one", "three"},
}
func TestFilters(t *testing.T) {
var (
m1 = map[string]interface{}{"name": "m1"}
m2 = map[string]interface{}{"name": "m2"}
m3 = map[string]interface{}{"name": "m3"}
)
filterTestBindings["dup_maps"] = []interface{}{m1, m2, m1, m3}
settings := expression.NewConfig()
AddStandardFilters(&settings)
context := expression.NewContext(filterTestBindings, settings)
for i, test := range filterTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
value, err := expression.EvaluateString(test.in, context)
actual, err := expression.EvaluateString(test.in, context)
require.NoErrorf(t, err, test.in)
expected := test.expected
switch v := value.(type) {
switch v := actual.(type) {
case int:
value = float64(v)
actual = float64(v)
}
switch ex := expected.(type) {
case int:
expected = float64(ex)
}
require.Equalf(t, expected, value, test.in)
require.Equalf(t, expected, actual, test.in)
})
}
}

View File

@ -27,8 +27,6 @@ func Render(node Node, w io.Writer, vars map[string]interface{}, c Config) error
func renderNode(node Node, w io.Writer, ctx nodeContext) error { // nolint: gocyclo
switch n := node.(type) {
case *TagNode:
return n.renderer(w, rendererContext{ctx, n, nil})
case *BlockNode:
cd, ok := ctx.config.findBlockDef(n.Name)
if !ok || cd.parser == nil {
@ -58,6 +56,8 @@ func renderNode(node Node, w io.Writer, ctx nodeContext) error { // nolint: gocy
return err
}
}
case *TagNode:
return n.renderer(w, rendererContext{ctx, n, nil})
case *TextNode:
_, err := w.Write([]byte(n.Source))
return err