...

Source file src/github.com/noirbizarre/gonja/exec/renderer.go

Documentation: github.com/noirbizarre/gonja/exec

     1  package exec
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/pkg/errors"
     7  
     8  	"github.com/noirbizarre/gonja/nodes"
     9  )
    10  
    11  // TrimState stores and apply trim policy
    12  type TrimState struct {
    13  	Should      bool
    14  	ShouldBlock bool
    15  	Buffer      *strings.Builder
    16  }
    17  
    18  func (ts *TrimState) TrimBlocks(r rune) bool {
    19  	if ts.ShouldBlock {
    20  		switch r {
    21  		case '\n':
    22  			ts.ShouldBlock = false
    23  			return true
    24  		case ' ', '\t':
    25  			return true
    26  		default:
    27  			return false
    28  		}
    29  	}
    30  	return false
    31  }
    32  
    33  // Renderer is a node visitor in charge of rendering
    34  type Renderer struct {
    35  	*EvalConfig
    36  	Ctx      *Context
    37  	Template *Template
    38  	Root     *nodes.Template
    39  	Out      *strings.Builder
    40  	Trim     *TrimState
    41  }
    42  
    43  // NewRenderer initialize a new renderer
    44  func NewRenderer(ctx *Context, out *strings.Builder, cfg *EvalConfig, tpl *Template) *Renderer {
    45  	var buffer strings.Builder
    46  	r := &Renderer{
    47  		EvalConfig: cfg,
    48  		Ctx:        ctx,
    49  		Template:   tpl,
    50  		Root:       tpl.Root,
    51  		Out:        out,
    52  		Trim:       &TrimState{Buffer: &buffer},
    53  	}
    54  	r.Ctx.Set("self", Self(r))
    55  	return r
    56  }
    57  
    58  // Inherit creates a new sub renderer
    59  func (r *Renderer) Inherit() *Renderer {
    60  	sub := &Renderer{
    61  		EvalConfig: r.EvalConfig.Inherit(),
    62  		Ctx:        r.Ctx.Inherit(),
    63  		Template:   r.Template,
    64  		Root:       r.Root,
    65  		Out:        r.Out,
    66  		Trim:       r.Trim,
    67  	}
    68  	return sub
    69  }
    70  
    71  func (r *Renderer) Flush(lstrip bool) {
    72  	r.FlushAndTrim(false, lstrip)
    73  }
    74  
    75  func (r *Renderer) FlushAndTrim(trim, lstrip bool) {
    76  	txt := r.Trim.Buffer.String()
    77  	if r.Config.LstripBlocks && !lstrip {
    78  		lines := strings.Split(txt, "\n")
    79  		last := lines[len(lines)-1]
    80  		lines[len(lines)-1] = strings.TrimLeft(last, " \t")
    81  		txt = strings.Join(lines, "\n")
    82  	}
    83  	if trim {
    84  		txt = strings.TrimRight(txt, " \t\n")
    85  	}
    86  	r.Out.WriteString(txt)
    87  	r.Trim.Buffer.Reset()
    88  }
    89  
    90  // WriteString wraps the triming policy
    91  func (r *Renderer) WriteString(txt string) (int, error) {
    92  	if r.Config.TrimBlocks {
    93  		txt = strings.TrimLeftFunc(txt, r.Trim.TrimBlocks)
    94  	}
    95  	if r.Trim.Should {
    96  		txt = strings.TrimLeft(txt, " \t\n")
    97  		if len(txt) > 0 {
    98  			r.Trim.Should = false
    99  		}
   100  	}
   101  	return r.Trim.Buffer.WriteString(txt)
   102  }
   103  
   104  // RenderValue properly render a value
   105  func (r *Renderer) RenderValue(value *Value) {
   106  	if r.Autoescape && value.IsString() && !value.Safe {
   107  		r.WriteString(value.Escaped())
   108  	} else {
   109  		r.WriteString(value.String())
   110  	}
   111  }
   112  
   113  func (r *Renderer) StartTag(trim *nodes.Trim, lstrip bool) {
   114  	if trim == nil {
   115  		r.Flush(lstrip)
   116  	} else {
   117  		r.FlushAndTrim(trim.Left, lstrip)
   118  	}
   119  	r.Trim.Should = false
   120  }
   121  
   122  func (r *Renderer) EndTag(trim *nodes.Trim) {
   123  	if trim == nil {
   124  		return
   125  	}
   126  	r.Trim.Should = trim.Right
   127  }
   128  
   129  func (r *Renderer) Tag(trim *nodes.Trim, lstrip bool) {
   130  	r.StartTag(trim, lstrip)
   131  	r.EndTag(trim)
   132  }
   133  
   134  // Visit implements the nodes.Visitor interface
   135  func (r *Renderer) Visit(node nodes.Node) (nodes.Visitor, error) {
   136  	switch n := node.(type) {
   137  	case *nodes.Comment:
   138  		r.Tag(n.Trim, false)
   139  		return nil, nil
   140  	case *nodes.Data:
   141  		r.WriteString(n.Data.Val)
   142  		return nil, nil
   143  	case *nodes.Output:
   144  		r.StartTag(n.Trim, false)
   145  		value := r.Eval(n.Expression)
   146  		if value.IsError() {
   147  			return nil, errors.Wrapf(value, `Unable to render expression '%s'`, n.Expression)
   148  		}
   149  		r.RenderValue(value)
   150  		r.EndTag(n.Trim)
   151  		return nil, nil
   152  	case *nodes.StatementBlock:
   153  		r.Tag(n.Trim, n.LStrip)
   154  		r.Trim.ShouldBlock = r.Config.TrimBlocks
   155  		stmt, ok := n.Stmt.(Statement)
   156  		if ok {
   157  			// Silently ignore non executable statements
   158  			// return nil, nil
   159  			// return nil, errors.Errorf(`Unable to execute statement '%s'`, n.Stmt)
   160  			if err := stmt.Execute(r, n); err != nil {
   161  				return nil, errors.Wrapf(err, `Unable to execute statement '%s'`, n.Stmt)
   162  			}
   163  		}
   164  		return nil, nil
   165  	default:
   166  		return r, nil
   167  	}
   168  }
   169  
   170  // ExecuteWrapper wraps the nodes.Wrapper execution logic
   171  func (r *Renderer) ExecuteWrapper(wrapper *nodes.Wrapper) error {
   172  	sub := r.Inherit()
   173  	err := nodes.Walk(sub, wrapper)
   174  	sub.Tag(wrapper.Trim, wrapper.LStrip)
   175  	r.Trim.ShouldBlock = r.Config.TrimBlocks
   176  	return err
   177  }
   178  
   179  func (r *Renderer) LStrip() {
   180  }
   181  
   182  func (r *Renderer) Execute() error {
   183  	// Determine the parent to be executed (for template inheritance)
   184  	root := r.Root
   185  	for root.Parent != nil {
   186  		root = root.Parent
   187  	}
   188  
   189  	err := nodes.Walk(r, root)
   190  	if err == nil {
   191  		r.Flush(false)
   192  	}
   193  	return err
   194  }
   195  
   196  func (r *Renderer) String() string {
   197  	r.Flush(false)
   198  	out := r.Out.String()
   199  	if !r.Config.KeepTrailingNewline {
   200  		out = strings.TrimSuffix(out, "\n")
   201  	}
   202  	return out
   203  }
   204  

View as plain text