1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-30 05:58:59 +01:00
liquid/values/value.go

225 lines
5.3 KiB
Go
Raw Permalink Normal View History

2017-07-28 00:11:37 +02:00
package values
import (
2017-07-20 15:52:05 +02:00
"fmt"
"reflect"
"strings"
2017-07-22 15:14:32 +02:00
yaml "gopkg.in/yaml.v2"
)
// A Value is a Liquid runtime value.
type Value interface {
// Value retrieval
Interface() interface{}
Int() int
// Comparison
Equal(Value) bool
Less(Value) bool
Contains(Value) bool
IndexValue(Value) Value
PropertyValue(Value) Value
// Predicate
Test() bool
}
// ValueOf returns a Value that wraps its argument.
// If the argument is already a Value, it returns this.
2017-07-21 22:26:04 +02:00
func ValueOf(value interface{}) Value { // nolint: gocyclo
// interned values
switch value {
case nil:
return nilValue
case true:
return trueValue
case false:
return falseValue
2017-08-09 14:16:36 +02:00
case 0:
return zeroValue
case 1:
return oneValue
}
2017-07-22 15:14:32 +02:00
// interfaces
2017-07-21 22:26:04 +02:00
switch v := value.(type) {
case drop:
2017-08-03 15:11:34 +02:00
return &dropWrapper{d: v}
2017-07-22 15:14:32 +02:00
case yaml.MapSlice:
return mapSliceValue{slice: v}
2017-07-21 22:26:04 +02:00
case Value:
return v
}
2017-08-09 14:16:36 +02:00
switch reflect.TypeOf(value).Kind() {
2017-07-20 15:42:36 +02:00
case reflect.Ptr:
rv := reflect.ValueOf(value)
2017-07-20 16:40:46 +02:00
if rv.Type().Elem().Kind() == reflect.Struct {
return structValue{wrapperValue{value}}
}
2017-07-20 15:42:36 +02:00
return ValueOf(rv.Elem().Interface())
case reflect.String:
return stringValue{wrapperValue{value}}
case reflect.Array, reflect.Slice:
return arrayValue{wrapperValue{value}}
case reflect.Map:
return mapValue{wrapperValue{value}}
2017-07-20 16:29:05 +02:00
case reflect.Struct:
return structValue{wrapperValue{value}}
default:
return wrapperValue{value}
}
}
2017-07-22 14:36:40 +02:00
const (
firstKey = "first"
lastKey = "last"
sizeKey = "size"
)
2017-08-09 14:16:36 +02:00
// embed this in a struct to "inherit" default implementations of the Value interface
type valueEmbed struct{}
func (v valueEmbed) Equal(Value) bool { return false }
func (v valueEmbed) Less(Value) bool { return false }
func (v valueEmbed) IndexValue(Value) Value { return nilValue }
func (v valueEmbed) Contains(Value) bool { return false }
func (v valueEmbed) Int() int { panic(conversionError("", v, reflect.TypeOf(1))) }
func (v valueEmbed) PropertyValue(Value) Value { return nilValue }
func (v valueEmbed) Test() bool { return true }
2017-08-09 14:16:36 +02:00
// A wrapperValue wraps a Go value.
type wrapperValue struct{ value interface{} }
2017-08-09 14:16:36 +02:00
func (v wrapperValue) Equal(other Value) bool { return Equal(v.value, other.Interface()) }
func (v wrapperValue) Less(other Value) bool { return Less(v.value, other.Interface()) }
func (v wrapperValue) IndexValue(Value) Value { return nilValue }
func (v wrapperValue) Contains(Value) bool { return false }
2017-08-09 14:16:36 +02:00
func (v wrapperValue) Interface() interface{} { return v.value }
func (v wrapperValue) PropertyValue(Value) Value { return nilValue }
2017-08-09 14:16:36 +02:00
func (v wrapperValue) Test() bool { return v.value != nil && v.value != false }
func (v wrapperValue) Int() int {
2017-08-09 14:16:36 +02:00
if n, ok := v.value.(int); ok {
return n
}
2017-08-09 14:16:36 +02:00
panic(conversionError("", v.value, reflect.TypeOf(1)))
}
2017-07-22 14:36:40 +02:00
// interned values
var nilValue = wrapperValue{nil}
var falseValue = wrapperValue{false}
var trueValue = wrapperValue{true}
2017-08-09 14:16:36 +02:00
var zeroValue = wrapperValue{0}
var oneValue = wrapperValue{1}
2017-08-09 14:16:36 +02:00
// container values
type arrayValue struct{ wrapperValue }
type mapValue struct{ wrapperValue }
type stringValue struct{ wrapperValue }
2017-08-23 21:56:18 +02:00
func (av arrayValue) Contains(ev Value) bool {
ar := reflect.ValueOf(av.value)
e := ev.Interface()
for i, len := 0, ar.Len(); i < len; i++ {
if Equal(ar.Index(i).Interface(), e) {
return true
}
}
return false
}
2017-08-23 21:56:18 +02:00
func (av arrayValue) IndexValue(iv Value) Value {
ar := reflect.ValueOf(av.value)
2017-07-21 22:23:08 +02:00
var n int
2017-08-23 21:56:18 +02:00
switch ix := iv.Interface().(type) {
2017-07-21 22:23:08 +02:00
case int:
n = ix
case float32:
2017-08-09 14:16:36 +02:00
// Ruby array indexing truncates floats
2017-07-21 22:23:08 +02:00
n = int(ix)
case float64:
n = int(ix)
default:
return nilValue
}
if n < 0 {
2017-08-23 21:56:18 +02:00
n += ar.Len()
2017-07-21 22:23:08 +02:00
}
2017-08-23 21:56:18 +02:00
if 0 <= n && n < ar.Len() {
return ValueOf(ar.Index(n).Interface())
}
return nilValue
}
2017-08-23 21:56:18 +02:00
func (av arrayValue) PropertyValue(iv Value) Value {
ar := reflect.ValueOf(av.value)
switch iv.Interface() {
2017-07-20 15:37:56 +02:00
case firstKey:
2017-08-23 21:56:18 +02:00
if ar.Len() > 0 {
return ValueOf(ar.Index(0).Interface())
}
2017-07-20 15:37:56 +02:00
case lastKey:
2017-08-23 21:56:18 +02:00
if ar.Len() > 0 {
return ValueOf(ar.Index(ar.Len() - 1).Interface())
}
2017-07-20 15:37:56 +02:00
case sizeKey:
2017-08-23 21:56:18 +02:00
return ValueOf(ar.Len())
}
return nilValue
}
2017-08-23 21:56:18 +02:00
func (mv mapValue) Contains(iv Value) bool {
mr := reflect.ValueOf(mv.value)
ir := reflect.ValueOf(iv.Interface())
if ir.IsValid() && mr.Type().Key() == ir.Type() {
return mr.MapIndex(ir).IsValid()
2017-08-09 14:16:36 +02:00
}
return false
}
2017-08-23 21:56:18 +02:00
func (mv mapValue) IndexValue(iv Value) Value {
mr := reflect.ValueOf(mv.value)
ir := reflect.ValueOf(iv.Interface())
kt := mr.Type().Key()
if ir.IsValid() && ir.Type().ConvertibleTo(kt) && ir.Type().Comparable() {
er := mr.MapIndex(ir.Convert(kt))
if er.IsValid() {
return ValueOf(er.Interface())
2017-08-09 14:16:36 +02:00
}
}
return nilValue
}
2017-08-23 21:56:18 +02:00
func (mv mapValue) PropertyValue(iv Value) Value {
mr := reflect.ValueOf(mv.Interface())
ir := reflect.ValueOf(iv.Interface())
if !ir.IsValid() {
2017-08-08 23:15:29 +02:00
return nilValue
}
er := mr.MapIndex(ir)
switch {
case er.IsValid():
return ValueOf(er.Interface())
2017-08-23 21:56:18 +02:00
case iv.Interface() == sizeKey:
return ValueOf(mr.Len())
default:
return nilValue
}
}
2017-08-23 21:56:18 +02:00
func (sv stringValue) Contains(substr Value) bool {
2017-08-09 14:16:36 +02:00
s, ok := substr.Interface().(string)
if !ok {
s = fmt.Sprint(substr.Interface())
}
2017-08-23 21:56:18 +02:00
return strings.Contains(sv.value.(string), s)
2017-08-09 14:16:36 +02:00
}
2017-08-23 21:56:18 +02:00
func (sv stringValue) PropertyValue(iv Value) Value {
if iv.Interface() == sizeKey {
return ValueOf(len(sv.value.(string)))
}
return nilValue
}