From ce524d0b5ebaef05d29fa368465f31358f26dcda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sat, 25 Feb 2023 09:24:59 +0100 Subject: [PATCH] Add a page template func Fixes #9339 --- commands/server.go | 2 +- common/hreflect/helpers.go | 17 ++ hugolib/alias.go | 5 +- hugolib/hugo_sites.go | 5 +- hugolib/page__per_output.go | 3 + hugolib/site.go | 25 +-- hugolib/site_render.go | 6 +- markup/converter/converter.go | 4 + markup/converter/hooks/hooks.go | 7 +- markup/goldmark/codeblocks/render.go | 1 + markup/goldmark/render_hooks.go | 4 + markup/highlight/highlight.go | 3 +- resources/errorResource.go | 4 + resources/page/pagegroup.go | 7 +- resources/resource.go | 1 + .../templates/execute_as_template.go | 7 +- resources/transform.go | 15 ++ tpl/cast/init.go | 4 +- tpl/collections/apply.go | 10 +- tpl/collections/init.go | 4 +- tpl/compare/init.go | 4 +- tpl/crypto/init.go | 4 +- tpl/css/css.go | 4 +- tpl/data/init.go | 4 +- tpl/debug/init.go | 4 +- tpl/diagrams/init.go | 4 +- tpl/encoding/init.go | 4 +- tpl/fmt/init.go | 4 +- tpl/hugo/init.go | 4 +- tpl/images/init.go | 4 +- tpl/inflect/init.go | 4 +- .../texttemplate/hugo_template.go | 36 +--- tpl/internal/templatefuncsRegistry.go | 11 +- tpl/js/init.go | 4 +- tpl/lang/init.go | 4 +- tpl/math/init.go | 4 +- tpl/openapi/openapi3/init.go | 4 +- tpl/os/init.go | 4 +- tpl/page/init.go | 49 +++++ tpl/page/integration_test.go | 179 ++++++++++++++++++ tpl/partials/init.go | 4 +- tpl/path/init.go | 3 +- tpl/reflect/init.go | 4 +- tpl/resources/init.go | 4 +- tpl/resources/resources.go | 8 +- tpl/safe/init.go | 4 +- tpl/site/init.go | 4 +- tpl/strings/init.go | 4 +- tpl/template.go | 17 +- tpl/templates/init.go | 4 +- tpl/time/init.go | 3 +- tpl/tplimpl/template.go | 4 + tpl/transform/init.go | 4 +- tpl/urls/init.go | 4 +- 54 files changed, 436 insertions(+), 108 deletions(-) create mode 100644 tpl/page/init.go create mode 100644 tpl/page/integration_test.go diff --git a/commands/server.go b/commands/server.go index 332e35c01b9..4f7d4db8f76 100644 --- a/commands/server.go +++ b/commands/server.go @@ -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 }, } diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go index 1b7e5acf722..17afbf9127e 100644 --- a/common/hreflect/helpers.go +++ b/common/hreflect/helpers.go @@ -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 { diff --git a/hugolib/alias.go b/hugolib/alias.go index 2609cd6bb49..071f73d412a 100644 --- a/hugolib/alias.go +++ b/hugolib/alias.go @@ -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 } diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index cdc5d97fb78..65cc23971db 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -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) } diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go index ce3498e0ef4..be65ad9e723 100644 --- a/hugolib/page__per_output.go +++ b/hugolib/page__per_output.go @@ -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, diff --git a/hugolib/site.go b/hugolib/site.go index e90fa41ff63..45b4643f035 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -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) diff --git a/hugolib/site_render.go b/hugolib/site_render.go index 51d638ddef0..f105a1ae4c1 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -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 { diff --git a/markup/converter/converter.go b/markup/converter/converter.go index e5a07f1a134..544d4841a89 100644 --- a/markup/converter/converter.go +++ b/markup/converter/converter.go @@ -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 diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go index 7eede071033..55d7c1127fc 100644 --- a/markup/converter/hooks/hooks.go +++ b/markup/converter/hooks/hooks.go @@ -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 } diff --git a/markup/goldmark/codeblocks/render.go b/markup/goldmark/codeblocks/render.go index 739781de1c9..cf5a0f2966c 100644 --- a/markup/goldmark/codeblocks/render.go +++ b/markup/goldmark/codeblocks/render.go @@ -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, ) diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go index f36f9f4e690..0bd800dc0d9 100644 --- a/markup/goldmark/render_hooks.go +++ b/markup/goldmark/render_hooks.go @@ -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, diff --git a/markup/highlight/highlight.go b/markup/highlight/highlight.go index b7499770055..410beb74068 100644 --- a/markup/highlight/highlight.go +++ b/markup/highlight/highlight.go @@ -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() diff --git a/resources/errorResource.go b/resources/errorResource.go index 42edb0bd0a7..c8c32dfc313 100644 --- a/resources/errorResource.go +++ b/resources/errorResource.go @@ -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) +} diff --git a/resources/page/pagegroup.go b/resources/page/pagegroup.go index bac5d8327ea..99f1af3ff7e 100644 --- a/resources/page/pagegroup.go +++ b/resources/page/pagegroup.go @@ -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 diff --git a/resources/resource.go b/resources/resource.go index 8a524247ae3..7ccc5da39de 100644 --- a/resources/resource.go +++ b/resources/resource.go @@ -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 { diff --git a/resources/resource_transformers/templates/execute_as_template.go b/resources/resource_transformers/templates/execute_as_template.go index 5fe4230f133..4d4aba39607 100644 --- a/resources/resource_transformers/templates/execute_as_template.go +++ b/resources/resource_transformers/templates/execute_as_template.go @@ -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, diff --git a/resources/transform.go b/resources/transform.go index 3477c710f34..4ab51485e1b 100644 --- a/resources/transform.go +++ b/resources/transform.go @@ -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 diff --git a/tpl/cast/init.go b/tpl/cast/init.go index f1badf993c6..84211a00b0a 100644 --- a/tpl/cast/init.go +++ b/tpl/cast/init.go @@ -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, diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go index 74ecc5b196f..1dc09c8e51a 100644 --- a/tpl/collections/apply.go +++ b/tpl/collections/apply.go @@ -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) } diff --git a/tpl/collections/init.go b/tpl/collections/init.go index c992d3fb2a1..8801422ac5d 100644 --- a/tpl/collections/init.go +++ b/tpl/collections/init.go @@ -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, diff --git a/tpl/compare/init.go b/tpl/compare/init.go index 98c07f41b8d..f080647b18f 100644 --- a/tpl/compare/init.go +++ b/tpl/compare/init.go @@ -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, diff --git a/tpl/crypto/init.go b/tpl/crypto/init.go index dddc5585a9e..418fbd9fb47 100644 --- a/tpl/crypto/init.go +++ b/tpl/crypto/init.go @@ -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, diff --git a/tpl/css/css.go b/tpl/css/css.go index e1783334ee8..5fc61301192 100644 --- a/tpl/css/css.go +++ b/tpl/css/css.go @@ -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 diff --git a/tpl/data/init.go b/tpl/data/init.go index 22e685fc848..507e0d43e9c 100644 --- a/tpl/data/init.go +++ b/tpl/data/init.go @@ -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, diff --git a/tpl/debug/init.go b/tpl/debug/init.go index 12a99783d2c..796a34bfcfc 100644 --- a/tpl/debug/init.go +++ b/tpl/debug/init.go @@ -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, diff --git a/tpl/diagrams/init.go b/tpl/diagrams/init.go index 1ed308c5730..e6356ce9c45 100644 --- a/tpl/diagrams/init.go +++ b/tpl/diagrams/init.go @@ -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 diff --git a/tpl/encoding/init.go b/tpl/encoding/init.go index 1d42b4e3771..1c3322d6ed5 100644 --- a/tpl/encoding/init.go +++ b/tpl/encoding/init.go @@ -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, diff --git a/tpl/fmt/init.go b/tpl/fmt/init.go index b0683f06174..8000627e211 100644 --- a/tpl/fmt/init.go +++ b/tpl/fmt/init.go @@ -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, diff --git a/tpl/hugo/init.go b/tpl/hugo/init.go index e2b4ae7af5a..ad589722c36 100644 --- a/tpl/hugo/init.go +++ b/tpl/hugo/init.go @@ -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. diff --git a/tpl/images/init.go b/tpl/images/init.go index d9b9af4e7db..a350d5b9d2a 100644 --- a/tpl/images/init.go +++ b/tpl/images/init.go @@ -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, diff --git a/tpl/inflect/init.go b/tpl/inflect/init.go index a2d28f6bfd7..736e9fbd66e 100644 --- a/tpl/inflect/init.go +++ b/tpl/inflect/init.go @@ -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, diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go index 96f526005ed..23bc18e42a0 100644 --- a/tpl/internal/go_templates/texttemplate/hugo_template.go +++ b/tpl/internal/go_templates/texttemplate/hugo_template.go @@ -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 diff --git a/tpl/internal/templatefuncsRegistry.go b/tpl/internal/templatefuncsRegistry.go index 84e0e25faf4..363f6d82f33 100644 --- a/tpl/internal/templatefuncsRegistry.go +++ b/tpl/internal/templatefuncsRegistry.go @@ -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] { diff --git a/tpl/js/init.go b/tpl/js/init.go index d57e0fdcbcf..16e6c7efad1 100644 --- a/tpl/js/init.go +++ b/tpl/js/init.go @@ -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 diff --git a/tpl/lang/init.go b/tpl/lang/init.go index 3f7b57ffca7..62c3a56a049 100644 --- a/tpl/lang/init.go +++ b/tpl/lang/init.go @@ -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, diff --git a/tpl/math/init.go b/tpl/math/init.go index 19905fd3aca..67aa95f4164 100644 --- a/tpl/math/init.go +++ b/tpl/math/init.go @@ -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, diff --git a/tpl/openapi/openapi3/init.go b/tpl/openapi/openapi3/init.go index 8597e3294e2..4d88e148e03 100644 --- a/tpl/openapi/openapi3/init.go +++ b/tpl/openapi/openapi3/init.go @@ -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, diff --git a/tpl/os/init.go b/tpl/os/init.go index cd9e370cd08..d7afba16fbc 100644 --- a/tpl/os/init.go +++ b/tpl/os/init.go @@ -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, diff --git a/tpl/page/init.go b/tpl/page/init.go new file mode 100644 index 00000000000..52aeaafd69e --- /dev/null +++ b/tpl/page/init.go @@ -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) +} diff --git a/tpl/page/integration_test.go b/tpl/page/integration_test.go new file mode 100644 index 00000000000..30a4885e0cd --- /dev/null +++ b/tpl/page/integration_test.go @@ -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.`) + } + + }) + + } + +} diff --git a/tpl/partials/init.go b/tpl/partials/init.go index 2662b8894d1..e9d901bbf63 100644 --- a/tpl/partials/init.go +++ b/tpl/partials/init.go @@ -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, diff --git a/tpl/path/init.go b/tpl/path/init.go index 0d81f61819b..97a479d3f01 100644 --- a/tpl/path/init.go +++ b/tpl/path/init.go @@ -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, diff --git a/tpl/reflect/init.go b/tpl/reflect/init.go index 3af6dfa119e..259f97323f2 100644 --- a/tpl/reflect/init.go +++ b/tpl/reflect/init.go @@ -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, diff --git a/tpl/resources/init.go b/tpl/resources/init.go index 73a7b8f42ea..db51b0287e5 100644 --- a/tpl/resources/init.go +++ b/tpl/resources/init.go @@ -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, diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go index 85323f057f6..cc5310277f7 100644 --- a/tpl/resources/resources.go +++ b/tpl/resources/resources.go @@ -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 diff --git a/tpl/safe/init.go b/tpl/safe/init.go index 794c9d6f0f2..8fc0e82eaa5 100644 --- a/tpl/safe/init.go +++ b/tpl/safe/init.go @@ -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, diff --git a/tpl/site/init.go b/tpl/site/init.go index 34ea7309f70..3d293f3fe2f 100644 --- a/tpl/site/init.go +++ b/tpl/site/init.go @@ -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 { diff --git a/tpl/strings/init.go b/tpl/strings/init.go index 503ec6a25e2..37a48912852 100644 --- a/tpl/strings/init.go +++ b/tpl/strings/init.go @@ -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, diff --git a/tpl/template.go b/tpl/template.go index dd9249adef5..f71de8bb2a0 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -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 { diff --git a/tpl/templates/init.go b/tpl/templates/init.go index e068fca81c2..ff6acdabd0b 100644 --- a/tpl/templates/init.go +++ b/tpl/templates/init.go @@ -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, diff --git a/tpl/time/init.go b/tpl/time/init.go index 4bb2ddf67bf..583dacd4acd 100644 --- a/tpl/time/init.go +++ b/tpl/time/init.go @@ -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 diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go index c01863ebb4b..70541e07682 100644 --- a/tpl/tplimpl/template.go +++ b/tpl/tplimpl/template.go @@ -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) } diff --git a/tpl/transform/init.go b/tpl/transform/init.go index dd31446bdd2..00ae8f89d04 100644 --- a/tpl/transform/init.go +++ b/tpl/transform/init.go @@ -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, diff --git a/tpl/urls/init.go b/tpl/urls/init.go index 3597e87c52a..ec954640d29 100644 --- a/tpl/urls/init.go +++ b/tpl/urls/init.go @@ -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,