2017-08-08 22:42:32 +02:00
|
|
|
// Package render is an internal package that renders a compiled template parse tree.
|
2017-07-04 17:03:18 +02:00
|
|
|
package render
|
2017-06-25 17:23:20 +02:00
|
|
|
|
2017-06-25 23:00:00 +02:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2017-06-29 19:41:26 +02:00
|
|
|
"reflect"
|
2017-08-18 16:43:31 +02:00
|
|
|
"time"
|
2017-07-03 18:00:43 +02:00
|
|
|
|
2017-07-28 00:11:37 +02:00
|
|
|
"github.com/osteele/liquid/values"
|
2017-06-25 23:00:00 +02:00
|
|
|
)
|
2017-06-25 17:23:20 +02:00
|
|
|
|
2017-07-06 15:21:26 +02:00
|
|
|
// Render renders the render tree.
|
2017-07-10 17:49:14 +02:00
|
|
|
func Render(node Node, w io.Writer, vars map[string]interface{}, c Config) Error {
|
2017-07-16 23:43:04 +02:00
|
|
|
tw := trimWriter{w: w}
|
2017-07-17 00:02:07 +02:00
|
|
|
if err := node.render(&tw, newNodeContext(vars, c)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := tw.Flush(); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return nil
|
2017-06-25 17:23:20 +02:00
|
|
|
}
|
|
|
|
|
2017-07-16 23:43:04 +02:00
|
|
|
// RenderASTSequence renders a sequence of nodes.
|
|
|
|
func (c nodeContext) RenderSequence(w io.Writer, seq []Node) Error {
|
|
|
|
tw := trimWriter{w: w}
|
|
|
|
for _, n := range seq {
|
|
|
|
if err := n.render(&tw, c); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-17 00:02:07 +02:00
|
|
|
}
|
|
|
|
if err := tw.Flush(); err != nil {
|
|
|
|
panic(err)
|
2017-07-16 23:43:04 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *BlockNode) render(w *trimWriter, ctx nodeContext) Error {
|
2017-07-14 19:01:04 +02:00
|
|
|
cd, ok := ctx.config.findBlockDef(n.Name)
|
|
|
|
if !ok || cd.parser == nil {
|
|
|
|
// this should have been detected during compilation; it's an implementation error if it happens here
|
2017-07-19 00:37:28 +02:00
|
|
|
panic(fmt.Errorf("undefined tag %q", n.Name))
|
2017-07-14 19:01:04 +02:00
|
|
|
}
|
|
|
|
renderer := n.renderer
|
|
|
|
if renderer == nil {
|
|
|
|
panic(fmt.Errorf("unset renderer for %v", n))
|
|
|
|
}
|
|
|
|
err := renderer(w, rendererContext{ctx, nil, n})
|
|
|
|
return wrapRenderError(err, n)
|
|
|
|
}
|
|
|
|
|
2017-07-16 23:43:04 +02:00
|
|
|
func (n *RawNode) render(w *trimWriter, ctx nodeContext) Error {
|
2017-07-14 19:01:04 +02:00
|
|
|
for _, s := range n.slices {
|
2017-07-16 03:13:21 +02:00
|
|
|
_, err := io.WriteString(w, s)
|
2017-06-27 23:40:15 +02:00
|
|
|
if err != nil {
|
2017-07-10 17:49:14 +02:00
|
|
|
return wrapRenderError(err, n)
|
2017-06-27 23:40:15 +02:00
|
|
|
}
|
2017-07-14 19:01:04 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-16 23:43:04 +02:00
|
|
|
func (n *ObjectNode) render(w *trimWriter, ctx nodeContext) Error {
|
|
|
|
w.TrimLeft(n.TrimLeft)
|
2017-07-14 19:01:04 +02:00
|
|
|
value, err := ctx.Evaluate(n.expr)
|
|
|
|
if err != nil {
|
2017-07-10 17:49:14 +02:00
|
|
|
return wrapRenderError(err, n)
|
2017-07-14 19:01:04 +02:00
|
|
|
}
|
2017-07-26 16:59:35 +02:00
|
|
|
if err := wrapRenderError(writeObject(w, value), n); err != nil {
|
2017-07-16 23:43:04 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
w.TrimRight(n.TrimRight)
|
|
|
|
return nil
|
2017-07-14 19:01:04 +02:00
|
|
|
}
|
|
|
|
|
2017-07-16 23:43:04 +02:00
|
|
|
func (n *SeqNode) render(w *trimWriter, ctx nodeContext) Error {
|
2017-07-14 19:01:04 +02:00
|
|
|
for _, c := range n.Children {
|
|
|
|
if err := c.render(w, ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-27 23:40:15 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-16 23:43:04 +02:00
|
|
|
func (n *TagNode) render(w *trimWriter, ctx nodeContext) Error {
|
|
|
|
w.TrimLeft(n.TrimLeft)
|
|
|
|
err := wrapRenderError(n.renderer(w, rendererContext{ctx, n, nil}), n)
|
|
|
|
w.TrimRight(n.TrimRight)
|
|
|
|
return err
|
2017-07-14 19:01:04 +02:00
|
|
|
}
|
|
|
|
|
2017-07-16 23:43:04 +02:00
|
|
|
func (n *TextNode) render(w *trimWriter, ctx nodeContext) Error {
|
2017-07-16 03:13:21 +02:00
|
|
|
_, err := io.WriteString(w, n.Source)
|
2017-07-14 19:01:04 +02:00
|
|
|
return wrapRenderError(err, n)
|
|
|
|
}
|
|
|
|
|
2017-06-30 20:51:21 +02:00
|
|
|
// writeObject writes a value used in an object node
|
2017-07-26 16:59:35 +02:00
|
|
|
func writeObject(w io.Writer, value interface{}) error {
|
2017-07-28 00:11:37 +02:00
|
|
|
value = values.ToLiquid(value)
|
2017-06-29 19:41:26 +02:00
|
|
|
if value == nil {
|
2017-06-27 23:29:50 +02:00
|
|
|
return nil
|
|
|
|
}
|
2017-07-23 17:49:09 +02:00
|
|
|
switch value := value.(type) {
|
2017-08-18 16:43:31 +02:00
|
|
|
case time.Time:
|
|
|
|
_, err := io.WriteString(w, value.Format("2006-01-02 15:04:05 -0700"))
|
|
|
|
return err
|
2017-07-23 17:49:09 +02:00
|
|
|
case []byte:
|
|
|
|
_, err := w.Write(value)
|
|
|
|
return err
|
2017-07-26 16:59:35 +02:00
|
|
|
// there used be a case on fmt.Stringer here, but fmt.Sprint produces better results than obj.Write
|
|
|
|
// for instances of error and *string
|
2017-07-23 17:49:09 +02:00
|
|
|
}
|
2017-06-29 19:41:26 +02:00
|
|
|
rt := reflect.ValueOf(value)
|
|
|
|
switch rt.Kind() {
|
|
|
|
case reflect.Array, reflect.Slice:
|
|
|
|
for i := 0; i < rt.Len(); i++ {
|
|
|
|
item := rt.Index(i)
|
|
|
|
if item.IsValid() {
|
2017-07-26 16:59:35 +02:00
|
|
|
if err := writeObject(w, item.Interface()); err != nil {
|
2017-06-29 19:41:26 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2017-07-26 16:59:35 +02:00
|
|
|
case reflect.Ptr:
|
|
|
|
return writeObject(w, reflect.ValueOf(value).Elem())
|
2017-06-29 19:41:26 +02:00
|
|
|
default:
|
2017-07-16 03:13:21 +02:00
|
|
|
_, err := io.WriteString(w, fmt.Sprint(value))
|
2017-06-29 19:41:26 +02:00
|
|
|
return err
|
|
|
|
}
|
2017-06-25 17:23:20 +02:00
|
|
|
}
|