mirror of
https://github.com/danog/liquid.git
synced 2024-11-26 17:44:48 +01:00
Define IterationKeyedMap
This commit is contained in:
parent
c49d979750
commit
4bc4c8a71b
@ -10,6 +10,7 @@ package liquid
|
||||
|
||||
import (
|
||||
"github.com/osteele/liquid/render"
|
||||
"github.com/osteele/liquid/tags"
|
||||
)
|
||||
|
||||
// Bindings is a map of variable names to values.
|
||||
@ -32,3 +33,9 @@ type SourceError interface {
|
||||
Path() string
|
||||
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"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
@ -11,6 +12,9 @@ import (
|
||||
"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"
|
||||
|
||||
var errLoopContinueLoop = fmt.Errorf("continue outside a loop")
|
||||
@ -174,6 +178,7 @@ func applyLoopModifiers(loop expressions.Loop, iter iterable) iterable {
|
||||
}
|
||||
return iter
|
||||
}
|
||||
|
||||
func makeIterator(value interface{}) iterable {
|
||||
if iter, ok := value.(iterable); ok {
|
||||
return iter
|
||||
@ -181,8 +186,11 @@ func makeIterator(value interface{}) iterable {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if ms, ok := value.(yaml.MapSlice); ok {
|
||||
return mapSliceWrapper{ms}
|
||||
switch value := value.(type) {
|
||||
case IterationKeyedMap:
|
||||
return makeIterationKeyedMap(value)
|
||||
case yaml.MapSlice:
|
||||
return mapSliceWrapper{value}
|
||||
}
|
||||
switch reflect.TypeOf(value).Kind() {
|
||||
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
|
||||
|
||||
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 2 %}{{ 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 k in keyed_map %}{{ k }}={{ keyed_map[k] }}.{% endfor %}`, "a=1.b=2."},
|
||||
|
||||
// loop modifiers
|
||||
{`{% 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{}{
|
||||
"array": []string{"first", "second", "third"},
|
||||
// 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}},
|
||||
"products": []string{
|
||||
"Cool Shirt", "Alien Poster", "Batman Poster", "Bullseye Shirt", "Another Classic Vinyl", "Awesome Jeans",
|
||||
|
Loading…
Reference in New Issue
Block a user