Add a page template func

Fixes #9339
This commit is contained in:
Bjørn Erik Pedersen 2023-02-25 09:24:59 +01:00
parent 2662faf61f
commit ce524d0b5e
54 changed files with 436 additions and 108 deletions

View file

@ -577,7 +577,7 @@ func (c *commandeer) serve(s *serverCmd) error {
// to cached values if nil.
templ, handler := getErrorTemplateAndHandler(c.hugoTry())
b := &bytes.Buffer{}
err := handler.Execute(templ, b, ctx)
err := handler.ExecuteWithContext(context.Background(), templ, b, ctx)
return b, err
},
}

View file

@ -208,6 +208,23 @@ func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
return time.Time{}, false
}
func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
fn := v.MethodByName(name)
var args []reflect.Value
tp := fn.Type()
if tp.NumIn() > 0 {
if tp.NumIn() > 1 {
panic("not supported")
}
first := tp.In(0)
if first.Implements(ContextInterface) {
args = append(args, reflect.ValueOf(cxt))
}
}
return fn.Call(args)
}
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {

View file

@ -15,6 +15,7 @@ package hugolib
import (
"bytes"
"context"
"errors"
"fmt"
"io"
@ -64,8 +65,10 @@ func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, err
p,
}
ctx := tpl.SetPageInContext(context.Background(), p)
buffer := new(bytes.Buffer)
err := a.t.Execute(templ, buffer, data)
err := a.t.ExecuteWithContext(ctx, templ, buffer, data)
if err != nil {
return nil, err
}

View file

@ -767,10 +767,11 @@ func (h *HugoSites) renderCrossSitesSitemap() error {
}
s := h.Sites[0]
// We don't have any page context to pass in here.
ctx := context.Background()
templ := s.lookupLayouts("sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml")
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemapindex",
return s.renderAndWriteXML(ctx, &s.PathSpec.ProcessingStats.Sitemaps, "sitemapindex",
s.siteCfg.sitemap.Filename, h.toSiteInfos(), templ)
}

View file

