mirror of
https://github.com/Luzifer/cloudkeys-go.git
synced 2024-11-14 00:42:44 +00:00
192 lines
4.5 KiB
Go
192 lines
4.5 KiB
Go
|
package pongo2
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
)
|
||
|
|
||
|
type TemplateWriter interface {
|
||
|
io.Writer
|
||
|
WriteString(string) (int, error)
|
||
|
}
|
||
|
|
||
|
type templateWriter struct {
|
||
|
w io.Writer
|
||
|
}
|
||
|
|
||
|
func (tw *templateWriter) WriteString(s string) (int, error) {
|
||
|
return tw.w.Write([]byte(s))
|
||
|
}
|
||
|
|
||
|
func (tw *templateWriter) Write(b []byte) (int, error) {
|
||
|
return tw.w.Write(b)
|
||
|
}
|
||
|
|
||
|
type Template struct {
|
||
|
set *TemplateSet
|
||
|
|
||
|
// Input
|
||
|
is_tpl_string bool
|
||
|
name string
|
||
|
tpl string
|
||
|
size int
|
||
|
|
||
|
// Calculation
|
||
|
tokens []*Token
|
||
|
parser *Parser
|
||
|
|
||
|
// first come, first serve (it's important to not override existing entries in here)
|
||
|
level int
|
||
|
parent *Template
|
||
|
child *Template
|
||
|
blocks map[string]*NodeWrapper
|
||
|
exported_macros map[string]*tagMacroNode
|
||
|
|
||
|
// Output
|
||
|
root *nodeDocument
|
||
|
}
|
||
|
|
||
|
func newTemplateString(set *TemplateSet, tpl string) (*Template, error) {
|
||
|
return newTemplate(set, "<string>", true, tpl)
|
||
|
}
|
||
|
|
||
|
func newTemplate(set *TemplateSet, name string, is_tpl_string bool, tpl string) (*Template, error) {
|
||
|
// Create the template
|
||
|
t := &Template{
|
||
|
set: set,
|
||
|
is_tpl_string: is_tpl_string,
|
||
|
name: name,
|
||
|
tpl: tpl,
|
||
|
size: len(tpl),
|
||
|
blocks: make(map[string]*NodeWrapper),
|
||
|
exported_macros: make(map[string]*tagMacroNode),
|
||
|
}
|
||
|
|
||
|
// Tokenize it
|
||
|
tokens, err := lex(name, tpl)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
t.tokens = tokens
|
||
|
|
||
|
// For debugging purposes, show all tokens:
|
||
|
/*for i, t := range tokens {
|
||
|
fmt.Printf("%3d. %s\n", i, t)
|
||
|
}*/
|
||
|
|
||
|
// Parse it
|
||
|
err = t.parse()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return t, nil
|
||
|
}
|
||
|
|
||
|
func (tpl *Template) execute(context Context, writer TemplateWriter) error {
|
||
|
// Determine the parent to be executed (for template inheritance)
|
||
|
parent := tpl
|
||
|
for parent.parent != nil {
|
||
|
parent = parent.parent
|
||
|
}
|
||
|
|
||
|
// Create context if none is given
|
||
|
newContext := make(Context)
|
||
|
newContext.Update(tpl.set.Globals)
|
||
|
|
||
|
if context != nil {
|
||
|
newContext.Update(context)
|
||
|
|
||
|
if len(newContext) > 0 {
|
||
|
// Check for context name syntax
|
||
|
err := newContext.checkForValidIdentifiers()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Check for clashes with macro names
|
||
|
for k, _ := range newContext {
|
||
|
_, has := tpl.exported_macros[k]
|
||
|
if has {
|
||
|
return &Error{
|
||
|
Filename: tpl.name,
|
||
|
Sender: "execution",
|
||
|
ErrorMsg: fmt.Sprintf("Context key name '%s' clashes with macro '%s'.", k, k),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create operational context
|
||
|
ctx := newExecutionContext(parent, newContext)
|
||
|
|
||
|
// Run the selected document
|
||
|
if err := parent.root.Execute(ctx, writer); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (tpl *Template) newTemplateWriterAndExecute(context Context, writer io.Writer) error {
|
||
|
return tpl.execute(context, &templateWriter{w: writer})
|
||
|
}
|
||
|
|
||
|
func (tpl *Template) newBufferAndExecute(context Context) (*bytes.Buffer, error) {
|
||
|
// Create output buffer
|
||
|
// We assume that the rendered template will be 30% larger
|
||
|
buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3)))
|
||
|
if err := tpl.execute(context, buffer); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return buffer, nil
|
||
|
}
|
||
|
|
||
|
// Executes the template with the given context and writes to writer (io.Writer)
|
||
|
// on success. Context can be nil. Nothing is written on error; instead the error
|
||
|
// is being returned.
|
||
|
func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error {
|
||
|
buf, err := tpl.newBufferAndExecute(context)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
_, err = buf.WriteTo(writer)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Same as ExecuteWriter. The only difference between both functions is that
|
||
|
// this function might already have written parts of the generated template in the
|
||
|
// case of an execution error because there's no intermediate buffer involved for
|
||
|
// performance reasons. This is handy if you need high performance template
|
||
|
// generation or if you want to manage your own pool of buffers.
|
||
|
func (tpl *Template) ExecuteWriterUnbuffered(context Context, writer io.Writer) error {
|
||
|
return tpl.newTemplateWriterAndExecute(context, writer)
|
||
|
}
|
||
|
|
||
|
// Executes the template and returns the rendered template as a []byte
|
||
|
func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
|
||
|
// Execute template
|
||
|
buffer, err := tpl.newBufferAndExecute(context)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return buffer.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
// Executes the template and returns the rendered template as a string
|
||
|
func (tpl *Template) Execute(context Context) (string, error) {
|
||
|
// Execute template
|
||
|
buffer, err := tpl.newBufferAndExecute(context)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
return buffer.String(), nil
|
||
|
|
||
|
}
|