1
0
mirror of https://github.com/danog/liquid.git synced 2024-12-02 15:17:47 +01:00
liquid/filters/standard_filters.go

276 lines
7.1 KiB
Go
Raw Normal View History

2017-08-08 22:42:32 +02:00
// Package filters is an internal package that defines the standard Liquid filters.
2017-06-27 18:06:24 +02:00
package filters
import (
"encoding/json"
"fmt"
2017-06-28 21:33:13 +02:00
"html"
2017-06-28 19:28:55 +02:00
"math"
"net/url"
"reflect"
2017-06-28 21:33:13 +02:00
"regexp"
2017-06-27 18:06:24 +02:00
"strings"
"time"
2017-06-28 19:28:55 +02:00
"unicode"
2017-07-01 16:02:00 +02:00
"unicode/utf8"
2017-06-27 18:06:24 +02:00
2017-07-28 00:11:37 +02:00
"github.com/osteele/liquid/values"
2017-08-11 17:55:37 +02:00
"github.com/osteele/tuesday"
2017-06-27 18:06:24 +02:00
)
2017-07-07 13:30:32 +02:00
// A FilterDictionary holds filters.
type FilterDictionary interface {
AddFilter(string, interface{})
}
2017-07-01 16:36:47 +02:00
// AddStandardFilters defines the standard Liquid filters.
2017-07-07 13:30:32 +02:00
func AddStandardFilters(fd FilterDictionary) { // nolint: gocyclo
2017-08-18 16:35:15 +02:00
// value filters
2017-07-07 13:30:32 +02:00
fd.AddFilter("default", func(value, defaultValue interface{}) interface{} {
2017-07-28 00:11:37 +02:00
if value == nil || value == false || values.IsEmpty(value) {
2017-06-29 19:41:04 +02:00
value = defaultValue
}
2017-06-29 19:41:04 +02:00
return value
})
2017-08-18 16:35:15 +02:00
// array filters
2017-08-21 15:50:29 +02:00
fd.AddFilter("compact", func(a []interface{}) (result []interface{}) {
for _, item := range a {
2017-06-29 19:41:04 +02:00
if item != nil {
2017-08-18 16:35:15 +02:00
result = append(result, item)
2017-06-28 20:41:46 +02:00
}
}
2017-08-18 16:35:15 +02:00
return
2017-06-28 20:41:46 +02:00
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("join", joinFilter)
2017-08-21 15:50:29 +02:00
fd.AddFilter("map", func(a []map[string]interface{}, key string) (result []interface{}) {
for _, obj := range a {
2017-08-18 16:35:15 +02:00
result = append(result, obj[key])
2017-06-28 20:41:46 +02:00
}
2017-08-18 16:35:15 +02:00
return result
2017-06-28 20:41:46 +02:00
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("reverse", reverseFilter)
fd.AddFilter("sort", sortFilter)
2017-06-28 20:41:46 +02:00
// https://shopify.github.io/liquid/ does not demonstrate first and last as filters,
// but https://help.shopify.com/themes/liquid/filters/array-filters does
2017-08-21 15:50:29 +02:00
fd.AddFilter("first", func(a []interface{}) interface{} {
if len(a) == 0 {
2017-06-28 20:41:46 +02:00
return nil
}
2017-08-21 15:50:29 +02:00
return a[0]
2017-06-28 20:41:46 +02:00
})
2017-08-21 15:50:29 +02:00
fd.AddFilter("last", func(a []interface{}) interface{} {
if len(a) == 0 {
2017-06-28 20:41:46 +02:00
return nil
}
2017-08-21 15:50:29 +02:00
return a[len(a)-1]
2017-06-28 20:41:46 +02:00
})
2017-07-08 20:29:33 +02:00
fd.AddFilter("uniq", uniqFilter)
2017-07-07 15:32:24 +02:00
2017-08-18 16:35:15 +02:00
// date filters
fd.AddFilter("date", func(t time.Time, format func(string) string) (string, error) {
f := format("%a, %b %d, %y")
2017-08-11 17:55:37 +02:00
return tuesday.Strftime(f, t)
2017-07-07 15:32:24 +02:00
})
2017-06-27 18:06:24 +02:00
2017-08-18 16:35:15 +02:00
// number filters
2017-07-07 13:30:32 +02:00
fd.AddFilter("abs", math.Abs)
2018-07-25 17:18:10 +02:00
fd.AddFilter("ceil", func(a float64) int {
return int(math.Ceil(a))
})
fd.AddFilter("floor", func(a float64) int {
return int(math.Floor(a))
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("modulo", math.Mod)
fd.AddFilter("minus", func(a, b float64) float64 {
2017-06-28 22:43:18 +02:00
return a - b
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("plus", func(a, b float64) float64 {
2017-06-28 22:43:18 +02:00
return a + b
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("times", func(a, b float64) float64 {
2017-06-28 22:43:18 +02:00
return a * b
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("divided_by", func(a float64, b interface{}) interface{} {
2017-07-07 15:32:24 +02:00
switch q := b.(type) {
2017-06-28 22:43:18 +02:00
case int, int16, int32, int64:
2017-07-07 15:32:24 +02:00
return int(a) / q.(int)
2017-06-28 22:43:18 +02:00
case float32, float64:
2017-06-29 18:26:04 +02:00
return a / b.(float64)
2017-06-28 22:43:18 +02:00
default:
return nil
}
})
fd.AddFilter("round", func(n float64, places func(int) int) float64 {
pl := places(0)
2017-06-28 22:43:18 +02:00
exp := math.Pow10(pl)
return math.Floor(n*exp+0.5) / exp
})
2017-06-28 19:28:55 +02:00
2017-08-18 16:35:15 +02:00
// sequence filters
2017-07-28 00:11:37 +02:00
fd.AddFilter("size", values.Length)
2017-06-28 20:41:46 +02:00
2017-08-18 16:35:15 +02:00
// string filters
2017-07-07 13:30:32 +02:00
fd.AddFilter("append", func(s, suffix string) string {
2017-06-28 19:28:55 +02:00
return s + suffix
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("capitalize", func(s, suffix string) string {
2017-08-18 16:35:15 +02:00
if len(s) == 0 {
2017-06-28 19:28:55 +02:00
return s
}
return strings.ToUpper(s[:1]) + s[1:]
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("downcase", func(s, suffix string) string {
2017-06-28 19:28:55 +02:00
return strings.ToLower(s)
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("escape", html.EscapeString)
fd.AddFilter("escape_once", func(s, suffix string) string {
2017-06-28 21:33:13 +02:00
return html.EscapeString(html.UnescapeString(s))
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("newline_to_br", func(s string) string {
2017-06-28 21:33:13 +02:00
return strings.Replace(s, "\n", "<br />", -1)
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("prepend", func(s, prefix string) string {
2017-06-28 19:28:55 +02:00
return prefix + s
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("remove", func(s, old string) string {
2017-06-28 19:28:55 +02:00
return strings.Replace(s, old, "", -1)
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("remove_first", func(s, old string) string {
2017-06-28 19:28:55 +02:00
return strings.Replace(s, old, "", 1)
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("replace", func(s, old, new string) string {
2017-06-28 19:28:55 +02:00
return strings.Replace(s, old, new, -1)
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("replace_first", func(s, old, new string) string {
2017-06-28 19:28:55 +02:00
return strings.Replace(s, old, new, 1)
})
2017-07-12 19:10:19 +02:00
fd.AddFilter("sort_natural", sortNaturalFilter)
fd.AddFilter("slice", func(s string, start int, length func(int) int) string {
2017-07-01 16:02:00 +02:00
// runes aren't bytes; don't use slice
n := length(1)
2017-06-28 20:41:46 +02:00
if start < 0 {
2017-07-01 16:02:00 +02:00
start = utf8.RuneCountInString(s) + start
2017-06-28 20:41:46 +02:00
}
2017-07-01 16:02:00 +02:00
p := regexp.MustCompile(fmt.Sprintf(`^.{%d}(.{0,%d}).*$`, start, n))
return p.ReplaceAllString(s, "$1")
2017-06-28 20:41:46 +02:00
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("split", splitFilter)
fd.AddFilter("strip_html", func(s string) string {
2017-06-28 21:33:13 +02:00
// TODO this probably isn't sufficient
return regexp.MustCompile(`<.*?>`).ReplaceAllString(s, "")
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("strip_newlines", func(s string) string {
2017-06-28 21:33:13 +02:00
return strings.Replace(s, "\n", "", -1)
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("strip", strings.TrimSpace)
fd.AddFilter("lstrip", func(s string) string {
2017-06-28 19:28:55 +02:00
return strings.TrimLeftFunc(s, unicode.IsSpace)
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("rstrip", func(s string) string {
2017-06-28 19:28:55 +02:00
return strings.TrimRightFunc(s, unicode.IsSpace)
})
2017-07-10 22:47:11 +02:00
fd.AddFilter("truncate", func(s string, length func(int) int, ellipsis func(string) string) string {
n := length(50)
el := ellipsis("...")
2017-07-01 16:02:00 +02:00
// runes aren't bytes; don't use slice
2017-07-10 22:47:11 +02:00
re := regexp.MustCompile(fmt.Sprintf(`^(.{%d})..{%d,}`, n-len(el), len(el)))
return re.ReplaceAllString(s, `$1`+el)
})
fd.AddFilter("truncatewords", func(s string, length func(int) int, ellipsis func(string) string) string {
el := ellipsis("...")
n := length(15)
re := regexp.MustCompile(fmt.Sprintf(`^(?:\s*\S+){%d}`, n))
m := re.FindString(s)
if m == "" {
return s
}
return m + el
2017-06-28 19:28:55 +02:00
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("upcase", func(s, suffix string) string {
2017-06-28 19:28:55 +02:00
return strings.ToUpper(s)
})
fd.AddFilter("url_encode", url.QueryEscape)
fd.AddFilter("url_decode", url.QueryUnescape)
2017-06-27 18:06:24 +02:00
2017-08-18 16:35:15 +02:00
// debugging filters
// inspect is from Jekyll
2017-07-07 13:30:32 +02:00
fd.AddFilter("inspect", func(value interface{}) string {
s, err := json.Marshal(value)
if err != nil {
return fmt.Sprintf("%#v", value)
}
return string(s)
})
2017-07-07 13:30:32 +02:00
fd.AddFilter("type", func(value interface{}) string {
2017-07-22 15:40:43 +02:00
return fmt.Sprintf("%T", value)
})
2017-06-27 18:06:24 +02:00
}
2017-08-21 15:50:29 +02:00
func joinFilter(a []interface{}, sep func(string) string) interface{} {
ss := make([]string, 0, len(a))
s := sep(" ")
2017-08-21 15:50:29 +02:00
for _, v := range a {
if v != nil {
ss = append(ss, fmt.Sprint(v))
}
2017-06-27 18:06:24 +02:00
}
2017-08-21 15:50:29 +02:00
return strings.Join(ss, s)
2017-06-27 18:06:24 +02:00
}
2017-08-21 15:50:29 +02:00
func reverseFilter(a []interface{}) interface{} {
result := make([]interface{}, len(a))
for i, x := range a {
2017-07-12 19:10:19 +02:00
result[len(result)-1-i] = x
2017-06-27 23:29:50 +02:00
}
2017-07-12 19:10:19 +02:00
return result
2017-06-27 23:29:50 +02:00
}
2017-09-04 23:51:51 +02:00
var wsre = regexp.MustCompile(`[[:space:]]+`)
2017-06-29 19:41:04 +02:00
func splitFilter(s, sep string) interface{} {
2017-07-12 19:10:19 +02:00
result := strings.Split(s, sep)
2017-09-04 23:51:51 +02:00
if sep == " " {
// Special case for Ruby, therefore Liquid
result = wsre.Split(s, -1)
}
// This matches Ruby / Liquid / Jekyll's observed behavior.
for len(result) > 0 && result[len(result)-1] == "" {
2017-07-12 19:10:19 +02:00
result = result[:len(result)-1]
2017-06-29 19:41:04 +02:00
}
2017-07-12 19:10:19 +02:00
return result
2017-06-27 18:06:24 +02:00
}
2017-07-08 20:29:33 +02:00
2017-08-21 15:50:29 +02:00
func uniqFilter(a []interface{}) (result []interface{}) {
seenMap := map[interface{}]bool{}
2017-07-08 20:29:33 +02:00
seen := func(item interface{}) bool {
if k := reflect.TypeOf(item).Kind(); k < reflect.Array || k == reflect.Ptr || k == reflect.UnsafePointer {
if seenMap[item] {
2017-07-08 20:29:33 +02:00
return true
}
seenMap[item] = true
return false
}
// the O(n^2) case:
for _, other := range result {
if eqItems(item, other) {
2017-07-08 20:29:33 +02:00
return true
}
}
return false
}
2017-08-21 15:50:29 +02:00
for _, item := range a {
if !seen(item) {
result = append(result, item)
2017-07-08 20:29:33 +02:00
}
}
return
}
func eqItems(a, b interface{}) bool {
if reflect.TypeOf(a).Comparable() && reflect.TypeOf(b).Comparable() {
return a == b
}
return reflect.DeepEqual(a, b)
2017-07-08 20:29:33 +02:00
}