@ -740,6 +740,7 @@ func (cp *pageContentOutput) ParseContent(ctx context.Context, content []byte) (
return nil, ok, nil
}
rctx := converter.RenderContext{
Ctx: ctx,
Src: content,
RenderTOC: true,
GetRenderer: cp.renderHooks.getRenderer,
@ -758,6 +759,7 @@ func (cp *pageContentOutput) RenderContent(ctx context.Context, content []byte,
return nil, ok, nil
}
rctx := converter.RenderContext{
Ctx: ctx,
Src: content,
RenderTOC: true,
GetRenderer: cp.renderHooks.getRenderer,
@ -777,6 +779,7 @@ func (cp *pageContentOutput) RenderContent(ctx context.Context, content []byte,
func (cp *pageContentOutput) renderContentWithConverter(ctx context.Context, c converter.Converter, content []byte, renderTOC bool) (converter.ResultRender, error) {
r, err := c.Convert(
converter.RenderContext{
Ctx: ctx,
Src: content,
RenderTOC: renderTOC,
GetRenderer: cp.renderHooks.getRenderer,

View file

@ -1710,12 +1710,12 @@ func (s *Site) lookupLayouts(layouts ...string) tpl.Template {
return nil
}
func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d any, templ tpl.Template) error {
func (s *Site) renderAndWriteXML(ctx context.Context, statCounter *uint64, name string, targetPath string, d any, templ tpl.Template) error {
s.Log.Debugf("Render XML for %q to %q", name, targetPath)
renderBuffer := bp.GetBuffer()
defer bp.PutBuffer(renderBuffer)
if err := s.renderForTemplate(name, "", d, renderBuffer, templ); err != nil {
if err := s.renderForTemplate(ctx, name, "", d, renderBuffer, templ); err != nil {
return err
}
@ -1739,8 +1739,9 @@ func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath s
defer bp.PutBuffer(renderBuffer)
of := p.outputFormat()
ctx := tpl.SetPageInContext(context.Background(), p)
if err := s.renderForTemplate(p.Kind(), of.Name, p, renderBuffer, templ); err != nil {
if err := s.renderForTemplate(ctx, p.Kind(), of.Name, p, renderBuffer, templ); err != nil {
return err
}
@ -1797,16 +1798,16 @@ type hookRendererTemplate struct {
resolvePosition func(ctx any) text.Position
}
func (hr hookRendererTemplate) RenderLink(w io.Writer, ctx hooks.LinkContext) error {
return hr.templateHandler.Execute(hr.templ, w, ctx)
func (hr hookRendererTemplate) RenderLink(cctx context.Context, w io.Writer, ctx hooks.LinkContext) error {
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
}
func (hr hookRendererTemplate) RenderHeading(w io.Writer, ctx hooks.HeadingContext) error {
return hr.templateHandler.Execute(hr.templ, w, ctx)
func (hr hookRendererTemplate) RenderHeading(cctx context.Context, w io.Writer, ctx hooks.HeadingContext) error {
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
}
func (hr hookRendererTemplate) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
return hr.templateHandler.Execute(hr.templ, w, ctx)
func (hr hookRendererTemplate) RenderCodeblock(cctx context.Context, w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
}
func (hr hookRendererTemplate) ResolvePosition(ctx any) text.Position {
@ -1817,13 +1818,15 @@ func (hr hookRendererTemplate) IsDefaultCodeBlockRenderer() bool {
return false
}
func (s *Site) renderForTemplate(name, outputFormat string, d any, w io.Writer, templ tpl.Template) (err error) {
func (s *Site) renderForTemplate(ctx context.Context, name, outputFormat string, d any, w io.Writer, templ tpl.Template) (err error) {
if templ == nil {
s.logMissingLayout(name, "", "", outputFormat)
return nil
}
ctx := context.Background()
if ctx == nil {
panic("nil context")
}
if err = s.Tmpl().ExecuteWithContext(ctx, templ, w, d); err != nil {
return fmt.Errorf("render of %q failed: %w", name, err)

View file

@ -14,6 +14,7 @@
package hugolib
import (
"context"
"fmt"
"path"
"strings"
@ -197,7 +198,7 @@ func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error {
d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1)
targetPaths := page.CreateTargetPaths(d)
if err := s.writeDestAlias(targetPaths.TargetFilename, p.Permalink(), f, nil); err != nil {
if err := s.writeDestAlias(targetPaths.TargetFilename, p.Permalink(), f, p); err != nil {
return err
}
}
@ -278,6 +279,7 @@ func (s *Site) renderSitemap() error {
}
targetPath := p.targetPaths().TargetFilename
ctx := tpl.SetPageInContext(context.Background(), p)
if targetPath == "" {
return errors.New("failed to create targetPath for sitemap")
@ -285,7 +287,7 @@ func (s *Site) renderSitemap() error {
templ := s.lookupLayouts("sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml")
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, templ)
return s.renderAndWriteXML(ctx, &s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, templ)
}
func (s *Site) renderRobotsTXT() error {

View file

@ -15,6 +15,7 @@ package converter
import (
"bytes"
"context"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/loggers"
@ -141,6 +142,9 @@ type DocumentContext struct {
// RenderContext holds contextual information about the content to render.
type RenderContext struct {
// Ctx is the context.Context for the current Page render.
Ctx context.Context
// Src is the content to render.
Src []byte

View file

@ -14,6 +14,7 @@
package hooks
import (
"context"
"io"
"github.com/gohugoio/hugo/common/hugio"
@ -85,12 +86,12 @@ type AttributesOptionsSliceProvider interface {
}
type LinkRenderer interface {
RenderLink(w io.Writer, ctx LinkContext) error
RenderLink(cctx context.Context, w io.Writer, ctx LinkContext) error
identity.Provider
}
type CodeBlockRenderer interface {
RenderCodeblock(w hugio.FlexiWriter, ctx CodeblockContext) error
RenderCodeblock(cctx context.Context, w hugio.FlexiWriter, ctx CodeblockContext) error
identity.Provider
}
@ -119,7 +120,7 @@ type HeadingContext interface {
// HeadingRenderer describes a uniquely identifiable rendering hook.
type HeadingRenderer interface {
// Render writes the rendered content to w using the data in w.
RenderHeading(w io.Writer, ctx HeadingContext) error
RenderHeading(cctx context.Context, w io.Writer, ctx HeadingContext) error
identity.Provider
}

View file

@ -124,6 +124,7 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
cr := renderer.(hooks.CodeBlockRenderer)
err := cr.RenderCodeblock(
ctx.RenderContext().Ctx,
w,
cbctx,
)

View file

@ -181,6 +181,7 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
attrs := r.filterInternalAttributes(n.Attributes())
err := lr.RenderLink(
ctx.RenderContext().Ctx,
w,
imageLinkContext{
linkContext: linkContext{
@ -271,6 +272,7 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
ctx.Buffer.Truncate(pos)
err := lr.RenderLink(
ctx.RenderContext().Ctx,
w,
linkContext{
page: ctx.DocumentContext().Document,
@ -340,6 +342,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
}
err := lr.RenderLink(
ctx.RenderContext().Ctx,
w,
linkContext{
page: ctx.DocumentContext().Document,
@ -428,6 +431,7 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
anchor := anchori.([]byte)
err := hr.RenderHeading(
ctx.RenderContext().Ctx,
w,
headingContext{
page: ctx.DocumentContext().Document,

View file

@ -14,6 +14,7 @@
package highlight
import (
"context"
"fmt"
gohtml "html"
"html/template"
@ -122,7 +123,7 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts a
}, nil
}
func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
func (h chromaHighlighter) RenderCodeblock(cctx context.Context, w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
cfg := h.cfg
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()

View file

@ -135,3 +135,7 @@ func (e *errorResource) DecodeImage() (image.Image, error) {
func (e *errorResource) Transform(...ResourceTransformation) (ResourceTransformer, error) {
panic(e.ResourceError)
}
func (e *errorResource) TransformWithContext(context.Context, ...ResourceTransformation) (ResourceTransformer, error) {
panic(e.ResourceError)
}

View file

@ -159,12 +159,7 @@ func (p Pages) GroupBy(ctx context.Context, key string, order ...string) (PagesG
case reflect.StructField:
fv = ppv.Elem().FieldByName(key)
case reflect.Method:
var args []reflect.Value
fn := hreflect.GetMethodByName(ppv, key)
if fn.Type().NumIn() > 0 && fn.Type().In(0).Implements(hreflect.ContextInterface) {
args = []reflect.Value{reflect.ValueOf(ctx)}
}
fv = fn.Call(args)[0]
fv = hreflect.CallMethodByName(ctx, key, ppv)[0]
}
if !fv.IsValid() {
continue

View file

@ -104,6 +104,7 @@ type ResourceTransformer interface {
type Transformer interface {
Transform(...ResourceTransformation) (ResourceTransformer, error)
TransformWithContext(context.Context, ...ResourceTransformation) (ResourceTransformer, error)
}
func NewFeatureNotAvailableTransformer(key string, elements ...any) ResourceTransformation {

View file

@ -15,6 +15,7 @@
package templates
import (
"context"
"fmt"
"github.com/gohugoio/hugo/helpers"
@ -61,11 +62,11 @@ func (t *executeAsTemplateTransform) Transform(ctx *resources.ResourceTransforma
ctx.OutPath = t.targetPath
return t.t.Tmpl().Execute(templ, ctx.To, t.data)
return t.t.Tmpl().ExecuteWithContext(ctx.Ctx, templ, ctx.To, t.data)
}
func (c *Client) ExecuteAsTemplate(res resources.ResourceTransformer, targetPath string, data any) (resource.Resource, error) {
return res.Transform(&executeAsTemplateTransform{
func (c *Client) ExecuteAsTemplate(ctx context.Context, res resources.ResourceTransformer, targetPath string, data any) (resource.Resource, error) {
return res.TransformWithContext(ctx, &executeAsTemplateTransform{
rs: c.rs,
targetPath: helpers.ToSlashTrimLeading(targetPath),
t: c.t,

View file

@ -69,6 +69,7 @@ func newResourceAdapter(spec *Spec, lazyPublish bool, target transformableResour
return &resourceAdapter{
resourceTransformations: &resourceTransformations{},
resourceAdapterInner: &resourceAdapterInner{
ctx: context.TODO(),
spec: spec,
publishOnce: po,
target: target,
@ -84,6 +85,9 @@ type ResourceTransformation interface {
}
type ResourceTransformationCtx struct {
// The context that started the transformation.
Ctx context.Context
// The content to transform.
From io.Reader
@ -180,6 +184,7 @@ func (r *resourceAdapter) Data() any {
func (r resourceAdapter) cloneTo(targetPath string) resource.Resource {
newtTarget := r.target.cloneTo(targetPath)
newInner := &resourceAdapterInner{
ctx: r.ctx,
spec: r.spec,
target: newtTarget.(transformableResource),
}
@ -278,11 +283,17 @@ func (r *resourceAdapter) Title() string {
}
func (r resourceAdapter) Transform(t ...ResourceTransformation) (ResourceTransformer, error) {
return r.TransformWithContext(context.Background(), t...)
}
func (r resourceAdapter) TransformWithContext(ctx context.Context, t ...ResourceTransformation) (ResourceTransformer, error) {
r.resourceTransformations = &resourceTransformations{
transformations: append(r.transformations, t...),
}
r.resourceAdapterInner = &resourceAdapterInner{
ctx: ctx,
spec: r.spec,
publishOnce: &publishOnce{},
target: r.target,
@ -377,6 +388,7 @@ func (r *resourceAdapter) transform(publish, setContent bool) error {
defer bp.PutBuffer(b2)
tctx := &ResourceTransformationCtx{
Ctx: r.ctx,
Data: make(map[string]any),
OpenResourcePublisher: r.target.openPublishFileForWriting,
}
@ -599,6 +611,9 @@ func (r *resourceAdapter) initTransform(publish, setContent bool) {
}
type resourceAdapterInner struct {
// The context that started this transformation.
ctx context.Context
target transformableResource
spec *Spec

View file

@ -14,6 +14,8 @@
package cast
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.ToInt,

View file

@ -40,7 +40,7 @@ func (ns *Namespace) Apply(ctx context.Context, c any, fname string, args ...any
return nil, errors.New("can't iterate over a nil value")
}
fnv, found := ns.lookupFunc(fname)
fnv, found := ns.lookupFunc(ctx, fname)
if !found {
return nil, errors.New("can't find function " + fname)
}
@ -106,7 +106,7 @@ func applyFnToThis(ctx context.Context, fn, this reflect.Value, args ...any) (re
return reflect.ValueOf(nil), res[1].Interface().(error)
}
func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
func (ns *Namespace) lookupFunc(ctx context.Context, fname string) (reflect.Value, bool) {
namespace, methodName, ok := strings.Cut(fname, ".")
if !ok {
templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter)
@ -114,16 +114,16 @@ func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
}
// Namespace
nv, found := ns.lookupFunc(namespace)
nv, found := ns.lookupFunc(ctx, namespace)
if !found {
return reflect.Value{}, false
}
fn, ok := nv.Interface().(func(...any) (any, error))
fn, ok := nv.Interface().(func(context.Context, ...any) (any, error))
if !ok {
return reflect.Value{}, false
}
v, err := fn()
v, err := fn(ctx)
if err != nil {
panic(err)
}

View file

@ -14,6 +14,8 @@
package collections
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.After,

View file

@ -14,6 +14,8 @@
package compare
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/internal"
@ -31,7 +33,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Default,

View file

@ -14,6 +14,8 @@
package crypto
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.MD5,

View file

@ -1,6 +1,8 @@
package css
import (
"context"
"github.com/gohugoio/hugo/common/types/css"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
@ -31,7 +33,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
return ns

View file

@ -14,6 +14,8 @@
package data
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.GetCSV,

View file

@ -14,6 +14,8 @@
package debug
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Dump,

View file

@ -15,6 +15,8 @@
package diagrams
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -29,7 +31,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
return ns

View file

@ -14,6 +14,8 @@
package encoding
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Base64Decode,

View file

@ -14,6 +14,8 @@
package fmt
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Print,

View file

@ -15,6 +15,8 @@
package hugo
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -27,7 +29,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return h, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return h, nil },
}
// We just add the Hugo struct as the namespace here. No method mappings.

View file

@ -14,6 +14,8 @@
package images
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Config,

View file

@ -14,6 +14,8 @@
package inflect
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Humanize,

View file

@ -60,29 +60,29 @@ func NewExecuter(helper ExecHelper) Executer {
}
type (
dataContextKeyType string
pageContextKeyType string
hasLockContextKeyType string
stackContextKeyType string
)
const (
// The data object passed to Execute or ExecuteWithContext gets stored with this key if not already set.
DataContextKey = dataContextKeyType("data")
// The data page passed to ExecuteWithContext gets stored with this key.
PageContextKey = pageContextKeyType("page")
// Used in partialCached to signal to nested templates that a lock is already taken.
HasLockContextKey = hasLockContextKeyType("hasLock")
)
// Note: The context is currently not fully implemeted in Hugo. This is a work in progress.
func (t *executer) ExecuteWithContext(ctx context.Context, p Preparer, wr io.Writer, data any) error {
if ctx == nil {
panic("nil context")
}
tmpl, err := p.Prepare()
if err != nil {
return err
}
if v := ctx.Value(DataContextKey); v == nil {
ctx = context.WithValue(ctx, DataContextKey, data)
}
value, ok := data.(reflect.Value)
if !ok {
value = reflect.ValueOf(data)
@ -102,28 +102,6 @@ func (t *executer) ExecuteWithContext(ctx context.Context, p Preparer, wr io.Wri
return tmpl.executeWithState(state, value)
}
func (t *executer) Execute(p Preparer, wr io.Writer, data any) error {
tmpl, err := p.Prepare()
if err != nil {
return err
}
value, ok := data.(reflect.Value)
if !ok {
value = reflect.ValueOf(data)
}
state := &state{
helper: t.helper,
prep: p,
tmpl: tmpl,
wr: wr,
vars: []variable{{"$", value}},
}
return tmpl.executeWithState(state, value)
}
// Prepare returns a template ready for execution.
func (t *Template) Prepare() (*Template, error) {
return t, nil

View file

@ -17,6 +17,7 @@ package internal
import (
"bytes"
"context"
"encoding/json"
"fmt"
"go/doc"
@ -49,7 +50,7 @@ type TemplateFuncsNamespace struct {
Name string
// This is the method receiver.
Context func(v ...any) (any, error)
Context func(ctx context.Context, v ...any) (any, error)
// Additional info, aliases and examples, per method name.
MethodMappings map[string]TemplateFuncMethodMapping
@ -172,7 +173,7 @@ func (namespaces TemplateFuncsNamespaces) MarshalJSON() ([]byte, error) {
if i != 0 {
buf.WriteString(",")
}
b, err := ns.toJSON()
b, err := ns.toJSON(context.TODO())
if err != nil {
return nil, err
}
@ -188,7 +189,7 @@ var ignoreFuncs = map[string]bool{
"Reset": true,
}
func (t *TemplateFuncsNamespace) toJSON() ([]byte, error) {
func (t *TemplateFuncsNamespace) toJSON(ctx context.Context) ([]byte, error) {
var buf bytes.Buffer
godoc := getGetTplPackagesGoDoc()[t.Name]
@ -197,11 +198,11 @@ func (t *TemplateFuncsNamespace) toJSON() ([]byte, error) {
buf.WriteString(fmt.Sprintf(`%q: {`, t.Name))
ctx, err := t.Context()
tctx, err := t.Context(ctx)
if err != nil {
return nil, err
}
ctxType := reflect.TypeOf(ctx)
ctxType := reflect.TypeOf(tctx)
for i := 0; i < ctxType.NumMethod(); i++ {
method := ctxType.Method(i)
if ignoreFuncs[method.Name] {

View file

@ -14,6 +14,8 @@
package js
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
return ns

View file

@ -14,6 +14,8 @@
package lang
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/internal"
@ -27,7 +29,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Translate,

View file

@ -14,6 +14,8 @@
package math
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Add,

View file

@ -14,6 +14,8 @@
package openapi3
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Unmarshal,

View file

@ -14,6 +14,8 @@
package os
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Getenv,

49
tpl/page/init.go Normal file
View file

@ -0,0 +1,49 @@
// Copyright 2022 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 page provides template functions for accessing the current Page object,
// the entry level context for the current template.
package page
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/tpl/internal"
)
const name = "page"
func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(ctx context.Context, args ...interface{}) (interface{}, error) {
v := tpl.GetPageFromContext(ctx)
if v == nil {
// The multilingual sitemap does not have a page as its context.
return nil, nil
}
return v.(page.Page), nil
},
}
return ns
}
internal.AddTemplateFuncsNamespace(f)
}

View file

@ -0,0 +1,179 @@
// Copyright 2023 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 page_test
import (
"fmt"
"strings"
"testing"
"github.com/gohugoio/hugo/hugolib"
)
func TestThatPageIsAvailableEverywhere(t *testing.T) {
t.Parallel()
filesTemplate := `
-- config.toml --
baseURL = 'http://example.com/'
disableKinds = ["taxonomy", "term"]
enableInlineShortcodes = true
paginate = 1
enableRobotsTXT = true
LANG_CONFIG
-- content/_index.md --
---
title: "Home"
aliases: ["/homealias/"]
---
{{< shortcode "Angled Brackets" >}}
{{% shortcode "Percentage" %}}
{{< outer >}}
{{< inner >}}
{{< /outer >}}
{{< foo.inline >}}{{ if page.IsHome }}Shortcode Inline OK.{{ end }}{{< /foo.inline >}}
## Heading
[I'm an inline-style link](https://www.google.com)
![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1")
$$$bash
echo "hello";
$$$
-- content/p1.md --
-- content/p2/index.md --
-- content/p2/p2_1.md --
---
title: "P2_1"
---
{{< foo.inline >}}{{ if page.IsHome }}Shortcode in bundled page OK.{{ else}}Failed.{{ end }}{{< /foo.inline >}}
-- content/p3.md --
-- layouts/_default/_markup/render-heading.html --
{{ if page.IsHome }}
Heading OK.
{{ end }}
-- layouts/_default/_markup/render-image.html --
{{ if page.IsHome }}
Image OK.
{{ end }}
-- layouts/_default/_markup/render-link.html --
{{ if page.IsHome }}
Link OK.
{{ end }}
-- layouts/_default/myview.html
{{ if page.IsHome }}
Render OK.
{{ end }}
-- layouts/_default/_markup/render-codeblock.html --
{{ if page.IsHome }}
Codeblock OK.
{{ end }}
-- layouts/_default/single.html --
Single.
-- layouts/index.html --
{{ if eq page . }}Page OK.{{ end }}
{{ $r := "{{ if page.IsHome }}ExecuteAsTemplate OK.{{ end }}" | resources.FromString "foo.html" | resources.ExecuteAsTemplate "foo.html" . }}
{{ $r.Content }}
{{ .RenderString "{{< renderstring.inline >}}{{ if page.IsHome }}RenderString OK.{{ end }}{{< /renderstring.inline >}}}}"}}
{{ .Render "myview" }}
{{ .Content }}
partial: {{ partials.Include "foo.html" . }}
{{ $pag := (.Paginate site.RegularPages) }}
PageNumber: {{ $pag.PageNumber }}/{{ $pag.TotalPages }}|
{{ $p2 := site.GetPage "p2" }}
{{ $p2_1 := index $p2.Resources 0 }}
Bundled page: {{ $p2_1.Content }}
-- layouts/alias.html --
{{ if eq page .Page }}Alias OK.{{ else }}Failed.{{ end }}
-- layouts/404.html --
{{ if eq page . }}404 Page OK.{{ else }}Failed.{{ end }}
-- layouts/partials/foo.html --
{{ if page.IsHome }}Partial OK.{{ else }}Failed.{{ end }}
-- layouts/shortcodes/outer.html --
{{ .Inner }}
-- layouts/shortcodes/inner.html --
{{ if page.IsHome }}Shortcode Inner OK.{{ else }}Failed.{{ end }}
-- layouts/shortcodes/shortcode.html --
{{ if page.IsHome }}Shortcode {{ .Get 0 }} OK.{{ else }}Failed.{{ end }}
-- layouts/sitemap.xml --
HRE?{{ if eq page . }}Sitemap OK.{{ else }}Failed.{{ end }}
-- layouts/robots.txt --
{{ if eq page . }}Robots OK.{{ else }}Failed.{{ end }}
-- layouts/sitemapindex.xml --
{{ if not page }}SitemapIndex OK.{{ else }}Failed.{{ end }}
`
for _, multilingual := range []bool{false, true} {
t.Run(fmt.Sprintf("multilingual-%t", multilingual), func(t *testing.T) {
// Fenced code blocks.
files := strings.ReplaceAll(filesTemplate, "$$$", "```")
if multilingual {
files = strings.ReplaceAll(files, "LANG_CONFIG", `
[languages]
[languages.en]
weight = 1
[languages.no]
weight = 2
`)
} else {
files = strings.ReplaceAll(files, "LANG_CONFIG", "")
}
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b.AssertFileContent("public/index.html", `
Heading OK.
Image OK.
Link OK.
Codeblock OK.
Page OK.
Partial OK.
Shortcode Angled Brackets OK.
Shortcode Percentage OK.
Shortcode Inner OK.
Shortcode Inline OK.
ExecuteAsTemplate OK.
RenderString OK.
Render OK.
Shortcode in bundled page OK.
`)
b.AssertFileContent("public/404.html", `404 Page OK.`)
b.AssertFileContent("public/robots.txt", `Robots OK.`)
b.AssertFileContent("public/homealias/index.html", `Alias OK.`)
b.AssertFileContent("public/page/1/index.html", `Alias OK.`)
b.AssertFileContent("public/page/2/index.html", `Page OK.`)
if multilingual {
b.AssertFileContent("public/sitemap.xml", `SitemapIndex OK.`)
} else {
b.AssertFileContent("public/sitemap.xml", `Sitemap OK.`)
}
})
}
}

View file

@ -14,6 +14,8 @@
package partials
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: namespaceName,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Include,

View file

@ -14,6 +14,7 @@
package path
import (
"context"
"fmt"
"path/filepath"
@ -29,7 +30,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Split,

View file

@ -15,6 +15,8 @@
package reflect
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -27,7 +29,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.IsMap,

View file

@ -14,6 +14,8 @@
package resources
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -30,7 +32,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Get,

View file

@ -15,11 +15,10 @@
package resources
import (
"context"
"fmt"
"sync"
"github.com/gohugoio/hugo/common/herrors"
"errors"
"github.com/gohugoio/hugo/common/maps"
@ -227,7 +226,6 @@ func (ns *Namespace) ByType(typ any) resource.Resources {
//
// See Match for a more complete explanation about the rules used.
func (ns *Namespace) Match(pattern any) resource.Resources {
defer herrors.Recover()
patternStr, err := cast.ToStringE(pattern)
if err != nil {
panic(err)
@ -283,7 +281,7 @@ func (ns *Namespace) FromString(targetPathIn, contentIn any) (resource.Resource,
// ExecuteAsTemplate creates a Resource from a Go template, parsed and executed with
// the given data, and published to the relative target path.
func (ns *Namespace) ExecuteAsTemplate(args ...any) (resource.Resource, error) {
func (ns *Namespace) ExecuteAsTemplate(ctx context.Context, args ...any) (resource.Resource, error) {
if len(args) != 3 {
return nil, fmt.Errorf("must provide targetPath, the template data context and a Resource object")
}
@ -298,7 +296,7 @@ func (ns *Namespace) ExecuteAsTemplate(args ...any) (resource.Resource, error) {
return nil, fmt.Errorf("type %T not supported in Resource transformations", args[2])
}
return ns.templatesClient.ExecuteAsTemplate(r, targetPath, data)
return ns.templatesClient.ExecuteAsTemplate(ctx, r, targetPath, data)
}
// Fingerprint transforms the given Resource with a MD5 hash of the content in

View file

@ -14,6 +14,8 @@
package safe
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.CSS,

View file

@ -15,6 +15,8 @@
package site
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
@ -27,7 +29,7 @@ func init() {
s := d.Site
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return s, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return s, nil },
}
if s == nil {

View file

@ -14,6 +14,8 @@
package strings
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Chomp,

View file

@ -59,7 +59,6 @@ type UnusedTemplatesProvider interface {
// TemplateHandler finds and executes templates.
type TemplateHandler interface {
TemplateFinder
Execute(t Template, wr io.Writer, data any) error
ExecuteWithContext(ctx context.Context, t Template, wr io.Writer, data any) error
LookupLayout(d output.LayoutDescriptor, f output.Format) (Template, bool, error)
HasTemplate(name string) bool
@ -153,10 +152,18 @@ type TemplateFuncGetter interface {
GetFunc(name string) (reflect.Value, bool)
}
// GetDataFromContext returns the template data context (usually .Page) from ctx if set.
// NOte: This is not fully implemented yet.
func GetDataFromContext(ctx context.Context) any {
return ctx.Value(texttemplate.DataContextKey)
// GetPageFromContext returns the top level Page.
func GetPageFromContext(ctx context.Context) any {
return ctx.Value(texttemplate.PageContextKey)
}
// SetPageInContext sets the top level Page.
func SetPageInContext(ctx context.Context, p page) context.Context {
return context.WithValue(ctx, texttemplate.PageContextKey, p)
}
type page interface {
IsNode() bool
}
func GetHasLockFromContext(ctx context.Context) bool {

View file

@ -14,6 +14,8 @@
package templates
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Exists,

View file

@ -14,6 +14,7 @@
package time
import (
"context"
"errors"
"github.com/gohugoio/hugo/deps"
@ -32,7 +33,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) {
Context: func(cctx context.Context, args ...any) (any, error) {
// Handle overlapping "time" namespace and func.
//
// If no args are passed to `time`, assume namespace usage and

View file

@ -232,6 +232,10 @@ func (t templateExec) Clone(d *deps.Deps) *templateExec {
}
func (t *templateExec) Execute(templ tpl.Template, wr io.Writer, data any) error {
// TOD1
if true {
//panic("not implemented")
}
return t.ExecuteWithContext(context.Background(), templ, wr, data)
}

View file

@ -14,6 +14,8 @@
package transform
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.Emojify,

View file

@ -14,6 +14,8 @@
package urls
import (
"context"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...any) (any, error) { return ctx, nil },
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
}
ns.AddMethodMapping(ctx.AbsURL,