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, "", 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 }