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. // to cached values if nil.
templ, handler := getErrorTemplateAndHandler(c.hugoTry()) templ, handler := getErrorTemplateAndHandler(c.hugoTry())
b := &bytes.Buffer{} b := &bytes.Buffer{}
err := handler.Execute(templ, b, ctx) err := handler.ExecuteWithContext(context.Background(), templ, b, ctx)
return b, err return b, err
}, },
} }

View file

@ -208,6 +208,23 @@ func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
return time.Time{}, false 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 // Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value { func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface { if v.Kind() != reflect.Interface {

View file

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

View file

@ -767,10 +767,11 @@ func (h *HugoSites) renderCrossSitesSitemap() error {
} }
s := h.Sites[0] 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") templ := s.lookupLayouts("sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml")
return s.renderAndWriteXML(ctx, &s.PathSpec.ProcessingStats.Sitemaps, "sitemapindex",
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemapindex",
s.siteCfg.sitemap.Filename, h.toSiteInfos(), templ) 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 return nil, ok, nil
} }
rctx := converter.RenderContext{ rctx := converter.RenderContext{
Ctx: ctx,
Src: content, Src: content,
RenderTOC: true, RenderTOC: true,
GetRenderer: cp.renderHooks.getRenderer, GetRenderer: cp.renderHooks.getRenderer,
@ -758,6 +759,7 @@ func (cp *pageContentOutput) RenderContent(ctx context.Context, content []byte,
return nil, ok, nil return nil, ok, nil
} }
rctx := converter.RenderContext{ rctx := converter.RenderContext{
Ctx: ctx,
Src: content, Src: content,
RenderTOC: true, RenderTOC: true,
GetRenderer: cp.renderHooks.getRenderer, 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) { func (cp *pageContentOutput) renderContentWithConverter(ctx context.Context, c converter.Converter, content []byte, renderTOC bool) (converter.ResultRender, error) {
r, err := c.Convert( r, err := c.Convert(
converter.RenderContext{ converter.RenderContext{
Ctx: ctx,
Src: content, Src: content,
RenderTOC: renderTOC, RenderTOC: renderTOC,
GetRenderer: cp.renderHooks.getRenderer, GetRenderer: cp.renderHooks.getRenderer,

View file

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

View file

@ -14,6 +14,7 @@
package hugolib package hugolib
import ( import (
"context"
"fmt" "fmt"
"path" "path"
"strings" "strings"
@ -197,7 +198,7 @@ func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error {
d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1) d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1)
targetPaths := page.CreateTargetPaths(d) 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 return err
} }
} }
@ -278,6 +279,7 @@ func (s *Site) renderSitemap() error {
} }
targetPath := p.targetPaths().TargetFilename targetPath := p.targetPaths().TargetFilename
ctx := tpl.SetPageInContext(context.Background(), p)
if targetPath == "" { if targetPath == "" {
return errors.New("failed to create targetPath for sitemap") 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") 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 { func (s *Site) renderRobotsTXT() error {

View file

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

View file

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

View file

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

View file

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

View file

@ -14,6 +14,7 @@
package highlight package highlight
import ( import (
"context"
"fmt" "fmt"
gohtml "html" gohtml "html"
"html/template" "html/template"
@ -122,7 +123,7 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts a
}, nil }, 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 cfg := h.cfg
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice() 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) { func (e *errorResource) Transform(...ResourceTransformation) (ResourceTransformer, error) {
panic(e.ResourceError) 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: case reflect.StructField:
fv = ppv.Elem().FieldByName(key) fv = ppv.Elem().FieldByName(key)
case reflect.Method: case reflect.Method:
var args []reflect.Value fv = hreflect.CallMethodByName(ctx, key, ppv)[0]
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]
} }
if !fv.IsValid() { if !fv.IsValid() {
continue continue

View file

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

View file

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

View file

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

View file

@ -14,6 +14,8 @@
package cast package cast
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, 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") return nil, errors.New("can't iterate over a nil value")
} }
fnv, found := ns.lookupFunc(fname) fnv, found := ns.lookupFunc(ctx, fname)
if !found { if !found {
return nil, errors.New("can't find function " + fname) 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) 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, ".") namespace, methodName, ok := strings.Cut(fname, ".")
if !ok { if !ok {
templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter) templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter)
@ -114,16 +114,16 @@ func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
} }
// Namespace // Namespace
nv, found := ns.lookupFunc(namespace) nv, found := ns.lookupFunc(ctx, namespace)
if !found { if !found {
return reflect.Value{}, false return reflect.Value{}, false
} }
fn, ok := nv.Interface().(func(...any) (any, error)) fn, ok := nv.Interface().(func(context.Context, ...any) (any, error))
if !ok { if !ok {
return reflect.Value{}, false return reflect.Value{}, false
} }
v, err := fn() v, err := fn(ctx)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -14,6 +14,8 @@
package collections package collections
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.After,

View file

@ -14,6 +14,8 @@
package compare package compare
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
@ -31,7 +33,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Default,

View file

@ -14,6 +14,8 @@
package crypto package crypto
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.MD5,

View file

@ -1,6 +1,8 @@
package css package css
import ( import (
"context"
"github.com/gohugoio/hugo/common/types/css" "github.com/gohugoio/hugo/common/types/css"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
@ -31,7 +33,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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 return ns

View file

@ -14,6 +14,8 @@
package data package data
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.GetCSV,

View file

@ -14,6 +14,8 @@
package debug package debug
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Dump,

View file

@ -15,6 +15,8 @@
package diagrams package diagrams
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -29,7 +31,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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 return ns

View file

@ -14,6 +14,8 @@
package encoding package encoding
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Base64Decode,

View file

@ -14,6 +14,8 @@
package fmt package fmt
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Print,

View file

@ -15,6 +15,8 @@
package hugo package hugo
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -27,7 +29,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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. // We just add the Hugo struct as the namespace here. No method mappings.

View file

@ -14,6 +14,8 @@
package images package images
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Config,

View file

@ -14,6 +14,8 @@
package inflect package inflect
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Humanize,

View file

@ -60,29 +60,29 @@ func NewExecuter(helper ExecHelper) Executer {
} }
type ( type (
dataContextKeyType string pageContextKeyType string
hasLockContextKeyType string hasLockContextKeyType string
stackContextKeyType string stackContextKeyType string
) )
const ( const (
// The data object passed to Execute or ExecuteWithContext gets stored with this key if not already set. // The data page passed to ExecuteWithContext gets stored with this key.
DataContextKey = dataContextKeyType("data") PageContextKey = pageContextKeyType("page")
// Used in partialCached to signal to nested templates that a lock is already taken. // Used in partialCached to signal to nested templates that a lock is already taken.
HasLockContextKey = hasLockContextKeyType("hasLock") HasLockContextKey = hasLockContextKeyType("hasLock")
) )
// Note: The context is currently not fully implemeted in Hugo. This is a work in progress. // 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 { 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() tmpl, err := p.Prepare()
if err != nil { if err != nil {
return err return err
} }
if v := ctx.Value(DataContextKey); v == nil {
ctx = context.WithValue(ctx, DataContextKey, data)
}
value, ok := data.(reflect.Value) value, ok := data.(reflect.Value)
if !ok { if !ok {
value = reflect.ValueOf(data) 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) 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. // Prepare returns a template ready for execution.
func (t *Template) Prepare() (*Template, error) { func (t *Template) Prepare() (*Template, error) {
return t, nil return t, nil

View file

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

View file

@ -14,6 +14,8 @@
package js package js
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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 return ns

View file

@ -14,6 +14,8 @@
package lang package lang
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
@ -27,7 +29,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Translate,

View file

@ -14,6 +14,8 @@
package math package math
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Add,

View file

@ -14,6 +14,8 @@
package openapi3 package openapi3
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Unmarshal,

View file

@ -14,6 +14,8 @@
package os package os
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, 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 package partials
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: namespaceName, 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, ns.AddMethodMapping(ctx.Include,

View file

@ -14,6 +14,7 @@
package path package path
import ( import (
"context"
"fmt" "fmt"
"path/filepath" "path/filepath"
@ -29,7 +30,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Split,

View file

@ -15,6 +15,8 @@
package reflect package reflect
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -27,7 +29,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.IsMap,

View file

@ -14,6 +14,8 @@
package resources package resources
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -30,7 +32,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Get,

View file

@ -15,11 +15,10 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"sync" "sync"
"github.com/gohugoio/hugo/common/herrors"
"errors" "errors"
"github.com/gohugoio/hugo/common/maps" "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. // See Match for a more complete explanation about the rules used.
func (ns *Namespace) Match(pattern any) resource.Resources { func (ns *Namespace) Match(pattern any) resource.Resources {
defer herrors.Recover()
patternStr, err := cast.ToStringE(pattern) patternStr, err := cast.ToStringE(pattern)
if err != nil { if err != nil {
panic(err) 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 // ExecuteAsTemplate creates a Resource from a Go template, parsed and executed with
// the given data, and published to the relative target path. // 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 { if len(args) != 3 {
return nil, fmt.Errorf("must provide targetPath, the template data context and a Resource object") 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 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 // Fingerprint transforms the given Resource with a MD5 hash of the content in

View file

@ -14,6 +14,8 @@
package safe package safe
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.CSS,

View file

@ -15,6 +15,8 @@
package site package site
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
@ -27,7 +29,7 @@ func init() {
s := d.Site s := d.Site
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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 { if s == nil {

View file

@ -14,6 +14,8 @@
package strings package strings
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Chomp,

View file

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

View file

@ -14,6 +14,8 @@
package templates package templates
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Exists,

View file

@ -14,6 +14,7 @@
package time package time
import ( import (
"context"
"errors" "errors"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
@ -32,7 +33,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func(args ...any) (any, error) { Context: func(cctx context.Context, args ...any) (any, error) {
// Handle overlapping "time" namespace and func. // Handle overlapping "time" namespace and func.
// //
// If no args are passed to `time`, assume namespace usage and // 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 { 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) return t.ExecuteWithContext(context.Background(), templ, wr, data)
} }

View file

@ -14,6 +14,8 @@
package transform package transform
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.Emojify,

View file

@ -14,6 +14,8 @@
package urls package urls
import ( import (
"context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
) )
@ -26,7 +28,7 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, 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, ns.AddMethodMapping(ctx.AbsURL,