mirror of
https://github.com/danog/gojekyll.git
synced 2024-11-27 03:24:40 +01:00
Switch from acstech/liquid -> osteele/liquid
This commit is contained in:
parent
60ac92497e
commit
c41a7b975c
@ -30,3 +30,6 @@ type RemoteEngine interface {
|
|||||||
// IncludeTagHandler resolves the filename in a Liquid include tag into the expanded content
|
// IncludeTagHandler resolves the filename in a Liquid include tag into the expanded content
|
||||||
// of the included file.
|
// of the included file.
|
||||||
type IncludeTagHandler func(string, io.Writer, map[string]interface{}) error
|
type IncludeTagHandler func(string, io.Writer, map[string]interface{}) error
|
||||||
|
|
||||||
|
// A LinkTagHandler given an include tag file name returns a URL.
|
||||||
|
type LinkTagHandler func(string) (string, bool)
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
package liquid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/acstech/liquid"
|
|
||||||
"github.com/acstech/liquid/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
liquid.Tags["link"] = linkFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
// A LinkTagHandler given an include tag file name returns a URL.
|
|
||||||
type LinkTagHandler func(string) (string, bool)
|
|
||||||
|
|
||||||
var currentLinkHandler LinkTagHandler
|
|
||||||
|
|
||||||
// SetLinkHandler sets the function that resolves an include tag file name to a URL.
|
|
||||||
func SetLinkHandler(h LinkTagHandler) {
|
|
||||||
currentLinkHandler = h
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link tag data, for passing information from the factory to Execute
|
|
||||||
type linkData struct {
|
|
||||||
standaloneTag
|
|
||||||
filename string
|
|
||||||
}
|
|
||||||
|
|
||||||
// linkFactory creates a link tag
|
|
||||||
func linkFactory(p *core.Parser, config *core.Configuration) (core.Tag, error) {
|
|
||||||
start := p.Position
|
|
||||||
p.SkipPastTag()
|
|
||||||
end := p.Position - 2
|
|
||||||
filename := strings.TrimSpace(string(p.Data[start:end]))
|
|
||||||
return &linkData{standaloneTag{"link"}, filename}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute is required by the Liquid tag interface
|
|
||||||
func (l *linkData) Execute(writer io.Writer, data map[string]interface{}) core.ExecuteState {
|
|
||||||
url, ok := currentLinkHandler(l.filename)
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Errorf("link tag: %s not found", l.filename))
|
|
||||||
}
|
|
||||||
if _, err := writer.Write([]byte(url)); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return core.Normal
|
|
||||||
}
|
|
@ -3,23 +3,15 @@ package liquid
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/acstech/liquid/core"
|
"github.com/osteele/liquid/expressions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func (e *LocalWrapperEngine) addJekyllFilters() {
|
||||||
core.RegisterFilter("array_to_sentence_string", arrayToSentenceStringFactory)
|
e.engine.DefineFilter("array_to_sentence_string", arrayToSentenceStringFilter)
|
||||||
core.RegisterFilter("date_to_rfc822", dateToRFC822Factory)
|
|
||||||
core.RegisterFilter("jsonify", jsonifyFactory)
|
|
||||||
core.RegisterFilter("xml_escape", xmlEscapeFactory)
|
|
||||||
core.RegisterFilter("where_exp", whereExpFactory)
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddStandardFilters(e *LocalWrapperEngine) {
|
|
||||||
e.engine.DefineFilter("date_to_rfc822", func(date time.Time) interface{} {
|
e.engine.DefineFilter("date_to_rfc822", func(date time.Time) interface{} {
|
||||||
return date.Format(time.RFC822)
|
return date.Format(time.RFC822)
|
||||||
// Out: Mon, 07 Nov 2008 13:07:54 -0800
|
// Out: Mon, 07 Nov 2008 13:07:54 -0800
|
||||||
@ -28,111 +20,81 @@ func AddStandardFilters(e *LocalWrapperEngine) {
|
|||||||
return date.Format("02 Jan 2005")
|
return date.Format("02 Jan 2005")
|
||||||
// Out: 07 Nov 2008
|
// Out: 07 Nov 2008
|
||||||
})
|
})
|
||||||
}
|
e.engine.DefineFilter("jsonify", func(value interface{}) interface{} {
|
||||||
|
s, err := json.Marshal(value)
|
||||||
func arrayToSentenceStringFactory(parameters []core.Value) core.Filter {
|
|
||||||
conj := "and "
|
|
||||||
if len(parameters) > 0 {
|
|
||||||
conj = fmt.Sprint(parameters[0]) + " "
|
|
||||||
}
|
|
||||||
return func(input interface{}, data map[string]interface{}) interface{} {
|
|
||||||
rt := reflect.ValueOf(input)
|
|
||||||
switch rt.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
ar := make([]string, rt.Len())
|
|
||||||
for i := 0; i < rt.Len(); i++ {
|
|
||||||
ar[i] = fmt.Sprint(rt.Index(i))
|
|
||||||
if i == rt.Len()-1 {
|
|
||||||
ar[i] = conj + ar[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(ar, ", ")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dateToRFC822Factory(parameters []core.Value) core.Filter {
|
|
||||||
if len(parameters) != 0 {
|
|
||||||
panic("The date_to_rfc822 filter doesn't accept parameters")
|
|
||||||
}
|
|
||||||
return func(input interface{}, data map[string]interface{}) interface{} {
|
|
||||||
date := input.(time.Time) // TODO if a string, try parsing it
|
|
||||||
return date.Format(time.RFC822)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonifyFactory(parameters []core.Value) core.Filter {
|
|
||||||
if len(parameters) != 0 {
|
|
||||||
panic("The jsonify filter doesn't accept parameters")
|
|
||||||
}
|
|
||||||
return func(input interface{}, data map[string]interface{}) interface{} {
|
|
||||||
s, err := json.Marshal(input)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
})
|
||||||
|
e.engine.DefineFilter("where_exp", whereExpFilter)
|
||||||
|
e.engine.DefineFilter("xml_escape", xml.Marshal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func xmlEscapeFactory(parameters []core.Value) core.Filter {
|
func arrayToSentenceStringFilter(value []string, conjunction interface{}) interface{} {
|
||||||
if len(parameters) != 0 {
|
conj, ok := conjunction.(string)
|
||||||
panic("The xml_escape filter doesn't accept parameters")
|
if !ok {
|
||||||
|
conj = "and "
|
||||||
}
|
}
|
||||||
return func(input interface{}, data map[string]interface{}) interface{} {
|
rt := reflect.ValueOf(value)
|
||||||
s, err := xml.Marshal(input)
|
ar := make([]string, rt.Len())
|
||||||
// TODO can't handle maps
|
for i, v := range value {
|
||||||
// eval https://github.com/clbanning/mxj
|
ar[i] = v
|
||||||
// adapt https://stackoverflow.com/questions/30928770/marshall-map-to-xml-in-go
|
if i == rt.Len()-1 {
|
||||||
if err != nil {
|
ar[i] = conj + v
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
return strings.Join(ar, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// WhereExpFactory implements the Jekyll `where_exp` filter
|
// func xmlEscapeFilter(value interface{}) interface{} {
|
||||||
func whereExpFactory(parameters []core.Value) core.Filter {
|
// data, err := xml.Marshal(value)
|
||||||
if len(parameters) != 2 {
|
// // TODO can't handle maps
|
||||||
panic("The were_exp filter requires two parameters")
|
// // eval https://github.com/clbanning/mxj
|
||||||
}
|
// // adapt https://stackoverflow.com/questions/30928770/marshall-map-to-xml-in-go
|
||||||
return (&whereExpFilter{parameters[0], parameters[1]}).run
|
// if err != nil {
|
||||||
}
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// return data
|
||||||
|
// }
|
||||||
|
|
||||||
type whereExpFilter struct {
|
func whereExpFilter(in []interface{}, name string, expr expressions.Closure) (interface{}, error) {
|
||||||
varName core.Value
|
rt := reflect.ValueOf(in)
|
||||||
expr core.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *whereExpFilter) run(input interface{}, data map[string]interface{}) interface{} {
|
|
||||||
rt := reflect.ValueOf(input)
|
|
||||||
switch rt.Kind() {
|
switch rt.Kind() {
|
||||||
case reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
case reflect.Array:
|
|
||||||
default:
|
default:
|
||||||
return input
|
return in, nil
|
||||||
}
|
|
||||||
|
|
||||||
varName := f.varName.Resolve(data).(string)
|
|
||||||
expr := f.expr.Resolve(data).(string)
|
|
||||||
p := core.NewParser([]byte(expr + "%}"))
|
|
||||||
condition, err := p.ReadConditionGroup()
|
|
||||||
// TODO assert we're at the end of the string
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := []interface{}{}
|
|
||||||
d := make(map[string]interface{})
|
|
||||||
for k, v := range data {
|
|
||||||
d[k] = v
|
|
||||||
}
|
}
|
||||||
|
out := []interface{}{}
|
||||||
for i := 0; i < rt.Len(); i++ {
|
for i := 0; i < rt.Len(); i++ {
|
||||||
item := rt.Index(i).Interface()
|
item := rt.Index(i).Interface()
|
||||||
d[varName] = item
|
value, err := expr.Bind(name, item).Evaluate()
|
||||||
if condition.IsTrue(d) {
|
if err != nil {
|
||||||
result = append(result, item)
|
return nil, err
|
||||||
|
}
|
||||||
|
if value != nil && value != false {
|
||||||
|
out = append(out, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func whereFilter(in []interface{}, key string, value interface{}) interface{} {
|
||||||
|
rt := reflect.ValueOf(in)
|
||||||
|
switch rt.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
default:
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
out := []interface{}{}
|
||||||
|
for i := 0; i < rt.Len(); i++ {
|
||||||
|
item := rt.Index(i)
|
||||||
|
if item.Kind() == reflect.Map && item.Type().Key().Kind() == reflect.String {
|
||||||
|
attr := item.MapIndex(reflect.ValueOf(key))
|
||||||
|
if attr != reflect.Zero(attr.Type()) && (value == nil || attr.Interface() == value) {
|
||||||
|
out = append(out, item.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package liquid
|
package liquid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/acstech/liquid"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,12 +36,11 @@ func TestFilters(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireTemplateRender(t *testing.T, tmpl string, data map[string]interface{}, expected string) {
|
func requireTemplateRender(t *testing.T, tmpl string, scope map[string]interface{}, expected string) {
|
||||||
template, err := liquid.ParseString(tmpl, nil)
|
engine := NewLocalWrapperEngine()
|
||||||
|
data, err := engine.ParseAndRender([]byte(tmpl), scope)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
writer := new(bytes.Buffer)
|
require.Equal(t, expected, strings.TrimSpace(string(data)))
|
||||||
template.Render(writer, data)
|
|
||||||
require.Equal(t, expected, strings.TrimSpace(writer.String()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// func TestXMLEscapeFilter(t *testing.T) {
|
// func TestXMLEscapeFilter(t *testing.T) {
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
package liquid
|
package liquid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/osteele/liquid"
|
"github.com/osteele/liquid"
|
||||||
"github.com/osteele/liquid/chunks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LocalWrapperEngine is a wrapper around osteele/liquid.
|
// LocalWrapperEngine is a wrapper around osteele/liquid.
|
||||||
@ -18,22 +14,8 @@ type LocalWrapperEngine struct {
|
|||||||
// NewLocalWrapperEngine creates a LocalEngine.
|
// NewLocalWrapperEngine creates a LocalEngine.
|
||||||
func NewLocalWrapperEngine() LocalEngine {
|
func NewLocalWrapperEngine() LocalEngine {
|
||||||
e := &LocalWrapperEngine{engine: liquid.NewEngine()}
|
e := &LocalWrapperEngine{engine: liquid.NewEngine()}
|
||||||
AddStandardFilters(e)
|
e.addJekyllFilters()
|
||||||
e.engine.DefineTag("link", func(filename string) (func(io.Writer, chunks.Context) error, error) {
|
e.addJekyllTags()
|
||||||
return func(w io.Writer, _ chunks.Context) error {
|
|
||||||
url, found := e.linkHandler(filename)
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("missing link filename: %s", filename)
|
|
||||||
}
|
|
||||||
_, err := w.Write([]byte(url))
|
|
||||||
return err
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
e.engine.DefineTag("include", func(filename string) (func(io.Writer, chunks.Context) error, error) {
|
|
||||||
return func(w io.Writer, ctx chunks.Context) error {
|
|
||||||
return e.includeTagHandler(filename, w, ctx.GetVariableMap())
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,10 +31,7 @@ func (e *LocalWrapperEngine) IncludeHandler(h IncludeTagHandler) {
|
|||||||
|
|
||||||
// Parse is a wrapper for liquid.Parse.
|
// Parse is a wrapper for liquid.Parse.
|
||||||
func (e *LocalWrapperEngine) Parse(source []byte) (Template, error) {
|
func (e *LocalWrapperEngine) Parse(source []byte) (Template, error) {
|
||||||
// fmt.Println("parse", string(source))
|
return e.engine.ParseTemplate(source)
|
||||||
t, err := e.engine.ParseTemplate(source)
|
|
||||||
// return &localTemplate{t}, err
|
|
||||||
return t, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAndRender parses and then renders the template.
|
// ParseAndRender parses and then renders the template.
|
||||||
@ -61,6 +40,5 @@ func (e *LocalWrapperEngine) ParseAndRender(source []byte, scope map[string]inte
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// fmt.Println("render", t)
|
|
||||||
return t.Render(scope)
|
return t.Render(scope)
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
package liquid
|
|
||||||
|
|
||||||
import "github.com/acstech/liquid/core"
|
|
||||||
|
|
||||||
type standaloneTag struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCode is required by the Liquid tag interface
|
|
||||||
func (l *standaloneTag) AddCode(code core.Code) {
|
|
||||||
panic("AddCode should not have been called on a standalone tag")
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSibling is required by the Liquid tag interface
|
|
||||||
func (l *standaloneTag) AddSibling(tag core.Tag) error {
|
|
||||||
panic("AddSibling should not have been called on a standalone tag")
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastSibling is required by the Liquid tag interface
|
|
||||||
func (l *standaloneTag) LastSibling() core.Tag {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name is required by the Liquid tag interface
|
|
||||||
func (l *standaloneTag) Name() string {
|
|
||||||
return l.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type is required by the Liquid tag interface
|
|
||||||
func (l *standaloneTag) Type() core.TagType {
|
|
||||||
return core.StandaloneTag
|
|
||||||
}
|
|
26
liquid/tags.go
Normal file
26
liquid/tags.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package liquid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/osteele/liquid/chunks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *LocalWrapperEngine) addJekyllTags() {
|
||||||
|
e.engine.DefineTag("link", func(filename string) (func(io.Writer, chunks.Context) error, error) {
|
||||||
|
return func(w io.Writer, _ chunks.Context) error {
|
||||||
|
url, found := e.linkHandler(filename)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("missing link filename: %s", filename)
|
||||||
|
}
|
||||||
|
_, err := w.Write([]byte(url))
|
||||||
|
return err
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
e.engine.DefineTag("include", func(filename string) (func(io.Writer, chunks.Context) error, error) {
|
||||||
|
return func(w io.Writer, ctx chunks.Context) error {
|
||||||
|
return e.includeTagHandler(filename, w, ctx.GetVariableMap())
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user