github.com/segakazzz/buffalo@v0.16.22-0.20210119082501-1f52048d3feb/render/template.go (about)

     1  package render
     2  
     3  import (
     4  	"fmt"
     5  	"html/template"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/gobuffalo/packd"
    13  	"github.com/segakazzz/buffalo/internal/takeon/github.com/gobuffalo/syncx"
    14  	"github.com/segakazzz/buffalo/internal/takeon/github.com/markbates/errx"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  type templateRenderer struct {
    19  	*Engine
    20  	contentType string
    21  	names       []string
    22  	aliases     syncx.StringMap
    23  }
    24  
    25  func (s templateRenderer) ContentType() string {
    26  	return s.contentType
    27  }
    28  
    29  func (s templateRenderer) resolve(name string) ([]byte, error) {
    30  	if s.TemplatesBox == nil {
    31  		return nil, fmt.Errorf("no templates box is defined")
    32  	}
    33  
    34  	if s.TemplatesBox.Has(name) {
    35  		return s.TemplatesBox.Find(name)
    36  	}
    37  
    38  	v, ok := s.aliases.Load(name)
    39  	if !ok {
    40  		return nil, fmt.Errorf("could not find template %s", name)
    41  	}
    42  
    43  	return s.TemplatesBox.Find(v)
    44  }
    45  
    46  func (s *templateRenderer) Render(w io.Writer, data Data) error {
    47  	if s.TemplatesBox != nil {
    48  		err := s.TemplatesBox.Walk(func(p string, f packd.File) error {
    49  			base := filepath.Base(p)
    50  
    51  			dir := filepath.Dir(p)
    52  
    53  			var exts []string
    54  			sep := strings.Split(base, ".")
    55  			if len(sep) >= 1 {
    56  				base = sep[0]
    57  			}
    58  			if len(sep) > 1 {
    59  				exts = sep[1:]
    60  			}
    61  
    62  			for _, ext := range exts {
    63  				pn := filepath.Join(dir, base+"."+ext)
    64  				s.aliases.Store(pn, p)
    65  			}
    66  
    67  			return nil
    68  		})
    69  		if err != nil {
    70  			return err
    71  		}
    72  	}
    73  
    74  	var body template.HTML
    75  	var err error
    76  	for _, name := range s.names {
    77  		body, err = s.exec(name, data)
    78  		if err != nil {
    79  			return errx.Wrap(err, name)
    80  		}
    81  		data["yield"] = body
    82  	}
    83  	w.Write([]byte(body))
    84  	return nil
    85  }
    86  
    87  func fixExtension(name string, ct string) string {
    88  	if filepath.Ext(name) == "" {
    89  		switch {
    90  		case strings.Contains(ct, "html"):
    91  			name += ".html"
    92  		case strings.Contains(ct, "javascript"):
    93  			name += ".js"
    94  		case strings.Contains(ct, "markdown"):
    95  			name += ".md"
    96  		}
    97  	}
    98  	return name
    99  }
   100  
   101  // partialFeeder returns template string for the name from `TemplateBox`.
   102  // It should be registered as helper named `partialFeeder` so plush can
   103  // find it with the name.
   104  func (s templateRenderer) partialFeeder(name string) (string, error) {
   105  	ct := strings.ToLower(s.contentType)
   106  
   107  	d, f := filepath.Split(name)
   108  	name = filepath.Join(d, "_"+f)
   109  	name = fixExtension(name, ct)
   110  
   111  	b, err := s.resolve(name)
   112  	return string(b), err
   113  }
   114  
   115  func (s templateRenderer) exec(name string, data Data) (template.HTML, error) {
   116  	ct := strings.ToLower(s.contentType)
   117  	data["contentType"] = ct
   118  
   119  	name = fixExtension(name, ct)
   120  
   121  	// Try to use localized version
   122  	templateName := s.localizedName(name, data)
   123  
   124  	// Set current_template to context
   125  	if _, ok := data["current_template"]; !ok {
   126  		data["current_template"] = templateName
   127  	}
   128  
   129  	source, err := s.resolve(templateName)
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  
   134  	helpers := map[string]interface{}{}
   135  
   136  	for k, v := range s.Helpers {
   137  		helpers[k] = v
   138  	}
   139  
   140  	// Allows to specify custom partialFeeder
   141  	if helpers["partialFeeder"] == nil {
   142  		helpers["partialFeeder"] = s.partialFeeder
   143  	}
   144  
   145  	helpers = s.addAssetsHelpers(helpers)
   146  
   147  	body := string(source)
   148  	for _, ext := range s.exts(name) {
   149  		te, ok := s.TemplateEngines[ext]
   150  		if !ok {
   151  			logrus.Errorf("could not find a template engine for %s", ext)
   152  			continue
   153  		}
   154  		body, err = te(body, data, helpers)
   155  		if err != nil {
   156  			return "", err
   157  		}
   158  	}
   159  
   160  	return template.HTML(body), nil
   161  }
   162  
   163  func (s templateRenderer) localizedName(name string, data Data) string {
   164  	templateName := name
   165  
   166  	languages, ok := data["languages"].([]string)
   167  	if !ok || len(languages) == 0 {
   168  		return templateName
   169  	}
   170  
   171  	ll := len(languages)
   172  	// Default language is the last in the list
   173  	defaultLanguage := languages[ll-1]
   174  	ext := filepath.Ext(name)
   175  	rawName := strings.TrimSuffix(name, ext)
   176  
   177  	for _, l := range languages {
   178  		var candidateName string
   179  		if l == defaultLanguage {
   180  			break
   181  		}
   182  
   183  		candidateName = rawName + "." + strings.ToLower(l) + ext
   184  		if _, err := s.resolve(candidateName); err == nil {
   185  			// Replace name with the existing suffixed version
   186  			templateName = candidateName
   187  			break
   188  		}
   189  	}
   190  
   191  	return templateName
   192  }
   193  
   194  func (s templateRenderer) exts(name string) []string {
   195  	exts := []string{}
   196  	for {
   197  		ext := filepath.Ext(name)
   198  		if ext == "" {
   199  			break
   200  		}
   201  		name = strings.TrimSuffix(name, ext)
   202  		exts = append(exts, strings.ToLower(ext[1:]))
   203  	}
   204  	if len(exts) == 0 {
   205  		return []string{"html"}
   206  	}
   207  	sort.Sort(sort.Reverse(sort.StringSlice(exts)))
   208  	return exts
   209  }
   210  
   211  func (s templateRenderer) assetPath(file string) (string, error) {
   212  
   213  	if len(assetMap.Keys()) == 0 || os.Getenv("GO_ENV") != "production" {
   214  		manifest, err := s.AssetsBox.FindString("manifest.json")
   215  
   216  		if err != nil {
   217  			manifest, err = s.AssetsBox.FindString("assets/manifest.json")
   218  			if err != nil {
   219  				return assetPathFor(file), nil
   220  			}
   221  		}
   222  
   223  		err = loadManifest(manifest)
   224  		if err != nil {
   225  			return assetPathFor(file), fmt.Errorf("your manifest.json is not correct: %s", err)
   226  		}
   227  	}
   228  
   229  	return assetPathFor(file), nil
   230  }
   231  
   232  // Template renders the named files using the specified
   233  // content type and the github.com/gobuffalo/plush
   234  // package for templating. If more than 1 file is provided
   235  // the second file will be considered a "layout" file
   236  // and the first file will be the "content" file which will
   237  // be placed into the "layout" using "{{yield}}".
   238  func Template(c string, names ...string) Renderer {
   239  	e := New(Options{})
   240  	return e.Template(c, names...)
   241  }
   242  
   243  // Template renders the named files using the specified
   244  // content type and the github.com/gobuffalo/plush
   245  // package for templating. If more than 1 file is provided
   246  // the second file will be considered a "layout" file
   247  // and the first file will be the "content" file which will
   248  // be placed into the "layout" using "{{yield}}".
   249  func (e *Engine) Template(c string, names ...string) Renderer {
   250  	return &templateRenderer{
   251  		Engine:      e,
   252  		contentType: c,
   253  		names:       names,
   254  		aliases:     syncx.StringMap{},
   255  	}
   256  }