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:
parent
7a2b28cbfd
commit
585cc5df35
@ -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)
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user