2017-07-01 01:40:52 +02:00
|
|
|
|
package filters
|
2017-06-11 20:57:25 +02:00
|
|
|
|
|
|
|
|
|
import (
|
2017-06-28 20:42:04 +02:00
|
|
|
|
"fmt"
|
2017-07-01 04:38:27 +02:00
|
|
|
|
"math/rand"
|
2017-06-11 20:57:25 +02:00
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
2017-06-16 04:30:53 +02:00
|
|
|
|
"time"
|
2017-06-11 20:57:25 +02:00
|
|
|
|
|
2022-01-29 20:17:23 +01:00
|
|
|
|
"github.com/danog/gojekyll/config"
|
|
|
|
|
"github.com/danog/liquid"
|
2017-06-16 22:47:49 +02:00
|
|
|
|
"github.com/stretchr/testify/require"
|
2017-06-11 20:57:25 +02:00
|
|
|
|
)
|
|
|
|
|
|
2017-06-26 19:17:33 +02:00
|
|
|
|
var filterTests = []struct{ in, expected string }{
|
2017-06-28 22:55:15 +02:00
|
|
|
|
// dates
|
2017-07-07 00:50:56 +02:00
|
|
|
|
// FIXME date_to_xmlschema should use local timezone?
|
2017-06-29 15:41:33 +02:00
|
|
|
|
{`{{ time | date_to_xmlschema }}`, "2008-11-07T13:07:54+00:00"},
|
|
|
|
|
{`{{ time | date_to_rfc822 }}`, "07 Nov 08 13:07 UTC"},
|
|
|
|
|
{`{{ time | date_to_string }}`, "07 Nov 2008"},
|
|
|
|
|
{`{{ time | date_to_long_string }}`, "07 November 2008"},
|
2017-06-28 22:55:15 +02:00
|
|
|
|
|
|
|
|
|
// arrays
|
2017-07-01 04:38:27 +02:00
|
|
|
|
{`{{ array | array_to_sentence_string }}`, "first, second, and third"},
|
|
|
|
|
{`{{ array | sample }}`, "third"},
|
|
|
|
|
|
2017-08-20 17:38:44 +02:00
|
|
|
|
{`{{ site.members | group_by: "graduation_year" | map: "name" | sort | join }}`, "2013 2014 2015"},
|
2017-07-13 18:56:38 +02:00
|
|
|
|
{`{{ site.members | group_by_exp: "item", "item.graduation_year" | size }}`, "4"},
|
2017-06-29 15:41:33 +02:00
|
|
|
|
|
|
|
|
|
// TODO what is the default for nil first?
|
|
|
|
|
{`{{ animals | sort | join: ", " }}`, "Sally Snake, giraffe, octopus, zebra"},
|
2017-08-20 17:38:44 +02:00
|
|
|
|
{`{{ site.pages | sort: "weight" | map: "name" | join }}`, "b a d c"},
|
|
|
|
|
{`{{ site.pages | sort: "weight", true | map: "name" | join }}`, "b a d c"},
|
|
|
|
|
{`{{ site.pages | sort: "weight", false | map: "name" | join }}`, "a d c b"},
|
2017-06-29 15:41:33 +02:00
|
|
|
|
|
2017-06-30 18:21:55 +02:00
|
|
|
|
{`{{ site.members | where: "graduation_year", "2014" | map: "name" }}`, "Alan"},
|
|
|
|
|
{`{{ site.members | where_exp: "item", "item.graduation_year == 2014" | map: "name" }}`, "Alan"},
|
|
|
|
|
{`{{ site.members | where_exp: "item", "item.graduation_year < 2014" | map: "name" }}`, "Alonzo"},
|
2017-08-20 17:38:44 +02:00
|
|
|
|
{`{{ site.members | where_exp: "item", "item.name contains 'Al'" | map: "name" | join }}`, "Alonzo Alan"},
|
2017-06-29 15:41:33 +02:00
|
|
|
|
|
2017-08-20 17:38:44 +02:00
|
|
|
|
{`{{ page.tags | push: 'Spokane' | join }}`, "Seattle Tacoma Spokane"},
|
2017-07-07 00:50:56 +02:00
|
|
|
|
{`{{ page.tags | pop }}`, "Seattle"},
|
|
|
|
|
{`{{ page.tags | shift }}`, "Tacoma"},
|
2017-08-20 17:38:44 +02:00
|
|
|
|
{`{{ page.tags | unshift: "Olympia" | join }}`, "Olympia Seattle Tacoma"},
|
2017-06-28 22:55:15 +02:00
|
|
|
|
|
|
|
|
|
// strings
|
|
|
|
|
{`{{ "/assets/style.css" | relative_url }}`, "/my-baseurl/assets/style.css"},
|
|
|
|
|
{`{{ "/assets/style.css" | absolute_url }}`, "http://example.com/my-baseurl/assets/style.css"},
|
2017-06-29 15:41:33 +02:00
|
|
|
|
{`{{ "Markdown with _emphasis_ and *bold*." | markdownify }}`, "<p>Markdown with <em>emphasis</em> and <em>bold</em>.</p>"},
|
|
|
|
|
{`{{ obj | jsonify }}`, `{"a":[1,2,3,4]}`},
|
2017-08-20 17:38:44 +02:00
|
|
|
|
{`{{ site.pages | map: "name" | join }}`, "a b c d"},
|
|
|
|
|
{`{{ site.pages | filter: "weight" | map: "name" | join }}`, "a c d"},
|
2017-07-01 04:38:27 +02:00
|
|
|
|
{"{{ ws | normalize_whitespace }}", "a b c"},
|
2017-06-29 15:41:33 +02:00
|
|
|
|
{`{{ "123" | to_integer | type }}`, "int"},
|
|
|
|
|
{`{{ false | to_integer }}`, "0"},
|
|
|
|
|
{`{{ true | to_integer }}`, "1"},
|
|
|
|
|
{`{{ "here are some words" | number_of_words}}`, "4"},
|
2017-07-01 04:38:27 +02:00
|
|
|
|
|
|
|
|
|
{`{{ "The _config.yml file" | slugify }}`, "the-config-yml-file"},
|
|
|
|
|
{`{{ "The _config.yml file" | slugify: 'none' }}`, "the _config.yml file"},
|
|
|
|
|
{`{{ "The _config.yml file" | slugify: 'raw' }}`, "the-_config.yml-file"},
|
|
|
|
|
{`{{ "The _config.yml file" | slugify: 'default' }}`, "the-config-yml-file"},
|
|
|
|
|
{`{{ "The _config.yml file" | slugify: 'pretty' }}`, "the-_config.yml-file"},
|
|
|
|
|
|
2017-07-15 16:59:55 +02:00
|
|
|
|
// {`{{ "nav\n\tmargin: 0" | sassify }}`, "nav {\n margin: 0; }"},
|
|
|
|
|
{`{{ "nav {margin: 0}" | scssify }}`, "nav {\n margin: 0; }"},
|
|
|
|
|
|
2017-07-09 19:00:25 +02:00
|
|
|
|
{`{{ "smartify single 'quotes' here" | smartify }}`, "smartify single ‘quotes’ here"},
|
|
|
|
|
{`{{ 'smartify double "quotes" here' | smartify }}`, "smartify double “quotes” here"},
|
|
|
|
|
{"{{ \"smartify ``backticks''\" | smartify }}", "smartify “backticks”"},
|
|
|
|
|
{`{{ "smartify it's they're" | smartify }}`, "smartify it’s they’re"},
|
|
|
|
|
{`{{ "smartify ... (c) (r) (tm) -- ---" | smartify }}`, "smartify … © ® ™ – —"},
|
|
|
|
|
|
2017-07-10 23:16:53 +02:00
|
|
|
|
{`{{ "foo, bar; baz?" | cgi_escape }}`, "foo%2C+bar%3B+baz%3F"},
|
2017-06-29 15:41:33 +02:00
|
|
|
|
{`{{ "1 < 2 & 3" | xml_escape }}`, "1 < 2 & 3"},
|
2017-07-10 23:16:53 +02:00
|
|
|
|
|
|
|
|
|
// Jekyll produces the first. I believe the second is acceptable.
|
2017-06-29 15:41:33 +02:00
|
|
|
|
// {`{{ "http://foo.com/?q=foo, \bar?" | uri_escape }}`, "http://foo.com/?q=foo,%20%5Cbar?"},
|
2017-07-10 23:16:53 +02:00
|
|
|
|
{`{{ "http://foo.com/?q=foo, \bar?" | uri_escape }}`, "http://foo.com/?q=foo%2C+%5Cbar%3F"},
|
2017-06-26 19:17:33 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-27 19:56:01 +02:00
|
|
|
|
var filterTestBindings = liquid.Bindings{
|
2017-06-29 15:41:33 +02:00
|
|
|
|
"animals": []string{"zebra", "octopus", "giraffe", "Sally Snake"},
|
2017-07-01 04:38:27 +02:00
|
|
|
|
"array": []string{"first", "second", "third"},
|
2017-06-26 19:17:33 +02:00
|
|
|
|
"obj": map[string]interface{}{
|
|
|
|
|
"a": []int{1, 2, 3, 4},
|
|
|
|
|
},
|
2017-06-29 15:41:33 +02:00
|
|
|
|
"page": map[string]interface{}{
|
|
|
|
|
"tags": []string{"Seattle", "Tacoma"},
|
|
|
|
|
},
|
|
|
|
|
"site": map[string]interface{}{
|
|
|
|
|
"members": []map[string]interface{}{
|
2017-06-30 18:21:55 +02:00
|
|
|
|
{"name": "Alonzo", "graduation_year": 2013},
|
|
|
|
|
{"name": "Alan", "graduation_year": 2014},
|
|
|
|
|
{"name": "Moses", "graduation_year": 2015},
|
|
|
|
|
{"name": "Haskell"},
|
2017-06-29 15:41:33 +02:00
|
|
|
|
},
|
|
|
|
|
"pages": []map[string]interface{}{
|
|
|
|
|
{"name": "a", "weight": 10},
|
|
|
|
|
{"name": "b"},
|
|
|
|
|
{"name": "c", "weight": 50},
|
|
|
|
|
{"name": "d", "weight": 30},
|
|
|
|
|
},
|
2017-06-28 20:42:04 +02:00
|
|
|
|
},
|
2017-06-28 22:55:15 +02:00
|
|
|
|
"time": timeMustParse("2008-11-07T13:07:54Z"),
|
2017-07-01 04:38:27 +02:00
|
|
|
|
"ws": "a b\n\t c",
|
2017-06-26 19:17:33 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFilters(t *testing.T) {
|
2023-03-03 21:52:31 +01:00
|
|
|
|
//nolint:staticcheck // Ignore this for now
|
2017-07-01 04:38:27 +02:00
|
|
|
|
rand.Seed(1)
|
2017-06-28 20:42:04 +02:00
|
|
|
|
for i, test := range filterTests {
|
|
|
|
|
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
|
2017-08-27 19:56:01 +02:00
|
|
|
|
requireTemplateRender(t, test.in, filterTestBindings, test.expected)
|
2017-06-28 20:42:04 +02:00
|
|
|
|
})
|
2017-06-26 19:17:33 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-16 04:30:53 +02:00
|
|
|
|
// func TestXMLEscapeFilter(t *testing.T) {
|
2017-06-17 04:03:28 +02:00
|
|
|
|
// data := map[string]interface{}{
|
2017-06-16 04:30:53 +02:00
|
|
|
|
// "obj": map[string]interface{}{
|
|
|
|
|
// "a": []int{1, 2, 3, 4},
|
|
|
|
|
// },
|
|
|
|
|
// }
|
2017-06-16 22:47:49 +02:00
|
|
|
|
// requireTemplateRender(t, `{{obj | xml_escape }}`, data, `{"ak":[1,2,3,4]}`)
|
2017-06-16 04:30:53 +02:00
|
|
|
|
// }
|
|
|
|
|
|
2017-06-15 16:17:30 +02:00
|
|
|
|
func TestWhereExpFilter(t *testing.T) {
|
2017-06-11 20:57:25 +02:00
|
|
|
|
var tmpl = `
|
|
|
|
|
{% assign filtered = array | where_exp: "n", "n > 2" %}
|
|
|
|
|
{% for item in filtered %}{{item}}{% endfor %}
|
|
|
|
|
`
|
2017-06-17 04:03:28 +02:00
|
|
|
|
data := map[string]interface{}{
|
2017-06-11 20:57:25 +02:00
|
|
|
|
"array": []int{1, 2, 3, 4},
|
|
|
|
|
}
|
2017-06-16 22:47:49 +02:00
|
|
|
|
requireTemplateRender(t, tmpl, data, "34")
|
2017-06-11 20:57:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-07 18:10:03 +02:00
|
|
|
|
func TestWhereExpFilter_objects(t *testing.T) {
|
2017-06-11 20:57:25 +02:00
|
|
|
|
var tmpl = `
|
|
|
|
|
{% assign filtered = array | where_exp: "item", "item.flag == true" %}
|
|
|
|
|
{% for item in filtered %}{{item.name}}{% endfor %}
|
|
|
|
|
`
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"array": []map[string]interface{}{
|
2017-06-13 23:19:05 +02:00
|
|
|
|
{
|
2017-06-11 20:57:25 +02:00
|
|
|
|
"name": "A",
|
|
|
|
|
"flag": true,
|
|
|
|
|
},
|
2017-06-13 23:19:05 +02:00
|
|
|
|
{
|
2017-06-11 20:57:25 +02:00
|
|
|
|
"name": "B",
|
|
|
|
|
"flag": false,
|
|
|
|
|
},
|
|
|
|
|
}}
|
2017-06-16 22:47:49 +02:00
|
|
|
|
requireTemplateRender(t, tmpl, data, "A")
|
2017-06-11 20:57:25 +02:00
|
|
|
|
}
|
2017-08-27 19:56:01 +02:00
|
|
|
|
|
|
|
|
|
func requireTemplateRender(t *testing.T, tmpl string, bindings liquid.Bindings, expected string) {
|
|
|
|
|
engine := liquid.NewEngine()
|
|
|
|
|
cfg := config.Default()
|
|
|
|
|
cfg.BaseURL = "/my-baseurl"
|
|
|
|
|
cfg.AbsoluteURL = "http://example.com"
|
|
|
|
|
AddJekyllFilters(engine, &cfg)
|
|
|
|
|
data, err := engine.ParseAndRender([]byte(tmpl), bindings)
|
|
|
|
|
require.NoErrorf(t, err, tmpl)
|
|
|
|
|
require.Equalf(t, expected, strings.TrimSpace(string(data)), tmpl)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func timeMustParse(s string) time.Time {
|
|
|
|
|
t, err := time.Parse(time.RFC3339, s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
return t
|
|
|
|
|
}
|