2017-07-04 17:03:18 +02:00
package render
2017-06-26 21:36:05 +02:00
import (
2017-07-04 17:41:45 +02:00
"bytes"
"io"
"io/ioutil"
2020-11-03 01:36:02 +01:00
"os"
2017-07-04 17:41:45 +02:00
"strings"
2022-01-06 18:43:17 +01:00
"github.com/danog/liquid/expressions"
2017-06-26 21:36:05 +02:00
)
2017-07-04 17:41:45 +02:00
// Context provides the rendering context for a tag renderer.
type Context interface {
2017-07-14 18:59:30 +02:00
// Get retrieves the value of a variable from the current lexical environment.
2017-07-04 17:41:45 +02:00
Get ( name string ) interface { }
2017-07-14 18:59:30 +02:00
// Errorf creates a SourceError, that includes the source location.
// Use this to distinguish errors in the template from implementation errors
// in the template engine.
2017-07-10 17:49:14 +02:00
Errorf ( format string , a ... interface { } ) Error
2017-07-14 18:59:30 +02:00
// Evaluate evaluates a compiled expression within the current lexical context.
Evaluate ( expressions . Expression ) ( interface { } , error )
// EvaluateString compiles and evaluates a string expression such as “x”, “x < 10", or “a.b | split | first | default: 10”, within the current lexical context.
EvaluateString ( string ) ( interface { } , error )
2017-07-10 13:51:51 +02:00
// ExpandTagArg renders the current tag argument string as a Liquid template.
2017-07-14 18:59:30 +02:00
// It enables the implementation of tags such as Jekyll's "{% include {{ page.my_variable }} %}" andjekyll-avatar's "{% avatar {{page.author}} %}".
2017-07-06 05:25:03 +02:00
ExpandTagArg ( ) ( string , error )
2017-07-14 18:59:30 +02:00
// InnerString is the rendered content of the current block.
// It's used in the implementation of the Liquid "capture" tag and the Jekyll "highlght" tag.
2017-07-04 17:41:45 +02:00
InnerString ( ) ( string , error )
2017-07-14 18:59:30 +02:00
// RenderBlock is used in the implementation of the built-in control flow tags.
// It's not guaranteed stable.
RenderBlock ( io . Writer , * BlockNode ) error
// RenderChildren is used in the implementation of the built-in control flow tags.
// It's not guaranteed stable.
2017-07-10 17:49:14 +02:00
RenderChildren ( io . Writer ) Error
2017-07-14 18:59:30 +02:00
// RenderFile parses and renders a template. It's used in the implementation of the {% include %} tag.
// RenderFile does not cache the compiled template.
2017-07-04 22:48:38 +02:00
RenderFile ( string , map [ string ] interface { } ) ( string , error )
2017-07-14 18:59:30 +02:00
// Set updates the value of a variable in the current lexical environment.
// It's used in the implementation of the {% assign %} and {% capture %} tags.
2017-07-04 22:48:38 +02:00
Set ( name string , value interface { } )
2017-07-10 13:51:51 +02:00
// SourceFile retrieves the value set by template.SetSourcePath.
2017-07-14 18:59:30 +02:00
// It's used in the implementation of the {% include %} tag.
2017-07-04 22:48:38 +02:00
SourceFile ( ) string
2017-07-10 13:51:51 +02:00
// TagArgs returns the text of the current tag, not including its name.
// For example, the arguments to {% my_tag a b c %} would be “a b c”.
2017-07-04 17:41:45 +02:00
TagArgs ( ) string
2017-07-14 18:59:30 +02:00
// TagName returns the name of the current tag; for example "my_tag" for {% my_tag a b c %}.
2017-07-04 17:41:45 +02:00
TagName ( ) string
2017-07-14 18:59:30 +02:00
// WrapError creates a new error that records the source location from the current context.
2017-07-10 17:49:14 +02:00
WrapError ( err error ) Error
2017-06-26 21:36:05 +02:00
}
2017-07-07 13:30:32 +02:00
type rendererContext struct {
2017-07-04 17:41:45 +02:00
ctx nodeContext
2017-07-06 15:40:37 +02:00
node * TagNode
2017-07-06 15:21:26 +02:00
cn * BlockNode
2017-06-26 21:36:05 +02:00
}
2017-07-10 17:49:14 +02:00
func ( c rendererContext ) Errorf ( format string , a ... interface { } ) Error {
2017-07-12 13:07:57 +02:00
return renderErrorf ( c . node , format , a ... )
2017-07-10 17:49:14 +02:00
}
func ( c rendererContext ) WrapError ( err error ) Error {
return wrapRenderError ( err , c . node )
}
2017-07-14 02:18:23 +02:00
func ( c rendererContext ) Evaluate ( expr expressions . Expression ) ( out interface { } , err error ) {
2017-07-04 17:41:45 +02:00
return c . ctx . Evaluate ( expr )
}
// EvaluateString evaluates an expression within the template context.
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) EvaluateString ( source string ) ( out interface { } , err error ) {
2017-07-14 02:18:23 +02:00
return expressions . EvaluateString ( source , expressions . NewContext ( c . ctx . bindings , c . ctx . config . Config . Config ) )
2017-07-04 17:41:45 +02:00
}
// Get gets a variable value within an evaluation context.
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) Get ( name string ) interface { } {
2017-07-04 17:41:45 +02:00
return c . ctx . bindings [ name ]
}
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) ExpandTagArg ( ) ( string , error ) {
2017-07-04 22:48:38 +02:00
args := c . TagArgs ( )
if strings . Contains ( args , "{{" ) {
2017-07-14 19:01:04 +02:00
root , err := c . ctx . config . Compile ( args , c . node . SourceLoc )
2017-07-04 22:48:38 +02:00
if err != nil {
return "" , err
}
buf := new ( bytes . Buffer )
2017-07-16 23:43:04 +02:00
err = Render ( root , buf , c . ctx . bindings , c . ctx . config )
2017-07-04 22:48:38 +02:00
if err != nil {
return "" , err
}
return buf . String ( ) , nil
}
return args , nil
2017-07-04 17:41:45 +02:00
}
2017-07-14 19:01:04 +02:00
// RenderBlock renders a node.
func ( c rendererContext ) RenderBlock ( w io . Writer , b * BlockNode ) error {
2017-07-06 15:21:26 +02:00
return c . ctx . RenderSequence ( w , b . Body )
2017-07-04 17:41:45 +02:00
}
// RenderChildren renders the current node's children.
2017-07-10 17:49:14 +02:00
func ( c rendererContext ) RenderChildren ( w io . Writer ) Error {
2017-07-04 17:41:45 +02:00
if c . cn == nil {
return nil
}
2017-07-06 15:21:26 +02:00
return c . ctx . RenderSequence ( w , c . cn . Body )
2017-07-04 17:41:45 +02:00
}
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) RenderFile ( filename string , b map [ string ] interface { } ) ( string , error ) {
2017-07-04 17:41:45 +02:00
source , err := ioutil . ReadFile ( filename )
2020-11-03 01:36:02 +01:00
if err != nil && os . IsNotExist ( err ) {
// Is it cached?
if cval , ok := c . ctx . config . Cache [ filename ] ; ok {
source = cval
} else {
return "" , err
}
} else if err != nil {
2017-07-04 17:41:45 +02:00
return "" , err
}
2017-07-14 19:01:04 +02:00
root , err := c . ctx . config . Compile ( string ( source ) , c . node . SourceLoc )
2017-07-04 17:41:45 +02:00
if err != nil {
return "" , err
}
2017-07-19 16:03:11 +02:00
bindings := map [ string ] interface { } { }
for k , v := range c . ctx . bindings {
bindings [ k ] = v
}
2017-07-04 22:48:38 +02:00
for k , v := range b {
2017-07-19 16:03:11 +02:00
bindings [ k ] = v
2017-07-04 22:48:38 +02:00
}
2017-07-04 17:41:45 +02:00
buf := new ( bytes . Buffer )
2017-07-19 16:03:11 +02:00
if err := Render ( root , buf , bindings , c . ctx . config ) ; err != nil {
2017-07-04 17:41:45 +02:00
return "" , err
}
return buf . String ( ) , nil
}
// InnerString renders the children to a string.
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) InnerString ( ) ( string , error ) {
2017-07-04 17:41:45 +02:00
buf := new ( bytes . Buffer )
if err := c . RenderChildren ( buf ) ; err != nil {
return "" , err
}
return buf . String ( ) , nil
}
2017-07-04 22:48:38 +02:00
// Set sets a variable value from an evaluation context.
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) Set ( name string , value interface { } ) {
2017-07-04 22:48:38 +02:00
c . ctx . bindings [ name ] = value
2017-07-04 17:41:45 +02:00
}
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) SourceFile ( ) string {
2017-07-14 16:38:30 +02:00
return c . node . SourceLoc . Pathname
2017-07-04 17:41:45 +02:00
}
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) TagArgs ( ) string {
2017-07-04 17:41:45 +02:00
switch {
case c . node != nil :
2017-07-09 17:18:35 +02:00
return c . node . Token . Args
2017-07-04 17:41:45 +02:00
case c . cn != nil :
2017-07-09 17:18:35 +02:00
return c . cn . Token . Args
2017-07-04 17:41:45 +02:00
default :
return ""
}
}
2017-07-07 13:30:32 +02:00
func ( c rendererContext ) TagName ( ) string {
2017-07-04 17:41:45 +02:00
switch {
case c . node != nil :
2017-07-09 17:18:35 +02:00
return c . node . Token . Name
2017-07-04 17:41:45 +02:00
case c . cn != nil :
2017-07-09 17:18:35 +02:00
return c . cn . Token . Name
2017-07-04 17:41:45 +02:00
default :
return ""
}
2017-06-27 19:18:01 +02:00
}