diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-01-10 01:36:59 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-10 01:36:59 +0100 |
commit | d6000a208c7687ca3a3efd6961ac941ce325e199 (patch) | |
tree | 9644609a0d19894b46d065faec2cfe2aaeb410bd /tpl | |
parent | 4ea4359ac17a3b5304fb0d73773f99a07975ee1e (diff) |
all: Refactor to nonglobal template handling
Updates #2701
Diffstat (limited to 'tpl')
-rw-r--r-- | tpl/template.go | 88 | ||||
-rw-r--r-- | tpl/template_ast_transformers_test.go | 5 | ||||
-rw-r--r-- | tpl/template_funcs.go | 57 | ||||
-rw-r--r-- | tpl/template_funcs_test.go | 27 | ||||
-rw-r--r-- | tpl/template_test.go | 34 |
5 files changed, 113 insertions, 98 deletions
diff --git a/tpl/template.go b/tpl/template.go index db6a912ff..b26490f0c 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -30,10 +30,8 @@ import ( "github.com/yosssi/ace" ) -var localTemplates *template.Template - -// TODO(bep) globals get rid of the reset of the jww.ERR etc. -var tmpl *GoHTMLTemplate +// TODO(bep) globals get rid of the rest of the jww.ERR etc. +//var tmpl *GoHTMLTemplate // TODO(bep) an interface with hundreds of methods ... remove it. // And unexport most of these methods. @@ -45,13 +43,13 @@ type Template interface { GetClone() *template.Template LoadTemplates(absPath string) LoadTemplatesWithPrefix(absPath, prefix string) - MarkReady() AddTemplate(name, tpl string) error AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error AddInternalTemplate(prefix, name, tpl string) error AddInternalShortcode(name, tpl string) error PrintErrors() + Funcs(funcMap template.FuncMap) } type templateErr struct { @@ -60,7 +58,8 @@ type templateErr struct { } type GoHTMLTemplate struct { - template.Template + *template.Template + clone *template.Template // a separate storage for the overlays created from cloned master templates. @@ -69,41 +68,54 @@ type GoHTMLTemplate struct { errors []*templateErr + funcster *templateFuncster + // TODO(bep) globals template log *jww.Notepad } -// InitializeT resets the internal template state to its initial state -func InitializeT(logger *jww.Notepad) *GoHTMLTemplate { - tmpl = New(logger) - return tmpl -} - // New returns a new Hugo Template System // with all the additional features, templates & functions -func New(logger *jww.Notepad) *GoHTMLTemplate { - var templates = &GoHTMLTemplate{ - Template: *template.New(""), +func New(logger *jww.Notepad, withTemplate ...func(templ Template) error) *GoHTMLTemplate { + tmpl := &GoHTMLTemplate{ + Template: template.New(""), overlays: make(map[string]*template.Template), errors: make([]*templateErr, 0), log: logger, } - localTemplates = &templates.Template + tmpl.funcster = newTemplateFuncster(tmpl) // The URL funcs in the funcMap is somewhat language dependent, // so we need to wait until the language and site config is loaded. - initFuncMap() + // TODO(bep) globals + tmpl.funcster.initFuncMap() - for k, v := range funcMap { + // TODO(bep) globals + for k, v := range tmpl.funcster.funcMap { amber.FuncMap[k] = v } - templates.Funcs(funcMap) - templates.LoadEmbedded() - return templates + + tmpl.LoadEmbedded() + + for _, wt := range withTemplate { + err := wt(tmpl) + if err != nil { + tmpl.errors = append(tmpl.errors, &templateErr{"init", err}) + } + + } + + tmpl.markReady() + + return tmpl +} + +func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) { + t.Template.Funcs(funcMap) } -func partial(name string, contextList ...interface{}) template.HTML { +func (t *GoHTMLTemplate) partial(name string, contextList ...interface{}) template.HTML { if strings.HasPrefix("partials/", name) { name = name[8:] } @@ -114,16 +126,16 @@ func partial(name string, contextList ...interface{}) template.HTML { } else { context = contextList[0] } - return ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name) + return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name) } -func executeTemplate(context interface{}, w io.Writer, layouts ...string) { +func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layouts ...string) { var worked bool for _, layout := range layouts { - templ := Lookup(layout) + templ := t.Lookup(layout) if templ == nil { layout += ".html" - templ = Lookup(layout) + templ = t.Lookup(layout) } if templ != nil { @@ -136,28 +148,20 @@ func executeTemplate(context interface{}, w io.Writer, layouts ...string) { } } if !worked { - tmpl.log.ERROR.Println("Unable to render", layouts) - tmpl.log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts) + t.log.ERROR.Println("Unable to render", layouts) + t.log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts) } } -func ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML { +func (t *GoHTMLTemplate) ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML { b := bp.GetBuffer() defer bp.PutBuffer(b) - executeTemplate(context, b, layouts...) + t.executeTemplate(context, b, layouts...) return template.HTML(b.String()) } -func Lookup(name string) *template.Template { - return tmpl.Lookup(name) -} - func (t *GoHTMLTemplate) Lookup(name string) *template.Template { - if templ := localTemplates.Lookup(name); templ != nil { - return templ - } - if t.overlays != nil { if templ, ok := t.overlays[name]; ok { return templ @@ -183,9 +187,9 @@ func (t *GoHTMLTemplate) LoadEmbedded() { t.EmbedTemplates() } -// MarkReady marks the template as "ready for execution". No changes allowed +// markReady marks the template as "ready for execution". No changes allowed // after this is set. -func (t *GoHTMLTemplate) MarkReady() { +func (t *GoHTMLTemplate) markReady() { if t.clone == nil { t.clone = template.Must(t.Template.Clone()) } @@ -522,7 +526,7 @@ func (t *GoHTMLTemplate) LoadTemplates(absPath string) { } func (t *GoHTMLTemplate) PrintErrors() { - for _, e := range t.errors { - t.log.ERROR.Println(e.err) + for i, e := range t.errors { + t.log.ERROR.Println(i, ":", e.err) } } diff --git a/tpl/template_ast_transformers_test.go b/tpl/template_ast_transformers_test.go index c78c521c9..8ffb1cab1 100644 --- a/tpl/template_ast_transformers_test.go +++ b/tpl/template_ast_transformers_test.go @@ -18,7 +18,6 @@ import ( "html/template" - jww "github.com/spf13/jwalterweatherman" "github.com/stretchr/testify/require" ) @@ -265,7 +264,3 @@ P2: {{ .Params.LOWER }} require.Contains(t, result, "P1: P1L") require.Contains(t, result, "P2: P1L") } - -func init() { - jww.SetStdoutThreshold(jww.LevelCritical) -} diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 596902fcd..6c8a9957e 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -54,9 +54,19 @@ import ( _ "image/png" ) -var ( - funcMap template.FuncMap -) +// Some of the template funcs are'nt entirely stateless. +type templateFuncster struct { + t *GoHTMLTemplate + funcMap template.FuncMap + cachedPartials partialCache +} + +func newTemplateFuncster(t *GoHTMLTemplate) *templateFuncster { + return &templateFuncster{ + t: t, + cachedPartials: partialCache{p: make(map[string]template.HTML)}, + } +} // eq returns the boolean truth of arg1 == arg2. func eq(x, y interface{}) bool { @@ -1003,7 +1013,7 @@ func where(seq, key interface{}, args ...interface{}) (interface{}, error) { } // apply takes a map, array, or slice and returns a new slice with the function fname applied over it. -func apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) { +func (tf *templateFuncster) apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) { if seq == nil { return make([]interface{}, 0), nil } @@ -1018,7 +1028,7 @@ func apply(seq interface{}, fname string, args ...interface{}) (interface{}, err return nil, errors.New("can't iterate over a nil value") } - fn, found := funcMap[fname] + fn, found := tf.funcMap[fname] if !found { return nil, errors.New("can't find function " + fname) } @@ -1518,41 +1528,39 @@ type partialCache struct { // Get retrieves partial output from the cache based upon the partial name. // If the partial is not found in the cache, the partial is rendered and added // to the cache. -func (c *partialCache) Get(key, name string, context interface{}) (p template.HTML) { +func (tf *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) { var ok bool - c.RLock() - p, ok = c.p[key] - c.RUnlock() + tf.cachedPartials.RLock() + p, ok = tf.cachedPartials.p[key] + tf.cachedPartials.RUnlock() if ok { return p } - c.Lock() - if p, ok = c.p[key]; !ok { - p = partial(name, context) - c.p[key] = p + tf.cachedPartials.Lock() + if p, ok = tf.cachedPartials.p[key]; !ok { + p = tf.t.partial(name, context) + tf.cachedPartials.p[key] = p } - c.Unlock() + tf.cachedPartials.Unlock() return p } -var cachedPartials = partialCache{p: make(map[string]template.HTML)} - // partialCached executes and caches partial templates. An optional variant // string parameter (a string slice actually, but be only use a variadic // argument to make it optional) can be passed so that a given partial can have // multiple uses. The cache is created with name+variant as the key. -func partialCached(name string, context interface{}, variant ...string) template.HTML { +func (tf *templateFuncster) partialCached(name string, context interface{}, variant ...string) template.HTML { key := name if len(variant) > 0 { for i := 0; i < len(variant); i++ { key += variant[i] } } - return cachedPartials.Get(key, name, context) + return tf.Get(key, name, context) } // regexpCache represents a cache of regexp objects protected by a mutex. @@ -2090,8 +2098,8 @@ func getenv(key interface{}) (string, error) { return os.Getenv(skey), nil } -func initFuncMap() { - funcMap = template.FuncMap{ +func (tf *templateFuncster) initFuncMap() { + funcMap := template.FuncMap{ "absURL": absURL, "absLangURL": func(i interface{}) (template.HTML, error) { s, err := cast.ToStringE(i) @@ -2102,7 +2110,7 @@ func initFuncMap() { }, "add": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '+') }, "after": after, - "apply": apply, + "apply": tf.apply, "base64Decode": base64Decode, "base64Encode": base64Encode, "chomp": chomp, @@ -2147,8 +2155,8 @@ func initFuncMap() { "mul": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '*') }, "ne": ne, "now": func() time.Time { return time.Now() }, - "partial": partial, - "partialCached": partialCached, + "partial": tf.t.partial, + "partialCached": tf.partialCached, "plainify": plainify, "pluralize": pluralize, "querify": querify, @@ -2195,4 +2203,7 @@ func initFuncMap() { "i18n": i18nTranslate, "T": i18nTranslate, } + + tf.funcMap = funcMap + tf.t.Funcs(funcMap) } diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index c3fbb6ad8..6bbbf0146 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -1960,40 +1960,43 @@ func TestMarkdownify(t *testing.T) { } func TestApply(t *testing.T) { + + f := newTestFuncster() + strings := []interface{}{"a\n", "b\n"} noStringers := []interface{}{tstNoStringer{}, tstNoStringer{}} - chomped, _ := apply(strings, "chomp", ".") + chomped, _ := f.apply(strings, "chomp", ".") assert.Equal(t, []interface{}{template.HTML("a"), template.HTML("b")}, chomped) - chomped, _ = apply(strings, "chomp", "c\n") + chomped, _ = f.apply(strings, "chomp", "c\n") assert.Equal(t, []interface{}{template.HTML("c"), template.HTML("c")}, chomped) - chomped, _ = apply(nil, "chomp", ".") + chomped, _ = f.apply(nil, "chomp", ".") assert.Equal(t, []interface{}{}, chomped) - _, err := apply(strings, "apply", ".") + _, err := f.apply(strings, "apply", ".") if err == nil { t.Errorf("apply with apply should fail") } var nilErr *error - _, err = apply(nilErr, "chomp", ".") + _, err = f.apply(nilErr, "chomp", ".") if err == nil { t.Errorf("apply with nil in seq should fail") } - _, err = apply(strings, "dobedobedo", ".") + _, err = f.apply(strings, "dobedobedo", ".") if err == nil { t.Errorf("apply with unknown func should fail") } - _, err = apply(noStringers, "chomp", ".") + _, err = f.apply(noStringers, "chomp", ".") if err == nil { t.Errorf("apply when func fails should fail") } - _, err = apply(tstNoStringer{}, "chomp", ".") + _, err = f.apply(tstNoStringer{}, "chomp", ".") if err == nil { t.Errorf("apply with non-sequence should fail") } @@ -2780,7 +2783,6 @@ func TestPartialCached(t *testing.T) { data.Params = map[string]interface{}{"langCode": "en"} tstInitTemplates() - InitializeT(logger) for i, tc := range testCases { var tmp string if tc.variant != "" { @@ -2831,7 +2833,6 @@ func TestPartialCached(t *testing.T) { } func BenchmarkPartial(b *testing.B) { - InitializeT(logger) tmpl, err := New(logger).New("testroot").Parse(`{{ partial "bench1" . }}`) if err != nil { b.Fatalf("unable to create new html template: %s", err) @@ -2851,7 +2852,6 @@ func BenchmarkPartial(b *testing.B) { } func BenchmarkPartialCached(b *testing.B) { - InitializeT(logger) tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . }}`) if err != nil { b.Fatalf("unable to create new html template: %s", err) @@ -2871,7 +2871,6 @@ func BenchmarkPartialCached(b *testing.B) { } func BenchmarkPartialCachedVariants(b *testing.B) { - InitializeT(logger) tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . "header" }}`) if err != nil { b.Fatalf("unable to create new html template: %s", err) @@ -2889,3 +2888,7 @@ func BenchmarkPartialCachedVariants(b *testing.B) { buf.Reset() } } + +func newTestFuncster() *templateFuncster { + return New(logger).funcster +} diff --git a/tpl/template_test.go b/tpl/template_test.go index 2f4946598..cf691858b 100644 --- a/tpl/template_test.go +++ b/tpl/template_test.go @@ -55,8 +55,6 @@ html lang=en for _, root := range []string{"", os.TempDir()} { - templ := New(logger) - basePath := this.basePath innerPath := this.innerPath @@ -70,17 +68,20 @@ html lang=en d := "DATA" - err := templ.AddAceTemplate("mytemplate.ace", basePath, innerPath, - []byte(this.baseContent), []byte(this.innerContent)) + templ := New(logger, func(templ Template) error { + return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath, + []byte(this.baseContent), []byte(this.innerContent)) - if err != nil && this.expectErr == 0 { - t.Errorf("Test %d with root '%s' errored: %s", i, root, err) - } else if err == nil && this.expectErr == 1 { + }) + + if len(templ.errors) > 0 && this.expectErr == 0 { + t.Errorf("Test %d with root '%s' errored: %v", i, root, templ.errors) + } else if len(templ.errors) == 0 && this.expectErr == 1 { t.Errorf("#1 Test %d with root '%s' should have errored", i, root) } var buff bytes.Buffer - err = templ.ExecuteTemplate(&buff, "mytemplate.html", d) + err := templ.ExecuteTemplate(&buff, "mytemplate.html", d) if err != nil && this.expectErr == 0 { t.Errorf("Test %d with root '%s' errored: %s", i, root, err) @@ -245,7 +246,6 @@ func TestTplGoFuzzReports(t *testing.T) { // Issue #1095 {"{{apply .C \"urlize\" " + "\".\"}}", 2}} { - templ := New(logger) d := &Data{ A: 42, @@ -258,15 +258,17 @@ func TestTplGoFuzzReports(t *testing.T) { H: "a,b,c,d,e,f", } - err := templ.AddTemplate("fuzz", this.data) + templ := New(logger, func(templ Template) error { + return templ.AddTemplate("fuzz", this.data) - if err != nil && this.expectErr == 0 { - t.Fatalf("Test %d errored: %s", i, err) - } else if err == nil && this.expectErr == 1 { - t.Fatalf("#1 Test %d should have errored", i) - } + }) - err = templ.ExecuteTemplate(ioutil.Discard, "fuzz", d) + if len(templ.errors) > 0 && this.expectErr == 0 { + t.Errorf("Test %d errored: %v", i, templ.errors) + } else if len(templ.errors) == 0 && this.expectErr == 1 { + t.Errorf("#1 Test %d should have errored", i) + } + err := templ.ExecuteTemplate(ioutil.Discard, "fuzz", d) if err != nil && this.expectErr == 0 { t.Fatalf("Test %d errored: %s", i, err) |