mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
tpl/tplimpl: Rework template management to get rid of concurrency issues
This more or less completes the simplification of the template handling code in Hugo started in v0.62. The main motivation was to fix a long lasting issue about a crash in HTML content files without front matter. But this commit also comes with a big functional improvement. As we now have moved the base template evaluation to the build stage we now use the same lookup rules for `baseof` as for `list` etc. type of templates. This means that in this simple example you can have a `baseof` template for the `blog` section without having to duplicate the others: ``` layouts ├── _default │ ├── baseof.html │ ├── list.html │ └── single.html └── blog └── baseof.html ``` Also, when simplifying code, you often get rid of some double work, as shown in the "site building" benchmarks below. These benchmarks looks suspiciously good, but I have repeated the below with ca. the same result. Compared to master: ``` name old time/op new time/op delta SiteNew/Bundle_with_image-16 13.1ms ± 1% 10.5ms ± 1% -19.34% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 13.0ms ± 0% 10.7ms ± 1% -18.05% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 46.4ms ± 2% 43.1ms ± 1% -7.15% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 52.2ms ± 2% 47.8ms ± 1% -8.30% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 77.9ms ± 1% 70.9ms ± 1% -9.01% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 43.0ms ± 0% 37.2ms ± 1% -13.54% (p=0.029 n=4+4) SiteNew/Page_collections-16 58.2ms ± 1% 52.4ms ± 1% -9.95% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Bundle_with_image-16 3.81MB ± 0% 2.22MB ± 0% -41.70% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 3.60MB ± 0% 2.01MB ± 0% -44.20% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 19.3MB ± 1% 14.1MB ± 0% -26.91% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 70.7MB ± 0% 69.0MB ± 0% -2.40% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 37.1MB ± 0% 31.2MB ± 0% -15.94% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 17.6MB ± 0% 10.6MB ± 0% -39.92% (p=0.029 n=4+4) SiteNew/Page_collections-16 25.9MB ± 0% 21.2MB ± 0% -17.99% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Bundle_with_image-16 52.3k ± 0% 26.1k ± 0% -50.18% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 52.3k ± 0% 26.1k ± 0% -50.16% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 336k ± 1% 269k ± 0% -19.90% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 422k ± 0% 395k ± 0% -6.43% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 401k ± 0% 313k ± 0% -21.79% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 247k ± 0% 143k ± 0% -42.17% (p=0.029 n=4+4) SiteNew/Page_collections-16 282k ± 0% 207k ± 0% -26.55% (p=0.029 n=4+4) ``` Fixes #6716 Fixes #6760 Fixes #6768 Fixes #6778
This commit is contained in:
parent
8585b388d2
commit
c6d650c8c8
46 changed files with 1332 additions and 1446 deletions
|
@ -133,7 +133,7 @@ func (c *commandeer) getErrorWithContext() interface{} {
|
||||||
|
|
||||||
if c.h.verbose {
|
if c.h.verbose {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
herrors.FprintStackTrace(&b, c.buildErr)
|
herrors.FprintStackTraceFromErr(&b, c.buildErr)
|
||||||
m["StackTrace"] = b.String()
|
m["StackTrace"] = b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -716,7 +716,7 @@ func (c *commandeer) handleBuildErr(err error, msg string) {
|
||||||
c.logger.ERROR.Print(msg + ":\n\n")
|
c.logger.ERROR.Print(msg + ":\n\n")
|
||||||
c.logger.ERROR.Println(helpers.FirstUpper(err.Error()))
|
c.logger.ERROR.Println(helpers.FirstUpper(err.Error()))
|
||||||
if !c.h.quiet && c.h.verbose {
|
if !c.h.quiet && c.h.verbose {
|
||||||
herrors.PrintStackTrace(err)
|
herrors.PrintStackTraceFromErr(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -416,7 +416,7 @@ func (c *commandeer) serve(s *serverCmd) error {
|
||||||
roots = []string{""}
|
roots = []string{""}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ, err := c.hugo().TextTmpl.Parse("__default_server_error", buildErrorTemplate)
|
templ, err := c.hugo().TextTmpl().Parse("__default_server_error", buildErrorTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -428,7 +428,7 @@ func (c *commandeer) serve(s *serverCmd) error {
|
||||||
s: s,
|
s: s,
|
||||||
errorTemplate: func(ctx interface{}) (io.Reader, error) {
|
errorTemplate: func(ctx interface{}) (io.Reader, error) {
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
err := c.hugo().Tmpl.Execute(templ, b, ctx)
|
err := c.hugo().Tmpl().Execute(templ, b, ctx)
|
||||||
return b, err
|
return b, err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,14 @@
|
||||||
package herrors
|
package herrors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
_errors "github.com/pkg/errors"
|
_errors "github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -33,13 +36,13 @@ type stackTracer interface {
|
||||||
StackTrace() _errors.StackTrace
|
StackTrace() _errors.StackTrace
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintStackTrace prints the error's stack trace to stdoud.
|
// PrintStackTraceFromErr prints the error's stack trace to stdoud.
|
||||||
func PrintStackTrace(err error) {
|
func PrintStackTraceFromErr(err error) {
|
||||||
FprintStackTrace(os.Stdout, err)
|
FprintStackTraceFromErr(os.Stdout, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FprintStackTrace prints the error's stack trace to w.
|
// FprintStackTraceFromErr prints the error's stack trace to w.
|
||||||
func FprintStackTrace(w io.Writer, err error) {
|
func FprintStackTraceFromErr(w io.Writer, err error) {
|
||||||
if err, ok := err.(stackTracer); ok {
|
if err, ok := err.(stackTracer); ok {
|
||||||
for _, f := range err.StackTrace() {
|
for _, f := range err.StackTrace() {
|
||||||
fmt.Fprintf(w, "%+s:%d\n", f, f)
|
fmt.Fprintf(w, "%+s:%d\n", f, f)
|
||||||
|
@ -47,6 +50,13 @@ func FprintStackTrace(w io.Writer, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrintStackTrace prints the current stacktrace to w.
|
||||||
|
func PrintStackTrace(w io.Writer) {
|
||||||
|
buf := make([]byte, 1<<16)
|
||||||
|
runtime.Stack(buf, true)
|
||||||
|
fmt.Fprintf(w, "%s", buf)
|
||||||
|
}
|
||||||
|
|
||||||
// Recover is a helper function that can be used to capture panics.
|
// Recover is a helper function that can be used to capture panics.
|
||||||
// Put this at the top of a method/function that crashes in a template:
|
// Put this at the top of a method/function that crashes in a template:
|
||||||
// defer herrors.Recover()
|
// defer herrors.Recover()
|
||||||
|
@ -56,7 +66,16 @@ func Recover(args ...interface{}) {
|
||||||
args = append(args, "stacktrace from panic: \n"+string(debug.Stack()), "\n")
|
args = append(args, "stacktrace from panic: \n"+string(debug.Stack()), "\n")
|
||||||
fmt.Println(args...)
|
fmt.Println(args...)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current goroutine id. Used only for debugging.
|
||||||
|
func GetGID() uint64 {
|
||||||
|
b := make([]byte, 64)
|
||||||
|
b = b[:runtime.Stack(b, false)]
|
||||||
|
b = bytes.TrimPrefix(b, []byte("goroutine "))
|
||||||
|
b = b[:bytes.IndexByte(b, ' ')]
|
||||||
|
n, _ := strconv.ParseUint(string(b), 10, 64)
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrFeatureNotAvailable denotes that a feature is unavailable.
|
// ErrFeatureNotAvailable denotes that a feature is unavailable.
|
||||||
|
|
|
@ -21,6 +21,12 @@ import (
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RLocker represents the read locks in sync.RWMutex.
|
||||||
|
type RLocker interface {
|
||||||
|
RLock()
|
||||||
|
RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
// KeyValueStr is a string tuple.
|
// KeyValueStr is a string tuple.
|
||||||
type KeyValueStr struct {
|
type KeyValueStr struct {
|
||||||
Key string
|
Key string
|
||||||
|
|
|
@ -129,9 +129,9 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archety
|
||||||
archetypeTemplate = []byte(archetypeShortcodeReplacementsPre.Replace(string(archetypeTemplate)))
|
archetypeTemplate = []byte(archetypeShortcodeReplacementsPre.Replace(string(archetypeTemplate)))
|
||||||
|
|
||||||
// Reuse the Hugo template setup to get the template funcs properly set up.
|
// Reuse the Hugo template setup to get the template funcs properly set up.
|
||||||
templateHandler := s.Deps.Tmpl.(tpl.TemplateManager)
|
templateHandler := s.Deps.Tmpl().(tpl.TemplateManager)
|
||||||
templateName := "_text/" + helpers.Filename(archetypeFilename)
|
templateName := helpers.Filename(archetypeFilename)
|
||||||
if err := templateHandler.AddTemplate(templateName, string(archetypeTemplate)); err != nil {
|
if err := templateHandler.AddTemplate("_text/"+templateName, string(archetypeTemplate)); err != nil {
|
||||||
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
|
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
37
deps/deps.go
vendored
37
deps/deps.go
vendored
|
@ -5,6 +5,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/cache/filecache"
|
"github.com/gohugoio/hugo/cache/filecache"
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
|
@ -38,10 +39,10 @@ type Deps struct {
|
||||||
DistinctWarningLog *helpers.DistinctLogger
|
DistinctWarningLog *helpers.DistinctLogger
|
||||||
|
|
||||||
// The templates to use. This will usually implement the full tpl.TemplateManager.
|
// The templates to use. This will usually implement the full tpl.TemplateManager.
|
||||||
Tmpl tpl.TemplateHandler `json:"-"`
|
tmpl tpl.TemplateHandler
|
||||||
|
|
||||||
// We use this to parse and execute ad-hoc text templates.
|
// We use this to parse and execute ad-hoc text templates.
|
||||||
TextTmpl tpl.TemplateParseFinder `json:"-"`
|
textTmpl tpl.TemplateParseFinder
|
||||||
|
|
||||||
// The file systems to use.
|
// The file systems to use.
|
||||||
Fs *hugofs.Fs `json:"-"`
|
Fs *hugofs.Fs `json:"-"`
|
||||||
|
@ -92,6 +93,9 @@ type Deps struct {
|
||||||
// BuildStartListeners will be notified before a build starts.
|
// BuildStartListeners will be notified before a build starts.
|
||||||
BuildStartListeners *Listeners
|
BuildStartListeners *Listeners
|
||||||
|
|
||||||
|
// Atomic flags set during a build.
|
||||||
|
BuildFlags BuildFlags
|
||||||
|
|
||||||
*globalErrHandler
|
*globalErrHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,9 +157,20 @@ type ResourceProvider interface {
|
||||||
Clone(deps *Deps) error
|
Clone(deps *Deps) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateHandler returns the used tpl.TemplateFinder as tpl.TemplateHandler.
|
func (d *Deps) Tmpl() tpl.TemplateHandler {
|
||||||
func (d *Deps) TemplateHandler() tpl.TemplateManager {
|
return d.tmpl
|
||||||
return d.Tmpl.(tpl.TemplateManager)
|
}
|
||||||
|
|
||||||
|
func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
|
||||||
|
return d.textTmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Deps) SetTmpl(tmpl tpl.TemplateHandler) {
|
||||||
|
d.tmpl = tmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Deps) SetTextTmpl(tmpl tpl.TemplateParseFinder) {
|
||||||
|
d.textTmpl = tmpl
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadResources loads translations and templates.
|
// LoadResources loads translations and templates.
|
||||||
|
@ -315,6 +330,7 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
|
||||||
}
|
}
|
||||||
|
|
||||||
d.BuildStartListeners = &Listeners{}
|
d.BuildStartListeners = &Listeners{}
|
||||||
|
d.BuildFlags = BuildFlags{}
|
||||||
|
|
||||||
return &d, nil
|
return &d, nil
|
||||||
|
|
||||||
|
@ -358,3 +374,14 @@ type DepsCfg struct {
|
||||||
// Whether we are in running (server) mode
|
// Whether we are in running (server) mode
|
||||||
Running bool
|
Running bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildFlags are flags that may be turned on during a build.
|
||||||
|
type BuildFlags struct {
|
||||||
|
HasLateTemplate atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBuildFlags() BuildFlags {
|
||||||
|
return BuildFlags{
|
||||||
|
//HasLateTemplate: atomic.NewBool(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
deps/deps_test.go
vendored
Normal file
28
deps/deps_test.go
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package deps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildFlags(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
var bf BuildFlags
|
||||||
|
c.Assert(bf.HasLateTemplate.Load(), qt.Equals, false)
|
||||||
|
bf.HasLateTemplate.Store(true)
|
||||||
|
c.Assert(bf.HasLateTemplate.Load(), qt.Equals, true)
|
||||||
|
}
|
1
go.mod
1
go.mod
|
@ -55,6 +55,7 @@ require (
|
||||||
github.com/yuin/goldmark v1.1.21
|
github.com/yuin/goldmark v1.1.21
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5
|
github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5
|
||||||
go.opencensus.io v0.22.0 // indirect
|
go.opencensus.io v0.22.0 // indirect
|
||||||
|
go.uber.org/atomic v1.4.0
|
||||||
gocloud.dev v0.15.0
|
gocloud.dev v0.15.0
|
||||||
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52
|
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -421,6 +421,7 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
|
|
@ -81,7 +81,7 @@ func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
|
func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
|
||||||
handler := newAliasHandler(s.Tmpl, s.Log, allowRoot)
|
handler := newAliasHandler(s.Tmpl(), s.Log, allowRoot)
|
||||||
|
|
||||||
s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
|
s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
|
||||||
|
|
||||||
|
|
|
@ -68,12 +68,12 @@ func TestCascade(t *testing.T) {
|
||||||
42|taxonomy|tags/blue|blue|home.png|tags|HTML-|
|
42|taxonomy|tags/blue|blue|home.png|tags|HTML-|
|
||||||
42|section|sect3|Cascade Home|home.png|sect3|HTML-|
|
42|section|sect3|Cascade Home|home.png|sect3|HTML-|
|
||||||
42|taxonomyTerm|tags|Cascade Home|home.png|tags|HTML-|
|
42|taxonomyTerm|tags|Cascade Home|home.png|tags|HTML-|
|
||||||
42|page|p2.md|Cascade Home|home.png|page|HTML-|
|
42|page|p2.md|Cascade Home|home.png||HTML-|
|
||||||
42|page|sect2/p2.md|Cascade Home|home.png|sect2|HTML-|
|
42|page|sect2/p2.md|Cascade Home|home.png|sect2|HTML-|
|
||||||
42|page|sect3/p1.md|Cascade Home|home.png|sect3|HTML-|
|
42|page|sect3/p1.md|Cascade Home|home.png|sect3|HTML-|
|
||||||
42|taxonomy|tags/green|green|home.png|tags|HTML-|
|
42|taxonomy|tags/green|green|home.png|tags|HTML-|
|
||||||
42|home|_index.md|Home|home.png|page|HTML-|
|
42|home|_index.md|Home|home.png||HTML-|
|
||||||
42|page|p1.md|p1|home.png|page|HTML-|
|
42|page|p1.md|p1|home.png||HTML-|
|
||||||
42|section|sect1/_index.md|Sect1|sect1.png|stype|HTML-|
|
42|section|sect1/_index.md|Sect1|sect1.png|stype|HTML-|
|
||||||
42|section|sect1/s1_2/_index.md|Sect1_2|sect1.png|stype|HTML-|
|
42|section|sect1/s1_2/_index.md|Sect1_2|sect1.png|stype|HTML-|
|
||||||
42|page|sect1/s1_2/p1.md|Sect1_2_p1|sect1.png|stype|HTML-|
|
42|page|sect1/s1_2/p1.md|Sect1_2_p1|sect1.png|stype|HTML-|
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
|
|
||||||
package hugolib
|
package hugolib
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestRenderHooks(t *testing.T) {
|
func TestRenderHooks(t *testing.T) {
|
||||||
config := `
|
config := `
|
||||||
|
|
|
@ -340,6 +340,8 @@ b = "B param"
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModulesIncompatible(t *testing.T) {
|
func TestModulesIncompatible(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
b := newTestSitesBuilder(t).WithWorkingDir("/site").WithConfigFile("toml", `
|
b := newTestSitesBuilder(t).WithWorkingDir("/site").WithConfigFile("toml", `
|
||||||
baseURL="https://example.org"
|
baseURL="https://example.org"
|
||||||
|
|
||||||
|
@ -518,6 +520,7 @@ weight = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMountsProject(t *testing.T) {
|
func TestMountsProject(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
config := `
|
config := `
|
||||||
|
|
||||||
|
@ -547,6 +550,7 @@ title: "My Page"
|
||||||
|
|
||||||
// https://github.com/gohugoio/hugo/issues/6684
|
// https://github.com/gohugoio/hugo/issues/6684
|
||||||
func TestMountsContentFile(t *testing.T) {
|
func TestMountsContentFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-content-file")
|
workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-content-file")
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
|
|
|
@ -121,6 +121,9 @@ type hugoSitesInit struct {
|
||||||
// Loads the data from all of the /data folders.
|
// Loads the data from all of the /data folders.
|
||||||
data *lazy.Init
|
data *lazy.Init
|
||||||
|
|
||||||
|
// Performs late initialization (before render) of the templates.
|
||||||
|
layouts *lazy.Init
|
||||||
|
|
||||||
// Loads the Git info for all the pages if enabled.
|
// Loads the Git info for all the pages if enabled.
|
||||||
gitInfo *lazy.Init
|
gitInfo *lazy.Init
|
||||||
|
|
||||||
|
@ -130,6 +133,7 @@ type hugoSitesInit struct {
|
||||||
|
|
||||||
func (h *hugoSitesInit) Reset() {
|
func (h *hugoSitesInit) Reset() {
|
||||||
h.data.Reset()
|
h.data.Reset()
|
||||||
|
h.layouts.Reset()
|
||||||
h.gitInfo.Reset()
|
h.gitInfo.Reset()
|
||||||
h.translations.Reset()
|
h.translations.Reset()
|
||||||
}
|
}
|
||||||
|
@ -271,6 +275,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
|
||||||
Sites: sites,
|
Sites: sites,
|
||||||
init: &hugoSitesInit{
|
init: &hugoSitesInit{
|
||||||
data: lazy.New(),
|
data: lazy.New(),
|
||||||
|
layouts: lazy.New(),
|
||||||
gitInfo: lazy.New(),
|
gitInfo: lazy.New(),
|
||||||
translations: lazy.New(),
|
translations: lazy.New(),
|
||||||
},
|
},
|
||||||
|
@ -289,6 +294,15 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
h.init.layouts.Add(func() (interface{}, error) {
|
||||||
|
for _, s := range h.Sites {
|
||||||
|
if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
h.init.translations.Add(func() (interface{}, error) {
|
h.init.translations.Add(func() (interface{}, error) {
|
||||||
if len(h.Sites) > 1 {
|
if len(h.Sites) > 1 {
|
||||||
allTranslations := pagesToTranslationsMap(h.Sites)
|
allTranslations := pagesToTranslationsMap(h.Sites)
|
||||||
|
@ -429,10 +443,6 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||||
|
|
||||||
func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateManager) error) func(templ tpl.TemplateManager) error {
|
func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateManager) error) func(templ tpl.TemplateManager) error {
|
||||||
return func(templ tpl.TemplateManager) error {
|
return func(templ tpl.TemplateManager) error {
|
||||||
if err := templ.LoadTemplates(""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, wt := range withTemplates {
|
for _, wt := range withTemplates {
|
||||||
if wt == nil {
|
if wt == nil {
|
||||||
continue
|
continue
|
||||||
|
@ -619,10 +629,10 @@ func (h *HugoSites) renderCrossSitesArtifacts() error {
|
||||||
|
|
||||||
s := h.Sites[0]
|
s := h.Sites[0]
|
||||||
|
|
||||||
smLayouts := []string{"sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml"}
|
templ := s.lookupLayouts("sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml")
|
||||||
|
|
||||||
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemapindex",
|
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemapindex",
|
||||||
s.siteCfg.sitemap.Filename, h.toSiteInfos(), smLayouts...)
|
s.siteCfg.sitemap.Filename, h.toSiteInfos(), templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HugoSites) removePageByFilename(filename string) {
|
func (h *HugoSites) removePageByFilename(filename string) {
|
||||||
|
@ -832,7 +842,7 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
|
||||||
if po.cp == nil {
|
if po.cp == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for id, _ := range idset {
|
for id := range idset {
|
||||||
if po.cp.dependencyTracker.Search(id) != nil {
|
if po.cp.dependencyTracker.Search(id) != nil {
|
||||||
po.cp.Reset()
|
po.cp.Reset()
|
||||||
continue OUTPUTS
|
continue OUTPUTS
|
||||||
|
@ -841,7 +851,7 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range p.shortcodeState.shortcodes {
|
for _, s := range p.shortcodeState.shortcodes {
|
||||||
for id, _ := range idset {
|
for id := range idset {
|
||||||
if idm, ok := s.info.(identity.Manager); ok && idm.Search(id) != nil {
|
if idm, ok := s.info.(identity.Manager); ok && idm.Search(id) != nil {
|
||||||
for _, po := range p.pageOutputs {
|
for _, po := range p.pageOutputs {
|
||||||
if po.cp != nil {
|
if po.cp != nil {
|
||||||
|
|
|
@ -291,6 +291,10 @@ func (h *HugoSites) assemble(bcfg *BuildCfg) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HugoSites) render(config *BuildCfg) error {
|
func (h *HugoSites) render(config *BuildCfg) error {
|
||||||
|
if _, err := h.init.layouts.Do(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
siteRenderContext := &siteRenderContext{cfg: config, multihost: h.multihost}
|
siteRenderContext := &siteRenderContext{cfg: config, multihost: h.multihost}
|
||||||
|
|
||||||
if !config.PartialReRender {
|
if !config.PartialReRender {
|
||||||
|
@ -312,11 +316,6 @@ func (h *HugoSites) render(config *BuildCfg) error {
|
||||||
case <-h.Done():
|
case <-h.Done():
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
// For the non-renderable pages, we use the content iself as
|
|
||||||
// template and we may have to re-parse and execute it for
|
|
||||||
// each output format.
|
|
||||||
h.TemplateHandler().RebuildClone()
|
|
||||||
|
|
||||||
for _, s2 := range h.Sites {
|
for _, s2 := range h.Sites {
|
||||||
// We render site by site, but since the content is lazily rendered
|
// We render site by site, but since the content is lazily rendered
|
||||||
// and a site can "borrow" content from other sites, every site
|
// and a site can "borrow" content from other sites, every site
|
||||||
|
|
|
@ -27,7 +27,7 @@ func (t testSiteBuildErrorAsserter) getFileError(err error) *herrors.ErrorWithFi
|
||||||
|
|
||||||
func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) {
|
func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) {
|
||||||
fe := t.getFileError(err)
|
fe := t.getFileError(err)
|
||||||
t.c.Assert(fe.Position().LineNumber, qt.Equals, lineNumber)
|
t.c.Assert(fe.Position().LineNumber, qt.Equals, lineNumber, qt.Commentf(err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) {
|
func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) {
|
||||||
|
@ -65,7 +65,8 @@ func TestSiteBuildErrors(t *testing.T) {
|
||||||
fileFixer: func(content string) string {
|
fileFixer: func(content string) string {
|
||||||
return strings.Replace(content, ".Title }}", ".Title }", 1)
|
return strings.Replace(content, ".Title }}", ".Title }", 1)
|
||||||
},
|
},
|
||||||
assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
|
// Base templates gets parsed at build time.
|
||||||
|
assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
|
||||||
a.assertLineNumber(4, err)
|
a.assertLineNumber(4, err)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -90,7 +91,7 @@ func TestSiteBuildErrors(t *testing.T) {
|
||||||
a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
|
||||||
a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1)
|
a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1)
|
||||||
a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template")
|
a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template")
|
||||||
a.assertErrorMessage("\"layouts/_default/single.html:5:1\": parse failed: template: _default/single.html:5: unexpected \"}\" in operand", fe.Error())
|
a.assertErrorMessage("\"layouts/foo/single.html:5:1\": parse failed: template: foo/single.html:5: unexpected \"}\" in operand", fe.Error())
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -256,6 +257,13 @@ SINGLE L3:
|
||||||
SINGLE L4:
|
SINGLE L4:
|
||||||
SINGLE L5: {{ .Title }} {{ .Content }}
|
SINGLE L5: {{ .Title }} {{ .Content }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
`))
|
||||||
|
|
||||||
|
b.WithTemplatesAdded("layouts/foo/single.html", f(single, `
|
||||||
|
SINGLE L2:
|
||||||
|
SINGLE L3:
|
||||||
|
SINGLE L4:
|
||||||
|
SINGLE L5: {{ .Title }} {{ .Content }}
|
||||||
`))
|
`))
|
||||||
|
|
||||||
b.WithContent("myyaml.md", f(yamlcontent, `---
|
b.WithContent("myyaml.md", f(yamlcontent, `---
|
||||||
|
|
|
@ -23,16 +23,21 @@ func TestSitesRebuild(t *testing.T) {
|
||||||
baseURL = "https://example.com"
|
baseURL = "https://example.com"
|
||||||
title = "Rebuild this"
|
title = "Rebuild this"
|
||||||
contentDir = "content"
|
contentDir = "content"
|
||||||
|
enableInlineShortcodes = true
|
||||||
|
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
contentFilename := "content/blog/page1.md"
|
var (
|
||||||
|
contentFilename = "content/blog/page1.md"
|
||||||
|
dataFilename = "data/mydata.toml"
|
||||||
|
)
|
||||||
|
|
||||||
b := newTestSitesBuilder(t).WithConfigFile("toml", configFile)
|
createSiteBuilder := func(t testing.TB) *sitesBuilder {
|
||||||
|
b := newTestSitesBuilder(t).WithConfigFile("toml", configFile).Running()
|
||||||
|
|
||||||
|
b.WithSourceFile(dataFilename, `hugo = "Rocks!"`)
|
||||||
|
|
||||||
// To simulate https://github.com/gohugoio/hugo/issues/5838, the home page
|
|
||||||
// needs a content page.
|
|
||||||
b.WithContent("content/_index.md", `---
|
b.WithContent("content/_index.md", `---
|
||||||
title: Home, Sweet Home!
|
title: Home, Sweet Home!
|
||||||
---
|
---
|
||||||
|
@ -48,6 +53,27 @@ paginate: 3
|
||||||
|
|
||||||
Content.
|
Content.
|
||||||
|
|
||||||
|
{{< badge.inline >}}
|
||||||
|
Data Inline: {{ site.Data.mydata.hugo }}
|
||||||
|
{{< /badge.inline >}}
|
||||||
|
`)
|
||||||
|
|
||||||
|
// For .Page.Render tests
|
||||||
|
b.WithContent("prender.md", `---
|
||||||
|
title: Page 1
|
||||||
|
---
|
||||||
|
|
||||||
|
Content for Page 1.
|
||||||
|
|
||||||
|
{{< dorender >}}
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.WithTemplatesAdded(
|
||||||
|
"layouts/shortcodes/dorender.html", `
|
||||||
|
{{ $p := .Page }}
|
||||||
|
Render {{ $p.RelPermalink }}: {{ $p.Render "single" }}
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
b.WithTemplatesAdded("index.html", `
|
b.WithTemplatesAdded("index.html", `
|
||||||
|
@ -57,9 +83,24 @@ Content.
|
||||||
{{ range .Site.RegularPages }}
|
{{ range .Site.RegularPages }}
|
||||||
* Page Pages: {{ .Title }}|Summary: {{ .Summary }}|Content: {{ .Content }}
|
* Page Pages: {{ .Title }}|Summary: {{ .Summary }}|Content: {{ .Content }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
Content: {{ .Content }}
|
||||||
|
Data: {{ site.Data.mydata.hugo }}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
b.Running().Build(BuildCfg{})
|
b.WithTemplatesAdded("layouts/partials/mypartial1.html", `Mypartial1`)
|
||||||
|
b.WithTemplatesAdded("layouts/partials/mypartial2.html", `Mypartial2`)
|
||||||
|
b.WithTemplatesAdded("layouts/partials/mypartial3.html", `Mypartial3`)
|
||||||
|
b.WithTemplatesAdded("_default/single.html", `{{ define "main" }}Single Main: {{ .Title }}|Mypartial1: {{ partial "mypartial1.html" }}{{ end }}`)
|
||||||
|
b.WithTemplatesAdded("_default/list.html", `{{ define "main" }}List Main: {{ .Title }}{{ end }}`)
|
||||||
|
b.WithTemplatesAdded("_default/baseof.html", `Baseof:{{ block "main" . }}Baseof Main{{ end }}|Mypartial3: {{ partial "mypartial3.html" }}:END`)
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Refresh paginator on edit", func(t *testing.T) {
|
||||||
|
b := createSiteBuilder(t)
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
b.AssertFileContent("public/index.html", "* Page Paginate: Page 1|Summary: Initial summary|Content: <p>Content.</p>")
|
b.AssertFileContent("public/index.html", "* Page Paginate: Page 1|Summary: Initial summary|Content: <p>Content.</p>")
|
||||||
|
|
||||||
|
@ -76,8 +117,79 @@ Edited content.
|
||||||
b.Build(BuildCfg{})
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
b.AssertFileContent("public/index.html", "* Page Paginate: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
|
b.AssertFileContent("public/index.html", "* Page Paginate: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
|
||||||
|
|
||||||
// https://github.com/gohugoio/hugo/issues/5833
|
// https://github.com/gohugoio/hugo/issues/5833
|
||||||
b.AssertFileContent("public/index.html", "* Page Pages: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
|
b.AssertFileContent("public/index.html", "* Page Pages: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://github.com/gohugoio/hugo/issues/6768
|
||||||
|
t.Run("Edit data", func(t *testing.T) {
|
||||||
|
b := createSiteBuilder(t)
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html", `
|
||||||
|
Data: Rocks!
|
||||||
|
Data Inline: Rocks!
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.EditFiles(dataFilename, `hugo = "Rules!"`)
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html", `
|
||||||
|
Data: Rules!
|
||||||
|
Data Inline: Rules!`)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Page.Render, edit baseof", func(t *testing.T) {
|
||||||
|
b := createSiteBuilder(t)
|
||||||
|
|
||||||
|
b.WithTemplatesAdded("index.html", `
|
||||||
|
{{ $p := site.GetPage "prender.md" }}
|
||||||
|
prender: {{ $p.Title }}|{{ $p.Content }}
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html", `
|
||||||
|
Render /prender/: Baseof:Single Main: Page 1|Mypartial1: Mypartial1|Mypartial3: Mypartial3:END
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.EditFiles("layouts/_default/baseof.html", `Baseof Edited:{{ block "main" . }}Baseof Main{{ end }}:END`)
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html", `
|
||||||
|
Render /prender/: Baseof Edited:Single Main: Page 1|Mypartial1: Mypartial1:END
|
||||||
|
`)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Page.Render, edit partial in baseof", func(t *testing.T) {
|
||||||
|
b := createSiteBuilder(t)
|
||||||
|
|
||||||
|
b.WithTemplatesAdded("index.html", `
|
||||||
|
{{ $p := site.GetPage "prender.md" }}
|
||||||
|
prender: {{ $p.Title }}|{{ $p.Content }}
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html", `
|
||||||
|
Render /prender/: Baseof:Single Main: Page 1|Mypartial1: Mypartial1|Mypartial3: Mypartial3:END
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.EditFiles("layouts/partials/mypartial3.html", `Mypartial3 Edited`)
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html", `
|
||||||
|
Render /prender/: Baseof:Single Main: Page 1|Mypartial1: Mypartial1|Mypartial3: Mypartial3 Edited:END
|
||||||
|
`)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,37 +337,33 @@ func (p *pageState) createRenderHooks(f output.Format) (*hooks.Render, error) {
|
||||||
layoutDescriptor.Layout = ""
|
layoutDescriptor.Layout = ""
|
||||||
|
|
||||||
layoutDescriptor.Kind = "render-link"
|
layoutDescriptor.Kind = "render-link"
|
||||||
linkLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f)
|
linkTempl, linkTemplFound, err := p.s.Tmpl().LookupLayout(layoutDescriptor, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutDescriptor.Kind = "render-image"
|
layoutDescriptor.Kind = "render-image"
|
||||||
imageLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f)
|
imgTempl, imgTemplFound, err := p.s.Tmpl().LookupLayout(layoutDescriptor, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if linkLayouts == nil && imageLayouts == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var linkRenderer hooks.LinkRenderer
|
var linkRenderer hooks.LinkRenderer
|
||||||
var imageRenderer hooks.LinkRenderer
|
var imageRenderer hooks.LinkRenderer
|
||||||
|
|
||||||
if templ, found := p.s.lookupTemplate(linkLayouts...); found {
|
if linkTemplFound {
|
||||||
linkRenderer = contentLinkRenderer{
|
linkRenderer = contentLinkRenderer{
|
||||||
templateHandler: p.s.Tmpl,
|
templateHandler: p.s.Tmpl(),
|
||||||
Provider: templ.(tpl.Info),
|
Provider: linkTempl.(tpl.Info),
|
||||||
templ: templ,
|
templ: linkTempl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if templ, found := p.s.lookupTemplate(imageLayouts...); found {
|
if imgTemplFound {
|
||||||
imageRenderer = contentLinkRenderer{
|
imageRenderer = contentLinkRenderer{
|
||||||
templateHandler: p.s.Tmpl,
|
templateHandler: p.s.Tmpl(),
|
||||||
Provider: templ.(tpl.Info),
|
Provider: imgTempl.(tpl.Info),
|
||||||
templ: templ,
|
templ: imgTempl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,24 +402,25 @@ func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageState) getLayouts(layouts ...string) ([]string, error) {
|
func (p *pageState) resolveTemplate(layouts ...string) (tpl.Template, bool, error) {
|
||||||
f := p.outputFormat()
|
f := p.outputFormat()
|
||||||
|
|
||||||
if len(layouts) == 0 {
|
if len(layouts) == 0 {
|
||||||
selfLayout := p.selfLayoutForOutput(f)
|
selfLayout := p.selfLayoutForOutput(f)
|
||||||
if selfLayout != "" {
|
if selfLayout != "" {
|
||||||
return []string{selfLayout}, nil
|
templ, found := p.s.Tmpl().Lookup(selfLayout)
|
||||||
|
return templ, found, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutDescriptor := p.getLayoutDescriptor()
|
d := p.getLayoutDescriptor()
|
||||||
|
|
||||||
if len(layouts) > 0 {
|
if len(layouts) > 0 {
|
||||||
layoutDescriptor.Layout = layouts[0]
|
d.Layout = layouts[0]
|
||||||
layoutDescriptor.LayoutOverride = true
|
d.LayoutOverride = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.s.layoutHandler.For(layoutDescriptor, f)
|
return p.s.Tmpl().LookupLayout(d, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is serialized
|
// This is serialized
|
||||||
|
@ -601,31 +598,21 @@ func (p *pageState) RenderWithTemplateInfo(info tpl.Info, layout ...string) (tem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageState) Render(layout ...string) (template.HTML, error) {
|
func (p *pageState) Render(layout ...string) (template.HTML, error) {
|
||||||
l, err := p.getLayouts(layout...)
|
templ, found, err := p.resolveTemplate(layout...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", p.wrapError(errors.Errorf("failed to resolve layout %v", layout))
|
return "", p.wrapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, layout := range l {
|
|
||||||
templ, found := p.s.Tmpl.Lookup(layout)
|
|
||||||
if !found {
|
if !found {
|
||||||
// This is legacy from when we had only one output format and
|
return "", nil
|
||||||
// HTML templates only. Some have references to layouts without suffix.
|
|
||||||
// We default to good old HTML.
|
|
||||||
templ, _ = p.s.Tmpl.Lookup(layout + ".html")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if templ != nil {
|
|
||||||
p.addDependency(templ.(tpl.Info))
|
p.addDependency(templ.(tpl.Info))
|
||||||
res, err := executeToString(p.s.Tmpl, templ, p)
|
res, err := executeToString(p.s.Tmpl(), templ, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout))
|
return "", p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout))
|
||||||
}
|
}
|
||||||
return template.HTML(res), nil
|
return template.HTML(res), nil
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -689,6 +676,7 @@ Loop:
|
||||||
// This is HTML without front matter. It can still have shortcodes.
|
// This is HTML without front matter. It can still have shortcodes.
|
||||||
p.selfLayout = "__" + p.File().Filename()
|
p.selfLayout = "__" + p.File().Filename()
|
||||||
p.renderable = false
|
p.renderable = false
|
||||||
|
p.s.BuildFlags.HasLateTemplate.CAS(false, true)
|
||||||
rn.AddBytes(it)
|
rn.AddBytes(it)
|
||||||
case it.IsFrontMatter():
|
case it.IsFrontMatter():
|
||||||
f := metadecoders.FormatFromFrontMatterType(it.Type)
|
f := metadecoders.FormatFromFrontMatterType(it.Type)
|
||||||
|
|
|
@ -296,11 +296,7 @@ func (p *pageMeta) Type() string {
|
||||||
return p.contentType
|
return p.contentType
|
||||||
}
|
}
|
||||||
|
|
||||||
if x := p.Section(); x != "" {
|
return p.Section()
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
return "page"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageMeta) Weight() int {
|
func (p *pageMeta) Weight() int {
|
||||||
|
|
|
@ -189,10 +189,11 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
|
||||||
html := cp.p.s.ContentSpec.TrimShortHTML(b.Bytes())
|
html := cp.p.s.ContentSpec.TrimShortHTML(b.Bytes())
|
||||||
cp.summary = helpers.BytesToHTML(html)
|
cp.summary = helpers.BytesToHTML(html)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cp.content = helpers.BytesToHTML(cp.workContent)
|
cp.content = helpers.BytesToHTML(cp.workContent)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if !p.renderable {
|
if !p.renderable {
|
||||||
err := cp.addSelfTemplate()
|
err := cp.addSelfTemplate()
|
||||||
return err
|
return err
|
||||||
|
@ -427,7 +428,7 @@ func (p *pageContentOutput) setWordCounts(isCJKLanguage bool) {
|
||||||
|
|
||||||
func (p *pageContentOutput) addSelfTemplate() error {
|
func (p *pageContentOutput) addSelfTemplate() error {
|
||||||
self := p.p.selfLayoutForOutput(p.f)
|
self := p.p.selfLayoutForOutput(p.f)
|
||||||
err := p.p.s.TemplateHandler().AddLateTemplate(self, string(p.content))
|
err := p.p.s.Tmpl().(tpl.TemplateManager).AddLateTemplate(self, string(p.workContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -333,12 +333,6 @@ func checkPageSummary(t *testing.T, page page.Page, summary string, msg ...inter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPageType(t *testing.T, page page.Page, pageType string) {
|
|
||||||
if page.Type() != pageType {
|
|
||||||
t.Fatalf("Page type is: %s. Expected: %s", page.Type(), pageType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPageDate(t *testing.T, page page.Page, time time.Time) {
|
func checkPageDate(t *testing.T, page page.Page, time time.Time) {
|
||||||
if page.Date() != time {
|
if page.Date() != time {
|
||||||
t.Fatalf("Page date is: %s. Expected: %s", page.Date(), time)
|
t.Fatalf("Page date is: %s. Expected: %s", page.Date(), time)
|
||||||
|
@ -542,7 +536,6 @@ func TestCreateNewPage(t *testing.T) {
|
||||||
checkPageTitle(t, p, "Simple")
|
checkPageTitle(t, p, "Simple")
|
||||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n"))
|
checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n"))
|
||||||
checkPageSummary(t, p, "Simple Page")
|
checkPageSummary(t, p, "Simple Page")
|
||||||
checkPageType(t, p, "page")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := map[string]interface{}{
|
settings := map[string]interface{}{
|
||||||
|
@ -562,7 +555,6 @@ func TestPageSummary(t *testing.T) {
|
||||||
checkPageContent(t, p, normalizeExpected(ext, "<p><a href=\"https://lipsum.com/\">Lorem ipsum</a> dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n\n<p>Additional text.</p>\n\n<p>Further text.</p>\n"), ext)
|
checkPageContent(t, p, normalizeExpected(ext, "<p><a href=\"https://lipsum.com/\">Lorem ipsum</a> dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n\n<p>Additional text.</p>\n\n<p>Further text.</p>\n"), ext)
|
||||||
checkPageSummary(t, p, normalizeExpected(ext, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Additional text."), ext)
|
checkPageSummary(t, p, normalizeExpected(ext, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Additional text."), ext)
|
||||||
}
|
}
|
||||||
checkPageType(t, p, "page")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithoutSummaryDelimiter)
|
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithoutSummaryDelimiter)
|
||||||
|
@ -575,7 +567,6 @@ func TestPageWithDelimiter(t *testing.T) {
|
||||||
checkPageTitle(t, p, "Simple")
|
checkPageTitle(t, p, "Simple")
|
||||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n"), ext)
|
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n"), ext)
|
||||||
checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>"), ext)
|
checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>"), ext)
|
||||||
checkPageType(t, p, "page")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter)
|
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter)
|
||||||
|
@ -591,7 +582,6 @@ func TestPageWithSummaryParameter(t *testing.T) {
|
||||||
if ext != "ad" && ext != "rst" {
|
if ext != "ad" && ext != "rst" {
|
||||||
checkPageSummary(t, p, normalizeExpected(ext, "Page with summary parameter and <a href=\"http://www.example.com/\">a link</a>"), ext)
|
checkPageSummary(t, p, normalizeExpected(ext, "Page with summary parameter and <a href=\"http://www.example.com/\">a link</a>"), ext)
|
||||||
}
|
}
|
||||||
checkPageType(t, p, "page")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryParameter)
|
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryParameter)
|
||||||
|
@ -663,7 +653,6 @@ func TestPageWithShortCodeInSummary(t *testing.T) {
|
||||||
checkPageTitle(t, p, "Simple")
|
checkPageTitle(t, p, "Simple")
|
||||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure> <img src=\"/not/real\"/> </figure> . More text here.</p><p>Some more text</p>"))
|
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure> <img src=\"/not/real\"/> </figure> . More text here.</p><p>Some more text</p>"))
|
||||||
checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text")
|
checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text")
|
||||||
checkPageType(t, p, "page")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary)
|
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary)
|
||||||
|
@ -713,8 +702,6 @@ func TestPageWithMoreTag(t *testing.T) {
|
||||||
checkPageTitle(t, p, "Simple")
|
checkPageTitle(t, p, "Simple")
|
||||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n"))
|
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n"))
|
||||||
checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>"))
|
checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>"))
|
||||||
checkPageType(t, p, "page")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine)
|
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine)
|
||||||
|
|
|
@ -303,7 +303,7 @@ func renderShortcode(
|
||||||
templStr := sc.innerString()
|
templStr := sc.innerString()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
tmpl, err = s.TextTmpl.Parse(templName, templStr)
|
tmpl, err = s.TextTmpl().Parse(templName, templStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fe := herrors.ToFileError("html", err)
|
fe := herrors.ToFileError("html", err)
|
||||||
l1, l2 := p.posOffset(sc.pos).LineNumber, fe.Position().LineNumber
|
l1, l2 := p.posOffset(sc.pos).LineNumber, fe.Position().LineNumber
|
||||||
|
@ -314,14 +314,14 @@ func renderShortcode(
|
||||||
} else {
|
} else {
|
||||||
// Re-use of shortcode defined earlier in the same page.
|
// Re-use of shortcode defined earlier in the same page.
|
||||||
var found bool
|
var found bool
|
||||||
tmpl, found = s.TextTmpl.Lookup(templName)
|
tmpl, found = s.TextTmpl().Lookup(templName)
|
||||||
if !found {
|
if !found {
|
||||||
return "", false, _errors.Errorf("no earlier definition of shortcode %q found", sc.name)
|
return "", false, _errors.Errorf("no earlier definition of shortcode %q found", sc.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var found, more bool
|
var found, more bool
|
||||||
tmpl, found, more = s.Tmpl.LookupVariant(sc.name, tplVariants)
|
tmpl, found, more = s.Tmpl().LookupVariant(sc.name, tplVariants)
|
||||||
if !found {
|
if !found {
|
||||||
s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.File().Path())
|
s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.File().Path())
|
||||||
return "", false, nil
|
return "", false, nil
|
||||||
|
@ -395,7 +395,7 @@ func renderShortcode(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := renderShortcodeWithPage(s.Tmpl, tmpl, data)
|
result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data)
|
||||||
|
|
||||||
if err != nil && sc.isInline {
|
if err != nil && sc.isInline {
|
||||||
fe := herrors.ToFileError("html", err)
|
fe := herrors.ToFileError("html", err)
|
||||||
|
@ -537,7 +537,7 @@ Loop:
|
||||||
// Check if the template expects inner content.
|
// Check if the template expects inner content.
|
||||||
// We pick the first template for an arbitrary output format
|
// We pick the first template for an arbitrary output format
|
||||||
// if more than one. It is "all inner or no inner".
|
// if more than one. It is "all inner or no inner".
|
||||||
tmpl, found, _ := s.s.Tmpl.LookupVariant(sc.name, tpl.TemplateVariants{})
|
tmpl, found, _ := s.s.Tmpl().LookupVariant(sc.name, tpl.TemplateVariants{})
|
||||||
if !found {
|
if !found {
|
||||||
return nil, _errors.Errorf("template for shortcode %q not found", sc.name)
|
return nil, _errors.Errorf("template for shortcode %q not found", sc.name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,8 +105,6 @@ type Site struct {
|
||||||
Sections Taxonomy
|
Sections Taxonomy
|
||||||
Info SiteInfo
|
Info SiteInfo
|
||||||
|
|
||||||
layoutHandler *output.LayoutHandler
|
|
||||||
|
|
||||||
language *langs.Language
|
language *langs.Language
|
||||||
|
|
||||||
siteCfg siteConfigHolder
|
siteCfg siteConfigHolder
|
||||||
|
@ -324,7 +322,6 @@ func (s *Site) isEnabled(kind string) bool {
|
||||||
// reset returns a new Site prepared for rebuild.
|
// reset returns a new Site prepared for rebuild.
|
||||||
func (s *Site) reset() *Site {
|
func (s *Site) reset() *Site {
|
||||||
return &Site{Deps: s.Deps,
|
return &Site{Deps: s.Deps,
|
||||||
layoutHandler: output.NewLayoutHandler(),
|
|
||||||
disabledKinds: s.disabledKinds,
|
disabledKinds: s.disabledKinds,
|
||||||
titleFunc: s.titleFunc,
|
titleFunc: s.titleFunc,
|
||||||
relatedDocsHandler: s.relatedDocsHandler.Clone(),
|
relatedDocsHandler: s.relatedDocsHandler.Clone(),
|
||||||
|
@ -439,7 +436,6 @@ func newSite(cfg deps.DepsCfg) (*Site, error) {
|
||||||
|
|
||||||
s := &Site{
|
s := &Site{
|
||||||
PageCollections: c,
|
PageCollections: c,
|
||||||
layoutHandler: output.NewLayoutHandler(),
|
|
||||||
language: cfg.Language,
|
language: cfg.Language,
|
||||||
disabledKinds: disabledKinds,
|
disabledKinds: disabledKinds,
|
||||||
titleFunc: titleFunc,
|
titleFunc: titleFunc,
|
||||||
|
@ -936,7 +932,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
|
||||||
sourceChanged = append(sourceChanged, ev)
|
sourceChanged = append(sourceChanged, ev)
|
||||||
case files.ComponentFolderLayouts:
|
case files.ComponentFolderLayouts:
|
||||||
tmplChanged = true
|
tmplChanged = true
|
||||||
if _, found := s.Tmpl.Lookup(id.Path); !found {
|
if !s.Tmpl().HasTemplate(id.Path) {
|
||||||
tmplAdded = true
|
tmplAdded = true
|
||||||
}
|
}
|
||||||
if tmplAdded {
|
if tmplAdded {
|
||||||
|
@ -1030,7 +1026,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
|
||||||
sourceFilesChanged[ev.Name] = true
|
sourceFilesChanged[ev.Name] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.ErrRecovery || tmplAdded {
|
if config.ErrRecovery || tmplAdded || dataChanged {
|
||||||
h.resetPageState()
|
h.resetPageState()
|
||||||
} else {
|
} else {
|
||||||
h.resetPageStateFromEvents(changeIdentities)
|
h.resetPageStateFromEvents(changeIdentities)
|
||||||
|
@ -1226,10 +1222,9 @@ func (s *Site) initializeSiteInfo() error {
|
||||||
func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) {
|
func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) {
|
||||||
for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() {
|
for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() {
|
||||||
if p := fs.Path(e.Name); p != "" {
|
if p := fs.Path(e.Name); p != "" {
|
||||||
return identity.NewPathIdentity(fs.Name, p), true
|
return identity.NewPathIdentity(fs.Name, filepath.ToSlash(p)), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return identity.PathIdentity{}, false
|
return identity.PathIdentity{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1464,12 +1459,22 @@ func (s *Site) permalink(link string) string {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, layouts ...string) error {
|
func (s *Site) lookupLayouts(layouts ...string) tpl.Template {
|
||||||
|
for _, l := range layouts {
|
||||||
|
if templ, found := s.Tmpl().Lookup(l); found {
|
||||||
|
return templ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, templ tpl.Template) error {
|
||||||
s.Log.DEBUG.Printf("Render XML for %q to %q", name, targetPath)
|
s.Log.DEBUG.Printf("Render XML for %q to %q", name, targetPath)
|
||||||
renderBuffer := bp.GetBuffer()
|
renderBuffer := bp.GetBuffer()
|
||||||
defer bp.PutBuffer(renderBuffer)
|
defer bp.PutBuffer(renderBuffer)
|
||||||
|
|
||||||
if err := s.renderForLayouts(name, "", d, renderBuffer, layouts...); err != nil {
|
if err := s.renderForTemplate(name, "", d, renderBuffer, templ); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1498,13 +1503,13 @@ func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath st
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, layouts ...string) error {
|
func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, templ tpl.Template) error {
|
||||||
renderBuffer := bp.GetBuffer()
|
renderBuffer := bp.GetBuffer()
|
||||||
defer bp.PutBuffer(renderBuffer)
|
defer bp.PutBuffer(renderBuffer)
|
||||||
|
|
||||||
of := p.outputFormat()
|
of := p.outputFormat()
|
||||||
|
|
||||||
if err := s.renderForLayouts(p.Kind(), of.Name, p, renderBuffer, layouts...); err != nil {
|
if err := s.renderForTemplate(p.Kind(), of.Name, p, renderBuffer, templ); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1571,56 +1576,26 @@ func (r contentLinkRenderer) Render(w io.Writer, ctx hooks.LinkContext) error {
|
||||||
return r.templateHandler.Execute(r.templ, w, ctx)
|
return r.templateHandler.Execute(r.templ, w, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
|
func (s *Site) renderForTemplate(name, outputFormat string, d interface{}, w io.Writer, templ tpl.Template) (err error) {
|
||||||
for _, l := range layouts {
|
|
||||||
if templ, found := s.Tmpl.Lookup(l); found {
|
|
||||||
return templ, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Site) renderForLayouts(name, outputFormat string, d interface{}, w io.Writer, layouts ...string) (err error) {
|
|
||||||
templ := s.findFirstTemplate(layouts...)
|
|
||||||
if templ == nil {
|
if templ == nil {
|
||||||
log := s.Log.WARN
|
s.logMissingLayout(name, "", outputFormat)
|
||||||
if infoOnMissingLayout[name] {
|
|
||||||
log = s.Log.INFO
|
|
||||||
}
|
|
||||||
|
|
||||||
errMsg := "You should create a template file which matches Hugo Layouts Lookup Rules for this combination."
|
|
||||||
var args []interface{}
|
|
||||||
msg := "found no layout file for"
|
|
||||||
if outputFormat != "" {
|
|
||||||
msg += " %q"
|
|
||||||
args = append(args, outputFormat)
|
|
||||||
}
|
|
||||||
if name != "" {
|
|
||||||
msg += " for %q"
|
|
||||||
args = append(args, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg += ": " + errMsg
|
|
||||||
|
|
||||||
log.Printf(msg, args...)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = s.Tmpl.Execute(templ, w, d); err != nil {
|
if err = s.Tmpl().Execute(templ, w, d); err != nil {
|
||||||
return _errors.Wrapf(err, "render of %q failed", name)
|
return _errors.Wrapf(err, "render of %q failed", name)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) findFirstTemplate(layouts ...string) tpl.Template {
|
func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
|
||||||
for _, layout := range layouts {
|
for _, l := range layouts {
|
||||||
if templ, found := s.Tmpl.Lookup(layout); found {
|
if templ, found := s.Tmpl().Lookup(l); found {
|
||||||
return templ
|
return templ, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) publish(statCounter *uint64, path string, r io.Reader) (err error) {
|
func (s *Site) publish(statCounter *uint64, path string, r io.Reader) (err error) {
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/tpl"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/output"
|
"github.com/gohugoio/hugo/output"
|
||||||
|
@ -136,33 +138,62 @@ func pageRenderer(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts, err := p.getLayouts()
|
templ, found, err := p.resolveTemplate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.ERROR.Printf("Failed to resolve layout for output %q for page %q: %s", f.Name, p, err)
|
s.SendError(p.errorf(err, "failed to resolve template"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
s.logMissingLayout("", p.Kind(), f.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
targetPath := p.targetPaths().TargetFilename
|
targetPath := p.targetPaths().TargetFilename
|
||||||
|
|
||||||
if targetPath == "" {
|
if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+p.Title(), targetPath, p, templ); err != nil {
|
||||||
s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", f.Name, p, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+p.Title(), targetPath, p, layouts...); err != nil {
|
|
||||||
results <- err
|
results <- err
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.paginator != nil && p.paginator.current != nil {
|
if p.paginator != nil && p.paginator.current != nil {
|
||||||
if err := s.renderPaginator(p, layouts); err != nil {
|
if err := s.renderPaginator(p, templ); err != nil {
|
||||||
results <- err
|
results <- err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Site) logMissingLayout(name, kind, outputFormat string) {
|
||||||
|
log := s.Log.WARN
|
||||||
|
if name != "" && infoOnMissingLayout[name] {
|
||||||
|
log = s.Log.INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
errMsg := "You should create a template file which matches Hugo Layouts Lookup Rules for this combination."
|
||||||
|
var args []interface{}
|
||||||
|
msg := "found no layout file for"
|
||||||
|
if outputFormat != "" {
|
||||||
|
msg += " %q"
|
||||||
|
args = append(args, outputFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind != "" {
|
||||||
|
msg += " for kind %q"
|
||||||
|
args = append(args, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
msg += " for %q"
|
||||||
|
args = append(args, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg += ": " + errMsg
|
||||||
|
|
||||||
|
log.Printf(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// renderPaginator must be run after the owning Page has been rendered.
|
// renderPaginator must be run after the owning Page has been rendered.
|
||||||
func (s *Site) renderPaginator(p *pageState, layouts []string) error {
|
func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error {
|
||||||
|
|
||||||
paginatePath := s.Cfg.GetString("paginatePath")
|
paginatePath := s.Cfg.GetString("paginatePath")
|
||||||
|
|
||||||
|
@ -192,7 +223,7 @@ func (s *Site) renderPaginator(p *pageState, layouts []string) error {
|
||||||
if err := s.renderAndWritePage(
|
if err := s.renderAndWritePage(
|
||||||
&s.PathSpec.ProcessingStats.PaginatorPages,
|
&s.PathSpec.ProcessingStats.PaginatorPages,
|
||||||
p.Title(),
|
p.Title(),
|
||||||
targetPaths.TargetFilename, p, layouts...); err != nil {
|
targetPaths.TargetFilename, p, templ); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,15 +251,14 @@ func (s *Site) render404() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nfLayouts := []string{"404.html"}
|
templ := s.lookupLayouts("404.html")
|
||||||
|
|
||||||
targetPath := p.targetPaths().TargetFilename
|
targetPath := p.targetPaths().TargetFilename
|
||||||
|
|
||||||
if targetPath == "" {
|
if targetPath == "" {
|
||||||
return errors.New("failed to create targetPath for 404 page")
|
return errors.New("failed to create targetPath for 404 page")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, nfLayouts...)
|
return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) renderSitemap() error {
|
func (s *Site) renderSitemap() error {
|
||||||
|
@ -255,9 +285,9 @@ func (s *Site) renderSitemap() error {
|
||||||
return errors.New("failed to create targetPath for sitemap")
|
return errors.New("failed to create targetPath for sitemap")
|
||||||
}
|
}
|
||||||
|
|
||||||
smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}
|
templ := s.lookupLayouts("sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml")
|
||||||
|
|
||||||
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, smLayouts...)
|
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) renderRobotsTXT() error {
|
func (s *Site) renderRobotsTXT() error {
|
||||||
|
@ -282,9 +312,9 @@ func (s *Site) renderRobotsTXT() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}
|
templ := s.lookupLayouts("robots.txt", "_default/robots.txt", "_internal/_default/robots.txt")
|
||||||
|
|
||||||
return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, rLayouts...)
|
return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, templ)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -244,6 +244,178 @@ Page Content
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateLateTemplates(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
b := newTestSitesBuilder(t).WithSimpleConfigFile().Running()
|
||||||
|
|
||||||
|
numPages := 500 // To get some parallelism
|
||||||
|
homeTempl := `
|
||||||
|
Len RegularPages: {{ len site.RegularPages }}
|
||||||
|
{{ range site.RegularPages }}
|
||||||
|
Link: {{ .RelPermalink }} Len Content: {{ len .Content }}
|
||||||
|
{{ end }}
|
||||||
|
`
|
||||||
|
pageTemplate := `<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ .RelPermalink }}</title>
|
||||||
|
<meta name="description" content="The HTML5 Herald">
|
||||||
|
<meta name="author" content="SitePoint">
|
||||||
|
<link rel="stylesheet" href="css/styles.css?v=1.0">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{{ .RelPermalink }}</h1>
|
||||||
|
<p>Shortcode: {{< shortcode >}}</p>
|
||||||
|
<p>Partial: {{ partial "mypartial.html" . }}</p>
|
||||||
|
<script src="js/scripts.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
b.WithTemplatesAdded(
|
||||||
|
"index.html", homeTempl,
|
||||||
|
"partials/mypartial.html", `this my partial`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make sure we get some parallelism.
|
||||||
|
for i := 0; i < numPages; i++ {
|
||||||
|
b.WithContent(fmt.Sprintf("page%d.html", i+1), pageTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html", fmt.Sprintf(`
|
||||||
|
Len RegularPages: %d
|
||||||
|
Link: /page3/ Len Content: 0
|
||||||
|
Link: /page22/ Len Content: 0
|
||||||
|
`, numPages))
|
||||||
|
|
||||||
|
for i := 0; i < numPages; i++ {
|
||||||
|
b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", i+1),
|
||||||
|
fmt.Sprintf(`<title>/page%d/</title>`, i+1),
|
||||||
|
`<p>Shortcode: Shortcode: Hello</p>`,
|
||||||
|
"<p>Partial: this my partial</p>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.EditFiles(
|
||||||
|
"layouts/partials/mypartial.html", `this my changed partial`,
|
||||||
|
"layouts/index.html", (homeTempl + "CHANGED"),
|
||||||
|
)
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
b.EditFiles(fmt.Sprintf("content/page%d.html", i+1), pageTemplate+"CHANGED")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
b.AssertFileContent("public/index.html", fmt.Sprintf(`
|
||||||
|
Len RegularPages: %d
|
||||||
|
Link: /page3/ Len Content: 0
|
||||||
|
Link: /page2/ Len Content: 0
|
||||||
|
CHANGED
|
||||||
|
`, numPages))
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", i+1),
|
||||||
|
fmt.Sprintf(`<title>/page%d/</title>`, i+1),
|
||||||
|
`<p>Shortcode: Shortcode: Hello</p>`,
|
||||||
|
"<p>Partial: this my changed partial</p>",
|
||||||
|
"CHANGED",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTemplateManyBaseTemplates(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
b := newTestSitesBuilder(t).WithSimpleConfigFile()
|
||||||
|
|
||||||
|
numPages := 100 // To get some parallelism
|
||||||
|
|
||||||
|
pageTemplate := `---
|
||||||
|
title: "Page %d"
|
||||||
|
layout: "layout%d"
|
||||||
|
---
|
||||||
|
|
||||||
|
Content.
|
||||||
|
`
|
||||||
|
|
||||||
|
singleTemplate := `
|
||||||
|
{{ define "main" }}%d{{ end }}
|
||||||
|
`
|
||||||
|
baseTemplate := `
|
||||||
|
Base %d: {{ block "main" . }}FOO{{ end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
for i := 0; i < numPages; i++ {
|
||||||
|
id := i + 1
|
||||||
|
b.WithContent(fmt.Sprintf("page%d.md", id), fmt.Sprintf(pageTemplate, id, id))
|
||||||
|
b.WithTemplates(fmt.Sprintf("_default/layout%d.html", id), fmt.Sprintf(singleTemplate, id))
|
||||||
|
b.WithTemplates(fmt.Sprintf("_default/layout%d-baseof.html", id), fmt.Sprintf(baseTemplate, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
for i := 0; i < numPages; i++ {
|
||||||
|
id := i + 1
|
||||||
|
b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", id), fmt.Sprintf(`Base %d: %d`, id, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTemplateLookupSite(t *testing.T) {
|
||||||
|
t.Run("basic", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
b := newTestSitesBuilder(t).WithSimpleConfigFile()
|
||||||
|
b.WithTemplates(
|
||||||
|
"_default/single.html", `Single: {{ .Title }}`,
|
||||||
|
"_default/list.html", `List: {{ .Title }}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
createContent := func(title string) string {
|
||||||
|
return fmt.Sprintf(`---
|
||||||
|
title: %s
|
||||||
|
---`, title)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WithContent(
|
||||||
|
"_index.md", createContent("Home Sweet Home"),
|
||||||
|
"p1.md", createContent("P1"))
|
||||||
|
|
||||||
|
b.CreateSites().Build(BuildCfg{})
|
||||||
|
b.AssertFileContent("public/index.html", `List: Home Sweet Home`)
|
||||||
|
b.AssertFileContent("public/p1/index.html", `Single: P1`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("baseof", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig()
|
||||||
|
|
||||||
|
b.WithTemplatesAdded(
|
||||||
|
"index.html", `{{ define "main" }}Main Home En{{ end }}`,
|
||||||
|
"index.fr.html", `{{ define "main" }}Main Home Fr{{ end }}`,
|
||||||
|
"baseof.html", `Baseof en: {{ block "main" . }}main block{{ end }}`,
|
||||||
|
"baseof.fr.html", `Baseof fr: {{ block "main" . }}main block{{ end }}`,
|
||||||
|
"mysection/baseof.html", `Baseof mysection: {{ block "main" . }}mysection block{{ end }}`,
|
||||||
|
"_default/single.html", `{{ define "main" }}Main Default Single{{ end }}`,
|
||||||
|
"_default/list.html", `{{ define "main" }}Main Default List{{ end }}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
b.WithContent("mysection/p1.md", `---
|
||||||
|
title: My Page
|
||||||
|
---
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.CreateSites().Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/en/index.html", `Baseof en: Main Home En`)
|
||||||
|
b.AssertFileContent("public/fr/index.html", `Baseof fr: Main Home Fr`)
|
||||||
|
b.AssertFileContent("public/en/mysection/index.html", `Baseof mysection: Main Default List`)
|
||||||
|
b.AssertFileContent("public/en/mysection/p1/index.html", `Baseof mysection: Main Default Single`)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestTemplateFuncs(t *testing.T) {
|
func TestTemplateFuncs(t *testing.T) {
|
||||||
|
|
||||||
b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig()
|
b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig()
|
||||||
|
|
|
@ -429,7 +429,7 @@ func (s *sitesBuilder) writeFilePairs(folder string, filenameContent []string) *
|
||||||
|
|
||||||
func (s *sitesBuilder) CreateSites() *sitesBuilder {
|
func (s *sitesBuilder) CreateSites() *sitesBuilder {
|
||||||
if err := s.CreateSitesE(); err != nil {
|
if err := s.CreateSitesE(); err != nil {
|
||||||
herrors.PrintStackTrace(err)
|
herrors.PrintStackTraceFromErr(err)
|
||||||
s.Fatalf("Failed to create sites: %s", err)
|
s.Fatalf("Failed to create sites: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -569,7 +569,7 @@ func (s *sitesBuilder) build(cfg BuildCfg, shouldFail bool) *sitesBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil && !shouldFail {
|
if err != nil && !shouldFail {
|
||||||
herrors.PrintStackTrace(err)
|
herrors.PrintStackTraceFromErr(err)
|
||||||
s.Fatalf("Build failed: %s", err)
|
s.Fatalf("Build failed: %s", err)
|
||||||
} else if err == nil && shouldFail {
|
} else if err == nil && shouldFail {
|
||||||
s.Fatalf("Expected error")
|
s.Fatalf("Expected error")
|
||||||
|
@ -690,6 +690,7 @@ func (s *sitesBuilder) AssertImage(width, height int, filename string) {
|
||||||
s.Assert(err, qt.IsNil)
|
s.Assert(err, qt.IsNil)
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
cfg, err := jpeg.DecodeConfig(f)
|
cfg, err := jpeg.DecodeConfig(f)
|
||||||
|
s.Assert(err, qt.IsNil)
|
||||||
s.Assert(cfg.Width, qt.Equals, width)
|
s.Assert(cfg.Width, qt.Equals, width)
|
||||||
s.Assert(cfg.Height, qt.Equals, height)
|
s.Assert(cfg.Height, qt.Equals, height)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ type LayoutDescriptor struct {
|
||||||
LayoutOverride bool
|
LayoutOverride bool
|
||||||
|
|
||||||
RenderingHook bool
|
RenderingHook bool
|
||||||
|
Baseof bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d LayoutDescriptor) isList() bool {
|
func (d LayoutDescriptor) isList() bool {
|
||||||
|
@ -76,7 +77,6 @@ func (l *LayoutHandler) For(d LayoutDescriptor, f Format) ([]string, error) {
|
||||||
|
|
||||||
layouts := resolvePageTemplate(d, f)
|
layouts := resolvePageTemplate(d, f)
|
||||||
|
|
||||||
layouts = prependTextPrefixIfNeeded(f, layouts...)
|
|
||||||
layouts = helpers.UniqueStringsReuse(layouts)
|
layouts = helpers.UniqueStringsReuse(layouts)
|
||||||
|
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
|
@ -95,7 +95,11 @@ type layoutBuilder struct {
|
||||||
|
|
||||||
func (l *layoutBuilder) addLayoutVariations(vars ...string) {
|
func (l *layoutBuilder) addLayoutVariations(vars ...string) {
|
||||||
for _, layoutVar := range vars {
|
for _, layoutVar := range vars {
|
||||||
if !l.d.RenderingHook && l.d.LayoutOverride && layoutVar != l.d.Layout {
|
if l.d.Baseof && layoutVar != "baseof" {
|
||||||
|
l.layoutVariations = append(l.layoutVariations, layoutVar+"-baseof")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !l.d.RenderingHook && !l.d.Baseof && l.d.LayoutOverride && layoutVar != l.d.Layout {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
l.layoutVariations = append(l.layoutVariations, layoutVar)
|
l.layoutVariations = append(l.layoutVariations, layoutVar)
|
||||||
|
@ -173,7 +177,7 @@ func resolvePageTemplate(d LayoutDescriptor, f Format) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
isRSS := f.Name == RSSFormat.Name
|
isRSS := f.Name == RSSFormat.Name
|
||||||
if !d.RenderingHook && isRSS {
|
if !d.RenderingHook && !d.Baseof && isRSS {
|
||||||
// The historic and common rss.xml case
|
// The historic and common rss.xml case
|
||||||
b.addLayoutVariations("")
|
b.addLayoutVariations("")
|
||||||
}
|
}
|
||||||
|
@ -186,9 +190,13 @@ func resolvePageTemplate(d LayoutDescriptor, f Format) []string {
|
||||||
b.addLayoutVariations("list")
|
b.addLayoutVariations("list")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.Baseof {
|
||||||
|
b.addLayoutVariations("baseof")
|
||||||
|
}
|
||||||
|
|
||||||
layouts := b.resolveVariations()
|
layouts := b.resolveVariations()
|
||||||
|
|
||||||
if !d.RenderingHook && isRSS {
|
if !d.RenderingHook && !d.Baseof && isRSS {
|
||||||
layouts = append(layouts, "_internal/_default/rss.xml")
|
layouts = append(layouts, "_internal/_default/rss.xml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,20 +274,6 @@ func filterDotLess(layouts []string) []string {
|
||||||
return filteredLayouts
|
return filteredLayouts
|
||||||
}
|
}
|
||||||
|
|
||||||
func prependTextPrefixIfNeeded(f Format, layouts ...string) []string {
|
|
||||||
if !f.IsPlainText {
|
|
||||||
return layouts
|
|
||||||
}
|
|
||||||
|
|
||||||
newLayouts := make([]string, len(layouts))
|
|
||||||
|
|
||||||
for i, l := range layouts {
|
|
||||||
newLayouts[i] = "_text/" + l
|
|
||||||
}
|
|
||||||
|
|
||||||
return newLayouts
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceKeyValues(s string, oldNew ...string) string {
|
func replaceKeyValues(s string, oldNew ...string) string {
|
||||||
replacer := strings.NewReplacer(oldNew...)
|
replacer := strings.NewReplacer(oldNew...)
|
||||||
return replacer.Replace(s)
|
return replacer.Replace(s)
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
// Copyright 2017-present The Hugo Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/helpers"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
baseFileBase = "baseof"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
goTemplateInnerMarkers = [][]byte{[]byte("{{define"), []byte("{{ define"), []byte("{{- define"), []byte("{{-define")}
|
|
||||||
)
|
|
||||||
|
|
||||||
// TemplateNames represents a template naming scheme.
|
|
||||||
type TemplateNames struct {
|
|
||||||
// The name used as key in the template map. Note that this will be
|
|
||||||
// prefixed with "_text/" if it should be parsed with text/template.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
OverlayFilename string
|
|
||||||
MasterFilename string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemplateLookupDescriptor describes the template lookup configuration.
|
|
||||||
type TemplateLookupDescriptor struct {
|
|
||||||
// The full path to the site root.
|
|
||||||
WorkingDir string
|
|
||||||
|
|
||||||
// The path to the template relative the the base.
|
|
||||||
// I.e. shortcodes/youtube.html
|
|
||||||
RelPath string
|
|
||||||
|
|
||||||
// The template name prefix to look for.
|
|
||||||
Prefix string
|
|
||||||
|
|
||||||
// All the output formats in play. This is used to decide if text/template or
|
|
||||||
// html/template.
|
|
||||||
OutputFormats Formats
|
|
||||||
|
|
||||||
FileExists func(filename string) (bool, error)
|
|
||||||
ContainsAny func(filename string, subslices [][]byte) (bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isShorthCodeOrPartial(name string) bool {
|
|
||||||
return strings.HasPrefix(name, "shortcodes/") || strings.HasPrefix(name, "partials/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTemplateNames returns a TemplateNames object for a given template.
|
|
||||||
func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
|
|
||||||
|
|
||||||
name := filepath.ToSlash(d.RelPath)
|
|
||||||
name = strings.TrimPrefix(name, "/")
|
|
||||||
|
|
||||||
if d.Prefix != "" {
|
|
||||||
name = strings.Trim(d.Prefix, "/") + "/" + name
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
id TemplateNames
|
|
||||||
)
|
|
||||||
|
|
||||||
// The filename will have a suffix with an optional type indicator.
|
|
||||||
// Examples:
|
|
||||||
// index.html
|
|
||||||
// index.amp.html
|
|
||||||
// index.json
|
|
||||||
filename := filepath.Base(d.RelPath)
|
|
||||||
isPlainText := false
|
|
||||||
outputFormat, found := d.OutputFormats.FromFilename(filename)
|
|
||||||
|
|
||||||
if found && outputFormat.IsPlainText {
|
|
||||||
isPlainText = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var ext, outFormat string
|
|
||||||
|
|
||||||
parts := strings.Split(filename, ".")
|
|
||||||
if len(parts) > 2 {
|
|
||||||
outFormat = parts[1]
|
|
||||||
ext = parts[2]
|
|
||||||
} else if len(parts) > 1 {
|
|
||||||
ext = parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
filenameNoSuffix := parts[0]
|
|
||||||
|
|
||||||
id.OverlayFilename = d.RelPath
|
|
||||||
id.Name = name
|
|
||||||
|
|
||||||
if isPlainText {
|
|
||||||
id.Name = "_text/" + id.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go templates may have both a base and inner template.
|
|
||||||
if isShorthCodeOrPartial(name) {
|
|
||||||
// No base template support
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pathDir := filepath.Dir(d.RelPath)
|
|
||||||
|
|
||||||
innerMarkers := goTemplateInnerMarkers
|
|
||||||
|
|
||||||
var baseFilename string
|
|
||||||
|
|
||||||
if outFormat != "" {
|
|
||||||
baseFilename = fmt.Sprintf("%s.%s.%s", baseFileBase, outFormat, ext)
|
|
||||||
} else {
|
|
||||||
baseFilename = fmt.Sprintf("%s.%s", baseFileBase, ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This may be a view that shouldn't have base template
|
|
||||||
// Have to look inside it to make sure
|
|
||||||
needsBase, err := d.ContainsAny(d.RelPath, innerMarkers)
|
|
||||||
if err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsBase {
|
|
||||||
currBaseFilename := fmt.Sprintf("%s-%s", filenameNoSuffix, baseFilename)
|
|
||||||
|
|
||||||
// Look for base template in the follwing order:
|
|
||||||
// 1. <current-path>/<template-name>-baseof.<outputFormat>(optional).<suffix>, e.g. list-baseof.<outputFormat>(optional).<suffix>.
|
|
||||||
// 2. <current-path>/baseof.<outputFormat>(optional).<suffix>
|
|
||||||
// 3. _default/<template-name>-baseof.<outputFormat>(optional).<suffix>, e.g. list-baseof.<outputFormat>(optional).<suffix>.
|
|
||||||
// 4. _default/baseof.<outputFormat>(optional).<suffix>
|
|
||||||
//
|
|
||||||
// The filesystem it looks in a a composite of the project and potential theme(s).
|
|
||||||
pathsToCheck := createPathsToCheck(pathDir, baseFilename, currBaseFilename)
|
|
||||||
|
|
||||||
// We may have language code and/or "terms" in the template name. We want the most specific,
|
|
||||||
// but need to fall back to the baseof.html if needed.
|
|
||||||
// E.g. list-baseof.en.html and list-baseof.terms.en.html
|
|
||||||
// See #3893, #3856.
|
|
||||||
baseBaseFilename, currBaseBaseFilename := helpers.Filename(baseFilename), helpers.Filename(currBaseFilename)
|
|
||||||
p1, p2 := strings.Split(baseBaseFilename, "."), strings.Split(currBaseBaseFilename, ".")
|
|
||||||
if len(p1) > 0 && len(p1) == len(p2) {
|
|
||||||
for i := len(p1); i > 0; i-- {
|
|
||||||
v1, v2 := strings.Join(p1[:i], ".")+"."+ext, strings.Join(p2[:i], ".")+"."+ext
|
|
||||||
pathsToCheck = append(pathsToCheck, createPathsToCheck(pathDir, v1, v2)...)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range pathsToCheck {
|
|
||||||
if ok, err := d.FileExists(p); err == nil && ok {
|
|
||||||
id.MasterFilename = p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPathsToCheck(baseTemplatedDir, baseFilename, currBaseFilename string) []string {
|
|
||||||
return []string{
|
|
||||||
filepath.Join(baseTemplatedDir, currBaseFilename),
|
|
||||||
filepath.Join(baseTemplatedDir, baseFilename),
|
|
||||||
filepath.Join("_default", currBaseFilename),
|
|
||||||
filepath.Join("_default", baseFilename),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
// Copyright 2017-present The Hugo Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLayoutBase(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
|
|
||||||
var (
|
|
||||||
workingDir = "/sites/mysite/"
|
|
||||||
layoutPath1 = "_default/single.html"
|
|
||||||
layoutPathAmp = "_default/single.amp.html"
|
|
||||||
layoutPathJSON = "_default/single.json"
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, this := range []struct {
|
|
||||||
name string
|
|
||||||
d TemplateLookupDescriptor
|
|
||||||
needsBase bool
|
|
||||||
basePathMatchStrings string
|
|
||||||
expect TemplateNames
|
|
||||||
}{
|
|
||||||
{"No base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPath1}, false, "",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/single.html",
|
|
||||||
OverlayFilename: "_default/single.html",
|
|
||||||
}},
|
|
||||||
{"Base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPath1}, true, "",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/single.html",
|
|
||||||
OverlayFilename: "_default/single.html",
|
|
||||||
MasterFilename: "_default/single-baseof.html",
|
|
||||||
}},
|
|
||||||
// Issue #3893
|
|
||||||
{"Base Lang, Default Base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "_default/list.en.html"}, true, "_default/baseof.html",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/list.en.html",
|
|
||||||
OverlayFilename: "_default/list.en.html",
|
|
||||||
MasterFilename: "_default/baseof.html",
|
|
||||||
}},
|
|
||||||
{"Base Lang, Lang Base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "_default/list.en.html"}, true, "_default/baseof.html|_default/baseof.en.html",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/list.en.html",
|
|
||||||
OverlayFilename: "_default/list.en.html",
|
|
||||||
MasterFilename: "_default/baseof.en.html",
|
|
||||||
}},
|
|
||||||
// Issue #3856
|
|
||||||
{"Base Taxonomy Term", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "taxonomy/tag.terms.html"}, true, "_default/baseof.html",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "taxonomy/tag.terms.html",
|
|
||||||
OverlayFilename: "taxonomy/tag.terms.html",
|
|
||||||
MasterFilename: "_default/baseof.html",
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"Partial", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "partials/menu.html"}, true,
|
|
||||||
"mytheme/layouts/_default/baseof.html",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "partials/menu.html",
|
|
||||||
OverlayFilename: "partials/menu.html",
|
|
||||||
}},
|
|
||||||
{"Partial in subfolder", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "/partials/sub/menu.html"}, true,
|
|
||||||
"_default/baseof.html",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "partials/sub/menu.html",
|
|
||||||
OverlayFilename: "/partials/sub/menu.html",
|
|
||||||
}},
|
|
||||||
{"Shortcode in subfolder", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "shortcodes/sub/menu.html"}, true,
|
|
||||||
"_default/baseof.html",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "shortcodes/sub/menu.html",
|
|
||||||
OverlayFilename: "shortcodes/sub/menu.html",
|
|
||||||
}},
|
|
||||||
{"AMP, no base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathAmp}, false, "",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/single.amp.html",
|
|
||||||
OverlayFilename: "_default/single.amp.html",
|
|
||||||
}},
|
|
||||||
{"JSON, no base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathJSON}, false, "",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/single.json",
|
|
||||||
OverlayFilename: "_default/single.json",
|
|
||||||
}},
|
|
||||||
{"AMP with base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathAmp}, true, "single-baseof.html|single-baseof.amp.html",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/single.amp.html",
|
|
||||||
OverlayFilename: "_default/single.amp.html",
|
|
||||||
MasterFilename: "_default/single-baseof.amp.html",
|
|
||||||
}},
|
|
||||||
{"AMP with no AMP base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathAmp}, true, "single-baseof.html",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/single.amp.html",
|
|
||||||
OverlayFilename: "_default/single.amp.html",
|
|
||||||
MasterFilename: "_default/single-baseof.html",
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"JSON with base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathJSON}, true, "single-baseof.json",
|
|
||||||
TemplateNames{
|
|
||||||
Name: "_default/single.json",
|
|
||||||
OverlayFilename: "_default/single.json",
|
|
||||||
MasterFilename: "_default/single-baseof.json",
|
|
||||||
}},
|
|
||||||
} {
|
|
||||||
c.Run(this.name, func(c *qt.C) {
|
|
||||||
|
|
||||||
this.basePathMatchStrings = filepath.FromSlash(this.basePathMatchStrings)
|
|
||||||
|
|
||||||
fileExists := func(filename string) (bool, error) {
|
|
||||||
stringsToMatch := strings.Split(this.basePathMatchStrings, "|")
|
|
||||||
for _, s := range stringsToMatch {
|
|
||||||
if strings.Contains(filename, s) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
needsBase := func(filename string, subslices [][]byte) (bool, error) {
|
|
||||||
return this.needsBase, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
this.d.OutputFormats = Formats{AMPFormat, HTMLFormat, RSSFormat, JSONFormat}
|
|
||||||
this.d.WorkingDir = filepath.FromSlash(this.d.WorkingDir)
|
|
||||||
this.d.RelPath = filepath.FromSlash(this.d.RelPath)
|
|
||||||
this.d.ContainsAny = needsBase
|
|
||||||
this.d.FileExists = fileExists
|
|
||||||
|
|
||||||
this.expect.MasterFilename = filepath.FromSlash(this.expect.MasterFilename)
|
|
||||||
this.expect.OverlayFilename = filepath.FromSlash(this.expect.OverlayFilename)
|
|
||||||
|
|
||||||
if strings.Contains(this.d.RelPath, "json") {
|
|
||||||
// currently the only plain text templates in this test.
|
|
||||||
this.expect.Name = "_text/" + this.expect.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := CreateTemplateNames(this.d)
|
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
msg := qt.Commentf(this.name)
|
|
||||||
c.Assert(id, qt.Equals, this.expect, msg)
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -66,9 +66,13 @@ func TestLayout(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{"Home", LayoutDescriptor{Kind: "home"}, "", ampType,
|
{"Home", LayoutDescriptor{Kind: "home"}, "", ampType,
|
||||||
[]string{"index.amp.html", "home.amp.html", "list.amp.html", "index.html", "home.html", "list.html", "_default/index.amp.html"}, 12},
|
[]string{"index.amp.html", "home.amp.html", "list.amp.html", "index.html", "home.html", "list.html", "_default/index.amp.html"}, 12},
|
||||||
|
{"Home baseof", LayoutDescriptor{Kind: "home", Baseof: true}, "", ampType,
|
||||||
|
[]string{"index-baseof.amp.html", "home-baseof.amp.html", "list-baseof.amp.html", "baseof.amp.html", "index-baseof.html"}, 16},
|
||||||
{"Home, HTML", LayoutDescriptor{Kind: "home"}, "", htmlFormat,
|
{"Home, HTML", LayoutDescriptor{Kind: "home"}, "", htmlFormat,
|
||||||
// We will eventually get to index.html. This looks stuttery, but makes the lookup logic easy to understand.
|
// We will eventually get to index.html. This looks stuttery, but makes the lookup logic easy to understand.
|
||||||
[]string{"index.html.html", "home.html.html"}, 12},
|
[]string{"index.html.html", "home.html.html"}, 12},
|
||||||
|
{"Home, HTML, baseof", LayoutDescriptor{Kind: "home", Baseof: true}, "", htmlFormat,
|
||||||
|
[]string{"index-baseof.html.html", "home-baseof.html.html", "list-baseof.html.html", "baseof.html.html"}, 16},
|
||||||
{"Home, french language", LayoutDescriptor{Kind: "home", Lang: "fr"}, "", ampType,
|
{"Home, french language", LayoutDescriptor{Kind: "home", Lang: "fr"}, "", ampType,
|
||||||
[]string{"index.fr.amp.html"},
|
[]string{"index.fr.amp.html"},
|
||||||
24},
|
24},
|
||||||
|
@ -80,6 +84,8 @@ func TestLayout(t *testing.T) {
|
||||||
[]string{"_default/single.nem"}, 1},
|
[]string{"_default/single.nem"}, 1},
|
||||||
{"Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, "", ampType,
|
{"Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, "", ampType,
|
||||||
[]string{"sect1/sect1.amp.html", "sect1/section.amp.html", "sect1/list.amp.html", "sect1/sect1.html", "sect1/section.html", "sect1/list.html", "section/sect1.amp.html", "section/section.amp.html"}, 18},
|
[]string{"sect1/sect1.amp.html", "sect1/section.amp.html", "sect1/list.amp.html", "sect1/sect1.html", "sect1/section.html", "sect1/list.html", "section/sect1.amp.html", "section/section.amp.html"}, 18},
|
||||||
|
{"Section, baseof", LayoutDescriptor{Kind: "section", Section: "sect1", Baseof: true}, "", ampType,
|
||||||
|
[]string{"sect1/sect1-baseof.amp.html", "sect1/section-baseof.amp.html", "sect1/list-baseof.amp.html", "sect1/baseof.amp.html", "sect1/sect1-baseof.html", "sect1/section-baseof.html", "sect1/list-baseof.html", "sect1/baseof.html"}, 24},
|
||||||
{"Section with layout", LayoutDescriptor{Kind: "section", Section: "sect1", Layout: "mylayout"}, "", ampType,
|
{"Section with layout", LayoutDescriptor{Kind: "section", Section: "sect1", Layout: "mylayout"}, "", ampType,
|
||||||
[]string{"sect1/mylayout.amp.html", "sect1/sect1.amp.html", "sect1/section.amp.html", "sect1/list.amp.html", "sect1/mylayout.html", "sect1/sect1.html"}, 24},
|
[]string{"sect1/mylayout.amp.html", "sect1/sect1.amp.html", "sect1/section.amp.html", "sect1/list.amp.html", "sect1/mylayout.html", "sect1/sect1.html"}, 24},
|
||||||
{"Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, "", ampType,
|
{"Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, "", ampType,
|
||||||
|
@ -88,8 +94,12 @@ func TestLayout(t *testing.T) {
|
||||||
[]string{"taxonomy/categories.terms.amp.html", "taxonomy/terms.amp.html", "taxonomy/list.amp.html", "taxonomy/categories.terms.html", "taxonomy/terms.html"}, 18},
|
[]string{"taxonomy/categories.terms.amp.html", "taxonomy/terms.amp.html", "taxonomy/list.amp.html", "taxonomy/categories.terms.html", "taxonomy/terms.html"}, 18},
|
||||||
{"Page", LayoutDescriptor{Kind: "page"}, "", ampType,
|
{"Page", LayoutDescriptor{Kind: "page"}, "", ampType,
|
||||||
[]string{"_default/single.amp.html", "_default/single.html"}, 2},
|
[]string{"_default/single.amp.html", "_default/single.html"}, 2},
|
||||||
|
{"Page, baseof", LayoutDescriptor{Kind: "page", Baseof: true}, "", ampType,
|
||||||
|
[]string{"_default/single-baseof.amp.html", "_default/baseof.amp.html", "_default/single-baseof.html", "_default/baseof.html"}, 4},
|
||||||
{"Page with layout", LayoutDescriptor{Kind: "page", Layout: "mylayout"}, "", ampType,
|
{"Page with layout", LayoutDescriptor{Kind: "page", Layout: "mylayout"}, "", ampType,
|
||||||
[]string{"_default/mylayout.amp.html", "_default/single.amp.html", "_default/mylayout.html", "_default/single.html"}, 4},
|
[]string{"_default/mylayout.amp.html", "_default/single.amp.html", "_default/mylayout.html", "_default/single.html"}, 4},
|
||||||
|
{"Page with layout, baseof", LayoutDescriptor{Kind: "page", Layout: "mylayout", Baseof: true}, "", ampType,
|
||||||
|
[]string{"_default/mylayout-baseof.amp.html", "_default/single-baseof.amp.html", "_default/baseof.amp.html", "_default/mylayout-baseof.html", "_default/single-baseof.html", "_default/baseof.html"}, 6},
|
||||||
{"Page with layout and type", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype"}, "", ampType,
|
{"Page with layout and type", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype"}, "", ampType,
|
||||||
[]string{"myttype/mylayout.amp.html", "myttype/single.amp.html", "myttype/mylayout.html"}, 8},
|
[]string{"myttype/mylayout.amp.html", "myttype/single.amp.html", "myttype/mylayout.html"}, 8},
|
||||||
{"Page with layout and type with subtype", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype/mysubtype"}, "", ampType,
|
{"Page with layout and type with subtype", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype/mysubtype"}, "", ampType,
|
||||||
|
@ -97,6 +107,8 @@ func TestLayout(t *testing.T) {
|
||||||
// RSS
|
// RSS
|
||||||
{"RSS Home", LayoutDescriptor{Kind: "home"}, "", RSSFormat,
|
{"RSS Home", LayoutDescriptor{Kind: "home"}, "", RSSFormat,
|
||||||
[]string{"index.rss.xml", "home.rss.xml", "rss.xml"}, 15},
|
[]string{"index.rss.xml", "home.rss.xml", "rss.xml"}, 15},
|
||||||
|
{"RSS Home, baseof", LayoutDescriptor{Kind: "home", Baseof: true}, "", RSSFormat,
|
||||||
|
[]string{"index-baseof.rss.xml", "home-baseof.rss.xml", "list-baseof.rss.xml", "baseof.rss.xml"}, 16},
|
||||||
{"RSS Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, "", RSSFormat,
|
{"RSS Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, "", RSSFormat,
|
||||||
[]string{"sect1/sect1.rss.xml", "sect1/section.rss.xml", "sect1/rss.xml", "sect1/list.rss.xml", "sect1/sect1.xml", "sect1/section.xml"}, 22},
|
[]string{"sect1/sect1.rss.xml", "sect1/section.rss.xml", "sect1/rss.xml", "sect1/list.rss.xml", "sect1/sect1.xml", "sect1/section.xml"}, 22},
|
||||||
{"RSS Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, "", RSSFormat,
|
{"RSS Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, "", RSSFormat,
|
||||||
|
@ -104,13 +116,14 @@ func TestLayout(t *testing.T) {
|
||||||
{"RSS Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, "", RSSFormat,
|
{"RSS Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, "", RSSFormat,
|
||||||
[]string{"taxonomy/tag.terms.rss.xml", "taxonomy/terms.rss.xml", "taxonomy/rss.xml", "taxonomy/list.rss.xml", "taxonomy/tag.terms.xml"}, 22},
|
[]string{"taxonomy/tag.terms.rss.xml", "taxonomy/terms.rss.xml", "taxonomy/rss.xml", "taxonomy/list.rss.xml", "taxonomy/tag.terms.xml"}, 22},
|
||||||
{"Home plain text", LayoutDescriptor{Kind: "home"}, "", JSONFormat,
|
{"Home plain text", LayoutDescriptor{Kind: "home"}, "", JSONFormat,
|
||||||
[]string{"_text/index.json.json", "_text/home.json.json"}, 12},
|
[]string{"index.json.json", "home.json.json"}, 12},
|
||||||
{"Page plain text", LayoutDescriptor{Kind: "page"}, "", JSONFormat,
|
{"Page plain text", LayoutDescriptor{Kind: "page"}, "", JSONFormat,
|
||||||
[]string{"_text/_default/single.json.json", "_text/_default/single.json"}, 2},
|
[]string{"_default/single.json.json", "_default/single.json"}, 2},
|
||||||
{"Reserved section, shortcodes", LayoutDescriptor{Kind: "section", Section: "shortcodes", Type: "shortcodes"}, "", ampType,
|
{"Reserved section, shortcodes", LayoutDescriptor{Kind: "section", Section: "shortcodes", Type: "shortcodes"}, "", ampType,
|
||||||
[]string{"section/shortcodes.amp.html"}, 12},
|
[]string{"section/shortcodes.amp.html"}, 12},
|
||||||
{"Reserved section, partials", LayoutDescriptor{Kind: "section", Section: "partials", Type: "partials"}, "", ampType,
|
{"Reserved section, partials", LayoutDescriptor{Kind: "section", Section: "partials", Type: "partials"}, "", ampType,
|
||||||
[]string{"section/partials.amp.html"}, 12},
|
[]string{"section/partials.amp.html"}, 12},
|
||||||
|
|
||||||
// We may add type support ... later.
|
// We may add type support ... later.
|
||||||
{"Content hook", LayoutDescriptor{Kind: "render-link", RenderingHook: true, Layout: "mylayout", Section: "blog"}, "", ampType,
|
{"Content hook", LayoutDescriptor{Kind: "render-link", RenderingHook: true, Layout: "mylayout", Section: "blog"}, "", ampType,
|
||||||
[]string{"_default/_markup/render-link.amp.html", "_default/_markup/render-link.html"}, 2},
|
[]string{"_default/_markup/render-link.amp.html", "_default/_markup/render-link.html"}, 2},
|
||||||
|
@ -122,7 +135,7 @@ func TestLayout(t *testing.T) {
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
c.Assert(layouts, qt.Not(qt.IsNil))
|
c.Assert(layouts, qt.Not(qt.IsNil))
|
||||||
c.Assert(len(layouts) >= len(this.expect), qt.Equals, true)
|
c.Assert(len(layouts) >= len(this.expect), qt.Equals, true, qt.Commentf("%d vs %d", len(layouts), len(this.expect)))
|
||||||
// Not checking the complete list for now ...
|
// Not checking the complete list for now ...
|
||||||
got := layouts[:len(this.expect)]
|
got := layouts[:len(this.expect)]
|
||||||
if len(layouts) != this.expectCount || !reflect.DeepEqual(got, this.expect) {
|
if len(layouts) != this.expectCount || !reflect.DeepEqual(got, this.expect) {
|
||||||
|
@ -130,7 +143,7 @@ func TestLayout(t *testing.T) {
|
||||||
formatted = strings.Replace(formatted, "]", "\"", 1)
|
formatted = strings.Replace(formatted, "]", "\"", 1)
|
||||||
formatted = strings.Replace(formatted, " ", "\", \"", -1)
|
formatted = strings.Replace(formatted, " ", "\", \"", -1)
|
||||||
|
|
||||||
t.Fatalf("Got %d/%d:\n%v\nExpected:\n%v\nAll:\n%v\nFormatted:\n%s", len(layouts), this.expectCount, got, this.expect, layouts, formatted)
|
c.Fatalf("Got %d/%d:\n%v\nExpected:\n%v\nAll:\n%v\nFormatted:\n%s", len(layouts), this.expectCount, got, this.expect, layouts, formatted)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,26 +26,23 @@ import (
|
||||||
// Client contains methods to perform template processing of Resource objects.
|
// Client contains methods to perform template processing of Resource objects.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
rs *resources.Spec
|
rs *resources.Spec
|
||||||
|
t tpl.TemplatesProvider
|
||||||
templateHandler tpl.TemplateHandler
|
|
||||||
textTemplate tpl.TemplateParseFinder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Client with the given specification.
|
// New creates a new Client with the given specification.
|
||||||
func New(rs *resources.Spec, h tpl.TemplateHandler, textTemplate tpl.TemplateParseFinder) *Client {
|
func New(rs *resources.Spec, t tpl.TemplatesProvider) *Client {
|
||||||
if rs == nil {
|
if rs == nil {
|
||||||
panic("must provice a resource Spec")
|
panic("must provice a resource Spec")
|
||||||
}
|
}
|
||||||
if textTemplate == nil {
|
if t == nil {
|
||||||
panic("must provide a textTemplate")
|
panic("must provide a template provider")
|
||||||
}
|
}
|
||||||
return &Client{rs: rs, templateHandler: h, textTemplate: textTemplate}
|
return &Client{rs: rs, t: t}
|
||||||
}
|
}
|
||||||
|
|
||||||
type executeAsTemplateTransform struct {
|
type executeAsTemplateTransform struct {
|
||||||
rs *resources.Spec
|
rs *resources.Spec
|
||||||
textTemplate tpl.TemplateParseFinder
|
t tpl.TemplatesProvider
|
||||||
templateHandler tpl.TemplateHandler
|
|
||||||
targetPath string
|
targetPath string
|
||||||
data interface{}
|
data interface{}
|
||||||
}
|
}
|
||||||
|
@ -56,22 +53,21 @@ func (t *executeAsTemplateTransform) Key() internal.ResourceTransformationKey {
|
||||||
|
|
||||||
func (t *executeAsTemplateTransform) Transform(ctx *resources.ResourceTransformationCtx) error {
|
func (t *executeAsTemplateTransform) Transform(ctx *resources.ResourceTransformationCtx) error {
|
||||||
tplStr := helpers.ReaderToString(ctx.From)
|
tplStr := helpers.ReaderToString(ctx.From)
|
||||||
templ, err := t.textTemplate.Parse(ctx.InPath, tplStr)
|
templ, err := t.t.TextTmpl().Parse(ctx.InPath, tplStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to parse Resource %q as Template:", ctx.InPath)
|
return errors.Wrapf(err, "failed to parse Resource %q as Template:", ctx.InPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.OutPath = t.targetPath
|
ctx.OutPath = t.targetPath
|
||||||
|
|
||||||
return t.templateHandler.Execute(templ, ctx.To, t.data)
|
return t.t.Tmpl().Execute(templ, ctx.To, t.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ExecuteAsTemplate(res resources.ResourceTransformer, targetPath string, data interface{}) (resource.Resource, error) {
|
func (c *Client) ExecuteAsTemplate(res resources.ResourceTransformer, targetPath string, data interface{}) (resource.Resource, error) {
|
||||||
return res.Transform(&executeAsTemplateTransform{
|
return res.Transform(&executeAsTemplateTransform{
|
||||||
rs: c.rs,
|
rs: c.rs,
|
||||||
targetPath: helpers.ToSlashTrimLeading(targetPath),
|
targetPath: helpers.ToSlashTrimLeading(targetPath),
|
||||||
templateHandler: c.templateHandler,
|
t: c.t,
|
||||||
textTemplate: c.textTemplate,
|
|
||||||
data: data,
|
data: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value,
|
||||||
|
|
||||||
func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
|
func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
|
||||||
if !strings.ContainsRune(fname, '.') {
|
if !strings.ContainsRune(fname, '.') {
|
||||||
templ := ns.deps.Tmpl.(tpl.TemplateFuncGetter)
|
templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter)
|
||||||
return templ.GetFunc(fname)
|
return templ.GetFunc(fname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
|
"github.com/gohugoio/hugo/output"
|
||||||
"github.com/gohugoio/hugo/tpl"
|
"github.com/gohugoio/hugo/tpl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,10 +32,18 @@ func (templateFinder) Lookup(name string) (tpl.Template, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (templateFinder) HasTemplate(name string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (templateFinder) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
|
func (templateFinder) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
|
||||||
return nil, false, false
|
return nil, false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (templateFinder) LookupLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (templateFinder) Execute(t tpl.Template, wr io.Writer, data interface{}) error {
|
func (templateFinder) Execute(t tpl.Template, wr io.Writer, data interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -51,8 +60,9 @@ func (templateFinder) GetFunc(name string) (reflect.Value, bool) {
|
||||||
func TestApply(t *testing.T) {
|
func TestApply(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
d := &deps.Deps{}
|
||||||
ns := New(&deps.Deps{Tmpl: new(templateFinder)})
|
d.SetTmpl(new(templateFinder))
|
||||||
|
ns := New(d)
|
||||||
|
|
||||||
strings := []interface{}{"a\n", "b\n"}
|
strings := []interface{}{"a\n", "b\n"}
|
||||||
|
|
||||||
|
|
|
@ -105,11 +105,11 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
|
||||||
}
|
}
|
||||||
|
|
||||||
n := "partials/" + name
|
n := "partials/" + name
|
||||||
templ, found := ns.deps.Tmpl.Lookup(n)
|
templ, found := ns.deps.Tmpl().Lookup(n)
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
// For legacy reasons.
|
// For legacy reasons.
|
||||||
templ, found = ns.deps.Tmpl.Lookup(n + ".html")
|
templ, found = ns.deps.Tmpl().Lookup(n + ".html")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
|
@ -139,7 +139,7 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
|
||||||
w = b
|
w = b
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ns.deps.Tmpl.Execute(templ, w, context); err != nil {
|
if err := ns.deps.Tmpl().Execute(templ, w, context); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ func New(deps *deps.Deps) (*Namespace, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Namespace{
|
return &Namespace{
|
||||||
deps: deps,
|
deps: deps,
|
||||||
scssClient: scssClient,
|
scssClient: scssClient,
|
||||||
|
@ -53,7 +54,7 @@ func New(deps *deps.Deps) (*Namespace, error) {
|
||||||
integrityClient: integrity.New(deps.ResourceSpec),
|
integrityClient: integrity.New(deps.ResourceSpec),
|
||||||
minifyClient: minifier.New(deps.ResourceSpec),
|
minifyClient: minifier.New(deps.ResourceSpec),
|
||||||
postcssClient: postcss.New(deps.ResourceSpec),
|
postcssClient: postcss.New(deps.ResourceSpec),
|
||||||
templatesClient: templates.New(deps.ResourceSpec, deps.Tmpl, deps.TextTmpl),
|
templatesClient: templates.New(deps.ResourceSpec, deps),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,7 @@ type TemplateManager interface {
|
||||||
TemplateFuncGetter
|
TemplateFuncGetter
|
||||||
AddTemplate(name, tpl string) error
|
AddTemplate(name, tpl string) error
|
||||||
AddLateTemplate(name, tpl string) error
|
AddLateTemplate(name, tpl string) error
|
||||||
LoadTemplates(prefix string) error
|
MarkReady() error
|
||||||
|
|
||||||
RebuildClone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateVariants describes the possible variants of a template.
|
// TemplateVariants describes the possible variants of a template.
|
||||||
|
@ -52,6 +50,8 @@ type TemplateFinder interface {
|
||||||
type TemplateHandler interface {
|
type TemplateHandler interface {
|
||||||
TemplateFinder
|
TemplateFinder
|
||||||
Execute(t Template, wr io.Writer, data interface{}) error
|
Execute(t Template, wr io.Writer, data interface{}) error
|
||||||
|
LookupLayout(d output.LayoutDescriptor, f output.Format) (Template, bool, error)
|
||||||
|
HasTemplate(name string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type TemplateLookup interface {
|
type TemplateLookup interface {
|
||||||
|
@ -105,6 +105,12 @@ type templateInfoManager struct {
|
||||||
InfoManager
|
InfoManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TemplatesProvider as implemented by deps.Deps.
|
||||||
|
type TemplatesProvider interface {
|
||||||
|
Tmpl() TemplateHandler
|
||||||
|
TextTmpl() TemplateParseFinder
|
||||||
|
}
|
||||||
|
|
||||||
// WithInfo wraps the info in a template.
|
// WithInfo wraps the info in a template.
|
||||||
func WithInfo(templ Template, info Info) Template {
|
func WithInfo(templ Template, info Info) Template {
|
||||||
if manager, ok := info.(InfoManager); ok {
|
if manager, ok := info.(InfoManager); ok {
|
||||||
|
|
|
@ -34,7 +34,7 @@ type Namespace struct {
|
||||||
// Note that this is the Unix-styled relative path including filename suffix,
|
// Note that this is the Unix-styled relative path including filename suffix,
|
||||||
// e.g. partials/header.html
|
// e.g. partials/header.html
|
||||||
func (ns *Namespace) Exists(name string) bool {
|
func (ns *Namespace) Exists(name string) bool {
|
||||||
_, found := ns.deps.Tmpl.Lookup(name)
|
_, found := ns.deps.Tmpl().Lookup(name)
|
||||||
return found
|
return found
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,8 +32,7 @@ type shortcodeVariant struct {
|
||||||
// A slice of length numTemplateVariants.
|
// A slice of length numTemplateVariants.
|
||||||
variants []string
|
variants []string
|
||||||
|
|
||||||
info tpl.Info
|
ts *templateState
|
||||||
templ tpl.Template
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type shortcodeTemplates struct {
|
type shortcodeTemplates struct {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,32 +25,17 @@ var DefaultTemplateProvider *TemplateProvider
|
||||||
|
|
||||||
// Update updates the Hugo Template System in the provided Deps
|
// Update updates the Hugo Template System in the provided Deps
|
||||||
// with all the additional features, templates & functions.
|
// with all the additional features, templates & functions.
|
||||||
func (*TemplateProvider) Update(deps *deps.Deps) error {
|
func (*TemplateProvider) Update(d *deps.Deps) error {
|
||||||
newTmpl := newTemplateAdapter(deps)
|
tmpl, err := newTemplateExec(d)
|
||||||
deps.Tmpl = newTmpl
|
|
||||||
deps.TextTmpl = newTmpl.wrapTextTemplate(newTmpl.text.standalone)
|
|
||||||
// These needs to be there at parse time.
|
|
||||||
newTmpl.initTemplateExecuter()
|
|
||||||
|
|
||||||
if err := newTmpl.loadEmbedded(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if deps.WithTemplate != nil {
|
|
||||||
err := deps.WithTemplate(newTmpl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return tmpl.postTransform()
|
||||||
}
|
|
||||||
|
|
||||||
return newTmpl.markReady()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone clones.
|
// Clone clones.
|
||||||
func (*TemplateProvider) Clone(d *deps.Deps) error {
|
func (*TemplateProvider) Clone(d *deps.Deps) error {
|
||||||
t := d.Tmpl.(*templateHandler)
|
t := d.Tmpl().(*templateExec)
|
||||||
t.clone(d)
|
d.SetTmpl(t.Clone(d))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,9 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/identity"
|
htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
||||||
|
|
||||||
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
|
||||||
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
|
@ -41,25 +40,21 @@ type templateContext struct {
|
||||||
visited map[string]bool
|
visited map[string]bool
|
||||||
templateNotFound map[string]bool
|
templateNotFound map[string]bool
|
||||||
identityNotFound map[string]bool
|
identityNotFound map[string]bool
|
||||||
lookupFn func(name string) *templateInfoTree
|
lookupFn func(name string) *templateState
|
||||||
|
|
||||||
// The last error encountered.
|
// The last error encountered.
|
||||||
err error
|
err error
|
||||||
|
|
||||||
typ templateType
|
|
||||||
|
|
||||||
// Set when we're done checking for config header.
|
// Set when we're done checking for config header.
|
||||||
configChecked bool
|
configChecked bool
|
||||||
|
|
||||||
// Contains some info about the template
|
t *templateState
|
||||||
parseInfo *tpl.ParseInfo
|
|
||||||
id identity.Manager
|
|
||||||
|
|
||||||
// Store away the return node in partials.
|
// Store away the return node in partials.
|
||||||
returnNode *parse.CommandNode
|
returnNode *parse.CommandNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
|
func (c templateContext) getIfNotVisited(name string) *templateState {
|
||||||
if c.visited[name] {
|
if c.visited[name] {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -76,13 +71,11 @@ func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTemplateContext(
|
func newTemplateContext(
|
||||||
id identity.Manager,
|
t *templateState,
|
||||||
info *tpl.ParseInfo,
|
lookupFn func(name string) *templateState) *templateContext {
|
||||||
lookupFn func(name string) *templateInfoTree) *templateContext {
|
|
||||||
|
|
||||||
return &templateContext{
|
return &templateContext{
|
||||||
id: id,
|
t: t,
|
||||||
parseInfo: info,
|
|
||||||
lookupFn: lookupFn,
|
lookupFn: lookupFn,
|
||||||
visited: make(map[string]bool),
|
visited: make(map[string]bool),
|
||||||
templateNotFound: make(map[string]bool),
|
templateNotFound: make(map[string]bool),
|
||||||
|
@ -90,79 +83,36 @@ func newTemplateContext(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createGetTemplateInfoTreeFor(getID func(name string) *templateInfoTree) func(nn string) *templateInfoTree {
|
|
||||||
return func(nn string) *templateInfoTree {
|
|
||||||
return getID(nn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *templateHandler) applyTemplateTransformersToHMLTTemplate(typ templateType, templ *template.Template) (*templateContext, error) {
|
|
||||||
id, info := t.createTemplateInfo(templ.Name())
|
|
||||||
ti := &templateInfoTree{
|
|
||||||
tree: templ.Tree,
|
|
||||||
templ: templ,
|
|
||||||
typ: typ,
|
|
||||||
id: id,
|
|
||||||
info: info,
|
|
||||||
}
|
|
||||||
t.templateInfoTree[templ.Name()] = ti
|
|
||||||
getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
|
|
||||||
return t.templateInfoTree[name]
|
|
||||||
})
|
|
||||||
|
|
||||||
return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *templateHandler) applyTemplateTransformersToTextTemplate(typ templateType, templ *texttemplate.Template) (*templateContext, error) {
|
|
||||||
id, info := t.createTemplateInfo(templ.Name())
|
|
||||||
ti := &templateInfoTree{
|
|
||||||
tree: templ.Tree,
|
|
||||||
templ: templ,
|
|
||||||
typ: typ,
|
|
||||||
id: id,
|
|
||||||
info: info,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.templateInfoTree[templ.Name()] = ti
|
|
||||||
getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
|
|
||||||
return t.templateInfoTree[name]
|
|
||||||
})
|
|
||||||
|
|
||||||
return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type templateInfoTree struct {
|
|
||||||
info tpl.ParseInfo
|
|
||||||
typ templateType
|
|
||||||
id identity.Manager
|
|
||||||
templ tpl.Template
|
|
||||||
tree *parse.Tree
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyTemplateTransformers(
|
func applyTemplateTransformers(
|
||||||
typ templateType,
|
t *templateState,
|
||||||
templ *templateInfoTree,
|
lookupFn func(name string) *templateState) (*templateContext, error) {
|
||||||
lookupFn func(name string) *templateInfoTree) (*templateContext, error) {
|
|
||||||
|
|
||||||
if templ == nil {
|
if t == nil {
|
||||||
return nil, errors.New("expected template, but none provided")
|
return nil, errors.New("expected template, but none provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
c := newTemplateContext(templ.id, &templ.info, lookupFn)
|
c := newTemplateContext(t, lookupFn)
|
||||||
c.typ = typ
|
tree := getParseTree(t.Template)
|
||||||
|
|
||||||
_, err := c.applyTransformations(templ.tree.Root)
|
_, err := c.applyTransformations(tree.Root)
|
||||||
|
|
||||||
if err == nil && c.returnNode != nil {
|
if err == nil && c.returnNode != nil {
|
||||||
// This is a partial with a return statement.
|
// This is a partial with a return statement.
|
||||||
c.parseInfo.HasReturn = true
|
c.t.parseInfo.HasReturn = true
|
||||||
templ.tree.Root = c.wrapInPartialReturnWrapper(templ.tree.Root)
|
tree.Root = c.wrapInPartialReturnWrapper(tree.Root)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getParseTree(templ tpl.Template) *parse.Tree {
|
||||||
|
templ = unwrap(templ)
|
||||||
|
if text, ok := templ.(*texttemplate.Template); ok {
|
||||||
|
return text.Tree
|
||||||
|
}
|
||||||
|
return templ.(*htmltemplate.Template).Tree
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
|
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
|
||||||
)
|
)
|
||||||
|
@ -215,7 +165,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
|
||||||
case *parse.TemplateNode:
|
case *parse.TemplateNode:
|
||||||
subTempl := c.getIfNotVisited(x.Name)
|
subTempl := c.getIfNotVisited(x.Name)
|
||||||
if subTempl != nil {
|
if subTempl != nil {
|
||||||
c.applyTransformationsToNodes(subTempl.tree.Root)
|
c.applyTransformationsToNodes(getParseTree(subTempl.Template).Root)
|
||||||
}
|
}
|
||||||
case *parse.PipeNode:
|
case *parse.PipeNode:
|
||||||
c.collectConfig(x)
|
c.collectConfig(x)
|
||||||
|
@ -263,7 +213,7 @@ func (c *templateContext) hasIdent(idents []string, ident string) bool {
|
||||||
// on the form:
|
// on the form:
|
||||||
// {{ $_hugo_config:= `{ "version": 1 }` }}
|
// {{ $_hugo_config:= `{ "version": 1 }` }}
|
||||||
func (c *templateContext) collectConfig(n *parse.PipeNode) {
|
func (c *templateContext) collectConfig(n *parse.PipeNode) {
|
||||||
if c.typ != templateShortcode {
|
if c.t.typ != templateShortcode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.configChecked {
|
if c.configChecked {
|
||||||
|
@ -295,7 +245,7 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
|
||||||
c.err = errors.Wrap(err, errMsg)
|
c.err = errors.Wrap(err, errMsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := mapstructure.WeakDecode(m, &c.parseInfo.Config); err != nil {
|
if err := mapstructure.WeakDecode(m, &c.t.parseInfo.Config); err != nil {
|
||||||
c.err = errors.Wrap(err, errMsg)
|
c.err = errors.Wrap(err, errMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,10 +254,10 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
|
||||||
// collectInner determines if the given CommandNode represents a
|
// collectInner determines if the given CommandNode represents a
|
||||||
// shortcode call to its .Inner.
|
// shortcode call to its .Inner.
|
||||||
func (c *templateContext) collectInner(n *parse.CommandNode) {
|
func (c *templateContext) collectInner(n *parse.CommandNode) {
|
||||||
if c.typ != templateShortcode {
|
if c.t.typ != templateShortcode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.parseInfo.IsInner || len(n.Args) == 0 {
|
if c.t.parseInfo.IsInner || len(n.Args) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +271,7 @@ func (c *templateContext) collectInner(n *parse.CommandNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.hasIdent(idents, "Inner") {
|
if c.hasIdent(idents, "Inner") {
|
||||||
c.parseInfo.IsInner = true
|
c.t.parseInfo.IsInner = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -351,8 +301,9 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
|
||||||
}
|
}
|
||||||
partialName = "partials/" + partialName
|
partialName = "partials/" + partialName
|
||||||
info := c.lookupFn(partialName)
|
info := c.lookupFn(partialName)
|
||||||
|
|
||||||
if info != nil {
|
if info != nil {
|
||||||
c.id.Add(info.id)
|
c.t.Add(info)
|
||||||
} else {
|
} else {
|
||||||
// Delay for later
|
// Delay for later
|
||||||
c.identityNotFound[partialName] = true
|
c.identityNotFound[partialName] = true
|
||||||
|
@ -361,7 +312,7 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
|
func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
|
||||||
if c.typ != templatePartial || c.returnNode != nil {
|
if c.t.typ != templatePartial || c.returnNode != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,3 +332,18 @@ func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
|
||||||
return false
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findTemplateIn(name string, in tpl.Template) (tpl.Template, bool) {
|
||||||
|
in = unwrap(in)
|
||||||
|
if text, ok := in.(*texttemplate.Template); ok {
|
||||||
|
if templ := text.Lookup(name); templ != nil {
|
||||||
|
return templ, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if templ := in.(*htmltemplate.Template).Lookup(name); templ != nil {
|
||||||
|
return templ, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -13,15 +13,11 @@
|
||||||
package tplimpl
|
package tplimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gohugoio/hugo/hugofs/files"
|
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
"github.com/gohugoio/hugo/identity"
|
|
||||||
"github.com/gohugoio/hugo/tpl"
|
"github.com/gohugoio/hugo/tpl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,7 +29,7 @@ func TestTransformRecursiveTemplate(t *testing.T) {
|
||||||
{{ define "menu-nodes" }}
|
{{ define "menu-nodes" }}
|
||||||
{{ template "menu-node" }}
|
{{ template "menu-node" }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "menu-nßode" }}
|
{{ define "menu-node" }}
|
||||||
{{ template "menu-node" }}
|
{{ template "menu-node" }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ template "menu-nodes" }}
|
{{ template "menu-nodes" }}
|
||||||
|
@ -41,34 +37,44 @@ func TestTransformRecursiveTemplate(t *testing.T) {
|
||||||
|
|
||||||
templ, err := template.New("foo").Parse(recursive)
|
templ, err := template.New("foo").Parse(recursive)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
parseInfo := tpl.DefaultParseInfo
|
ts := newTestTemplate(templ)
|
||||||
|
|
||||||
ctx := newTemplateContext(
|
ctx := newTemplateContext(
|
||||||
newTemplateInfo("test").(identity.Manager),
|
ts,
|
||||||
&parseInfo,
|
newTestTemplateLookup(ts),
|
||||||
createGetTemplateInfoTree(templ.Tree),
|
|
||||||
)
|
)
|
||||||
ctx.applyTransformations(templ.Tree.Root)
|
ctx.applyTransformations(templ.Tree.Root)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createGetTemplateInfoTree(tree *parse.Tree) func(name string) *templateInfoTree {
|
func newTestTemplate(templ tpl.Template) *templateState {
|
||||||
return func(name string) *templateInfoTree {
|
return newTemplateState(
|
||||||
return &templateInfoTree{
|
templ,
|
||||||
tree: tree,
|
templateInfo{
|
||||||
|
name: templ.Name(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestTemplateLookup(in *templateState) func(name string) *templateState {
|
||||||
|
m := make(map[string]*templateState)
|
||||||
|
return func(name string) *templateState {
|
||||||
|
if in.Name() == name {
|
||||||
|
return in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ts, found := m[name]; found {
|
||||||
|
return ts
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
type I interface {
|
if templ, found := findTemplateIn(name, in); found {
|
||||||
Method0()
|
ts := newTestTemplate(templ)
|
||||||
}
|
m[name] = ts
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
|
||||||
type T struct {
|
return nil
|
||||||
NonEmptyInterfaceTypedNil I
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (T) Method0() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCollectInfo(t *testing.T) {
|
func TestCollectInfo(t *testing.T) {
|
||||||
|
@ -98,13 +104,14 @@ func TestCollectInfo(t *testing.T) {
|
||||||
|
|
||||||
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
|
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
parseInfo := tpl.DefaultParseInfo
|
ts := newTestTemplate(templ)
|
||||||
|
ts.typ = templateShortcode
|
||||||
ctx := newTemplateContext(
|
ctx := newTemplateContext(
|
||||||
newTemplateInfo("test").(identity.Manager), &parseInfo, createGetTemplateInfoTree(templ.Tree))
|
ts,
|
||||||
ctx.typ = templateShortcode
|
newTestTemplateLookup(ts),
|
||||||
|
)
|
||||||
ctx.applyTransformations(templ.Tree.Root)
|
ctx.applyTransformations(templ.Tree.Root)
|
||||||
c.Assert(ctx.parseInfo, qt.DeepEquals, &test.expected)
|
c.Assert(ctx.t.parseInfo, qt.DeepEquals, test.expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,11 +148,13 @@ func TestPartialReturn(t *testing.T) {
|
||||||
|
|
||||||
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
|
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
|
ts := newTestTemplate(templ)
|
||||||
|
ctx := newTemplateContext(
|
||||||
|
ts,
|
||||||
|
newTestTemplateLookup(ts),
|
||||||
|
)
|
||||||
|
|
||||||
_, err = applyTemplateTransformers(
|
_, err = ctx.applyTransformations(templ.Tree.Root)
|
||||||
templatePartial,
|
|
||||||
&templateInfoTree{tree: templ.Tree, info: tpl.DefaultParseInfo},
|
|
||||||
createGetTemplateInfoTree(templ.Tree))
|
|
||||||
|
|
||||||
// Just check that it doesn't fail in this test. We have functional tests
|
// Just check that it doesn't fail in this test. We have functional tests
|
||||||
// in hugoblib.
|
// in hugoblib.
|
||||||
|
@ -155,10 +164,3 @@ func TestPartialReturn(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTemplateInfo(name string) tpl.Info {
|
|
||||||
return tpl.NewInfo(
|
|
||||||
identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)),
|
|
||||||
tpl.DefaultParseInfo,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,7 +20,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type templateInfo struct {
|
type templateInfo struct {
|
||||||
|
name string
|
||||||
template string
|
template string
|
||||||
|
isText bool // HTML or plain text template.
|
||||||
|
|
||||||
// Used to create some error context in error situations
|
// Used to create some error context in error situations
|
||||||
fs afero.Fs
|
fs afero.Fs
|
||||||
|
@ -32,6 +34,10 @@ type templateInfo struct {
|
||||||
realFilename string
|
realFilename string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t templateInfo) resolveType() templateType {
|
||||||
|
return resolveTemplateType(t.name)
|
||||||
|
}
|
||||||
|
|
||||||
func (info templateInfo) errWithFileContext(what string, err error) error {
|
func (info templateInfo) errWithFileContext(what string, err error) error {
|
||||||
err = errors.Wrapf(err, what)
|
err = errors.Wrapf(err, what)
|
||||||
|
|
||||||
|
|
|
@ -127,8 +127,8 @@ func TestTemplateFuncsExamples(t *testing.T) {
|
||||||
c.Assert(d.LoadResources(), qt.IsNil)
|
c.Assert(d.LoadResources(), qt.IsNil)
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
templ, _ := d.Tmpl.Lookup("test")
|
templ, _ := d.Tmpl().Lookup("test")
|
||||||
c.Assert(d.Tmpl.Execute(templ, &b, &data), qt.IsNil)
|
c.Assert(d.Tmpl().Execute(templ, &b, &data), qt.IsNil)
|
||||||
if b.String() != expected {
|
if b.String() != expected {
|
||||||
t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected)
|
t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,15 @@ import (
|
||||||
func TestTemplateInfoShortcode(t *testing.T) {
|
func TestTemplateInfoShortcode(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
d := newD(c)
|
d := newD(c)
|
||||||
h := d.Tmpl.(*templateHandler)
|
h := d.Tmpl().(*templateExec)
|
||||||
|
|
||||||
c.Assert(h.AddTemplate("shortcodes/mytemplate.html", `
|
c.Assert(h.AddTemplate("shortcodes/mytemplate.html", `
|
||||||
{{ .Inner }}
|
{{ .Inner }}
|
||||||
`), qt.IsNil)
|
`), qt.IsNil)
|
||||||
|
|
||||||
c.Assert(h.markReady(), qt.IsNil)
|
c.Assert(h.postTransform(), qt.IsNil)
|
||||||
tt, found, _ := d.Tmpl.LookupVariant("mytemplate", tpl.TemplateVariants{})
|
|
||||||
|
tt, found, _ := d.Tmpl().LookupVariant("mytemplate", tpl.TemplateVariants{})
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
c.Assert(found, qt.Equals, true)
|
||||||
tti, ok := tt.(tpl.Info)
|
tti, ok := tt.(tpl.Info)
|
||||||
|
|
Loading…
Reference in a new issue