mirror of
https://github.com/danog/liquid.git
synced 2024-11-26 23:14:39 +01:00
Define IterationKeyedMap
This commit is contained in:
parent
c49d979750
commit
4bc4c8a71b
@ -10,6 +10,7 @@ package liquid
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/osteele/liquid/render"
|
"github.com/osteele/liquid/render"
|
||||||
|
"github.com/osteele/liquid/tags"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bindings is a map of variable names to values.
|
// Bindings is a map of variable names to values.
|
||||||
@ -32,3 +33,9 @@ type SourceError interface {
|
|||||||
Path() string
|
Path() string
|
||||||
LineNumber() int
|
LineNumber() int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IterationKeyedMap returns a map whose {% for %} tag iteration values are its keys, instead of [key, value] pairs.
|
||||||
|
// Use this to create a Go map with the semantics of a Ruby struct drop.
|
||||||
|
func IterationKeyedMap(m map[string]interface{}) tags.IterationKeyedMap {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
43
liquid_test.go
Normal file
43
liquid_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package liquid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIterationKeyedMap(t *testing.T) {
|
||||||
|
vars := map[string]interface{}{
|
||||||
|
"keyed_map": IterationKeyedMap(map[string]interface{}{"a": 1, "b": 2}),
|
||||||
|
}
|
||||||
|
engine := NewEngine()
|
||||||
|
tpl, err := engine.ParseTemplate([]byte(`{% for k in keyed_map %}{{ k }}={{ keyed_map[k] }}.{% endfor %}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
out, err := tpl.RenderString(vars)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "a=1.b=2.", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleIterationKeyedMap() {
|
||||||
|
vars := map[string]interface{}{
|
||||||
|
"map": map[string]interface{}{"a": 1},
|
||||||
|
"keyed_map": IterationKeyedMap(map[string]interface{}{"a": 1}),
|
||||||
|
}
|
||||||
|
engine := NewEngine()
|
||||||
|
out, err := engine.ParseAndRenderString(
|
||||||
|
`{% for k in map %}{{ k[0] }}={{ k[1] }}.{% endfor %}`, vars)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
|
out, err = engine.ParseAndRenderString(
|
||||||
|
`{% for k in keyed_map %}{{ k }}={{ keyed_map[k] }}.{% endfor %}`, vars)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
|
// Output: a=1.
|
||||||
|
// a=1.
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
@ -11,6 +12,9 @@ import (
|
|||||||
"github.com/osteele/liquid/render"
|
"github.com/osteele/liquid/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// An IterationKeyedMap is a map that yields its keys, instead of (key, value) pairs, when iterated.
|
||||||
|
type IterationKeyedMap map[string]interface{}
|
||||||
|
|
||||||
const forloopVarName = "forloop"
|
const forloopVarName = "forloop"
|
||||||
|
|
||||||
var errLoopContinueLoop = fmt.Errorf("continue outside a loop")
|
var errLoopContinueLoop = fmt.Errorf("continue outside a loop")
|
||||||
@ -174,6 +178,7 @@ func applyLoopModifiers(loop expressions.Loop, iter iterable) iterable {
|
|||||||
}
|
}
|
||||||
return iter
|
return iter
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeIterator(value interface{}) iterable {
|
func makeIterator(value interface{}) iterable {
|
||||||
if iter, ok := value.(iterable); ok {
|
if iter, ok := value.(iterable); ok {
|
||||||
return iter
|
return iter
|
||||||
@ -181,8 +186,11 @@ func makeIterator(value interface{}) iterable {
|
|||||||
if value == nil {
|
if value == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if ms, ok := value.(yaml.MapSlice); ok {
|
switch value := value.(type) {
|
||||||
return mapSliceWrapper{ms}
|
case IterationKeyedMap:
|
||||||
|
return makeIterationKeyedMap(value)
|
||||||
|
case yaml.MapSlice:
|
||||||
|
return mapSliceWrapper{value}
|
||||||
}
|
}
|
||||||
switch reflect.TypeOf(value).Kind() {
|
switch reflect.TypeOf(value).Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
@ -200,6 +208,17 @@ func makeIterator(value interface{}) iterable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeIterationKeyedMap(m map[string]interface{}) iterable {
|
||||||
|
// Iteration chooses a random start, so we need a copy of the keys to iterate through them.
|
||||||
|
keys := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
// Sorting isn't necessary to match Shopify liquid, but it simplifies debugging.
|
||||||
|
sort.Strings(keys)
|
||||||
|
return sliceWrapper(reflect.ValueOf(keys))
|
||||||
|
}
|
||||||
|
|
||||||
type sliceWrapper reflect.Value
|
type sliceWrapper reflect.Value
|
||||||
|
|
||||||
func (w sliceWrapper) Len() int { return reflect.Value(w).Len() }
|
func (w sliceWrapper) Len() int { return reflect.Value(w).Len() }
|
||||||
|
@ -21,8 +21,9 @@ var iterationTests = []struct{ in, expected string }{
|
|||||||
{`{% for a in false %}{{ a }}.{% endfor %}`, ""},
|
{`{% for a in false %}{{ a }}.{% endfor %}`, ""},
|
||||||
{`{% for a in 2 %}{{ a }}.{% endfor %}`, ""},
|
{`{% for a in 2 %}{{ a }}.{% endfor %}`, ""},
|
||||||
{`{% for a in "str" %}{{ a }}.{% endfor %}`, ""},
|
{`{% for a in "str" %}{{ a }}.{% endfor %}`, ""},
|
||||||
{`{% for a in hash %}{{ a[0] }}={{ a[1] }}.{% endfor %}`, "a=1."},
|
{`{% for a in map %}{{ a[0] }}={{ a[1] }}.{% endfor %}`, "a=1."},
|
||||||
{`{% for a in map_slice %}{{ a[0] }}={{ a[1] }}.{% endfor %}`, "a=1.b=2."},
|
{`{% for a in map_slice %}{{ a[0] }}={{ a[1] }}.{% endfor %}`, "a=1.b=2."},
|
||||||
|
{`{% for k in keyed_map %}{{ k }}={{ keyed_map[k] }}.{% endfor %}`, "a=1.b=2."},
|
||||||
|
|
||||||
// loop modifiers
|
// loop modifiers
|
||||||
{`{% for a in array reversed %}{{ a }}.{% endfor %}`, "third.second.first."},
|
{`{% for a in array reversed %}{{ a }}.{% endfor %}`, "third.second.first."},
|
||||||
@ -111,7 +112,8 @@ var iterationErrorTests = []struct{ in, expected string }{
|
|||||||
var iterationTestBindings = map[string]interface{}{
|
var iterationTestBindings = map[string]interface{}{
|
||||||
"array": []string{"first", "second", "third"},
|
"array": []string{"first", "second", "third"},
|
||||||
// hash has only one element, since iteration order is non-deterministic
|
// hash has only one element, since iteration order is non-deterministic
|
||||||
"hash": map[string]interface{}{"a": 1},
|
"map": map[string]interface{}{"a": 1},
|
||||||
|
"keyed_map": IterationKeyedMap(map[string]interface{}{"a": 1, "b": 2}),
|
||||||
"map_slice": yaml.MapSlice{{Key: "a", Value: 1}, {Key: "b", Value: 2}},
|
"map_slice": yaml.MapSlice{{Key: "a", Value: 1}, {Key: "b", Value: 2}},
|
||||||
"products": []string{
|
"products": []string{
|
||||||
"Cool Shirt", "Alien Poster", "Batman Poster", "Bullseye Shirt", "Another Classic Vinyl", "Awesome Jeans",
|
"Cool Shirt", "Alien Poster", "Batman Poster", "Bullseye Shirt", "Another Classic Vinyl", "Awesome Jeans",
|
||||||
|
Loading…
Reference in New Issue
Block a user