github.com/rotblauer/buffalo@v0.7.1-0.20170112214545-7aa55ef80dd3/render/template.go (about)

     1  package render
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"html/template"
     7  	"io"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/gobuffalo/velvet"
    12  	"github.com/pkg/errors"
    13  	"github.com/shurcooL/github_flavored_markdown"
    14  )
    15  
    16  type templateRenderer struct {
    17  	*Engine
    18  	contentType string
    19  	names       []string
    20  }
    21  
    22  func (s templateRenderer) ContentType() string {
    23  	return s.contentType
    24  }
    25  
    26  func (s *templateRenderer) Render(w io.Writer, data Data) error {
    27  	var yield template.HTML
    28  	var err error
    29  	for _, name := range s.names {
    30  		yield, err = s.execute(name, data.ToVelvet())
    31  		if err != nil {
    32  			err = errors.Errorf("error rendering %s:\n%+v", name, err)
    33  			return err
    34  		}
    35  		data["yield"] = yield
    36  	}
    37  	_, err = w.Write([]byte(yield))
    38  	if err != nil {
    39  		return errors.WithStack(err)
    40  	}
    41  	return nil
    42  }
    43  
    44  func (s *templateRenderer) execute(name string, data *velvet.Context) (template.HTML, error) {
    45  	source, err := s.source(name)
    46  	if err != nil {
    47  		return "", err
    48  	}
    49  
    50  	err = source.Helpers.Add("partial", func(name string, help velvet.HelperContext) (template.HTML, error) {
    51  		p, err := s.partial(name, help.Context)
    52  		if err != nil {
    53  			return template.HTML(fmt.Sprintf("<pre>%s: %s</pre>", name, err.Error())), err
    54  		}
    55  		return p, nil
    56  	})
    57  	if err != nil {
    58  		return template.HTML(fmt.Sprintf("<pre>%s: %s</pre>", name, err.Error())), err
    59  	}
    60  	yield, err := source.Exec(data)
    61  	if err != nil {
    62  		return template.HTML(fmt.Sprintf("<pre>%s: %s</pre>", name, err.Error())), err
    63  	}
    64  	return template.HTML(yield), nil
    65  }
    66  
    67  func (s *templateRenderer) source(name string) (*velvet.Template, error) {
    68  	var t *velvet.Template
    69  	var ok bool
    70  	var err error
    71  	if s.CacheTemplates {
    72  		if t, ok = s.templateCache[name]; ok {
    73  			return t.Clone(), nil
    74  		}
    75  	}
    76  	b, err := s.Resolver().Read(filepath.Join(s.TemplatesPath, name))
    77  	if err != nil {
    78  		return nil, errors.WithStack(fmt.Errorf("could not find template: %s", name))
    79  	}
    80  	if strings.ToLower(filepath.Ext(name)) == ".md" {
    81  		b = github_flavored_markdown.Markdown(b)
    82  		// unescape quotes so raymond can parse the file correctly.
    83  		b = bytes.Replace(b, []byte("&#34;"), []byte("\""), -1)
    84  	}
    85  	source := string(b)
    86  	t, err = velvet.Parse(source)
    87  	if err != nil {
    88  		return t, errors.Errorf("Error parsing %s: %+v", name, errors.WithStack(err))
    89  	}
    90  
    91  	err = t.Helpers.AddMany(s.Helpers)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	if s.CacheTemplates {
    96  		s.templateCache[name] = t
    97  	}
    98  	return t.Clone(), err
    99  }
   100  
   101  func (s *templateRenderer) partial(name string, data *velvet.Context) (template.HTML, error) {
   102  	d, f := filepath.Split(name)
   103  	name = filepath.Join(d, "_"+f)
   104  	return s.execute(name, data)
   105  }
   106  
   107  // Template renders the named files using the specified
   108  // content type and the github.com/aymerick/raymond
   109  // package for templating. If more than 1 file is provided
   110  // the second file will be considered a "layout" file
   111  // and the first file will be the "content" file which will
   112  // be placed into the "layout" using "{{yield}}".
   113  func Template(c string, names ...string) Renderer {
   114  	e := New(Options{})
   115  	return e.Template(c, names...)
   116  }
   117  
   118  // Template renders the named files using the specified
   119  // content type and the github.com/aymerick/raymond
   120  // package for templating. If more than 1 file is provided
   121  // the second file will be considered a "layout" file
   122  // and the first file will be the "content" file which will
   123  // be placed into the "layout" using "{{yield}}".
   124  func (e *Engine) Template(c string, names ...string) Renderer {
   125  	return &templateRenderer{
   126  		Engine:      e,
   127  		contentType: c,
   128  		names:       names,
   129  	}
   130  }