// 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
},
}
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 {
import (
"bytes"
+ "context"
"errors"
"fmt"
"io"
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
}
}
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)
}
return nil, ok, nil
}
rctx := converter.RenderContext{
+ Ctx: ctx,
Src: content,
RenderTOC: true,
GetRenderer: cp.renderHooks.getRenderer,
return nil, ok, nil
}
rctx := converter.RenderContext{
+ Ctx: ctx,
Src: content,
RenderTOC: true,
GetRenderer: cp.renderHooks.getRenderer,
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,
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
}
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
}
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 {
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)
package hugolib
import (
+ "context"
"fmt"
"path"
"strings"
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
}
}
}
targetPath := p.targetPaths().TargetFilename
+ ctx := tpl.SetPageInContext(context.Background(), p)
if targetPath == "" {
return errors.New("failed to create targetPath for sitemap")
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 {
import (
"bytes"
+ "context"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/loggers"
// 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
package hooks
import (
+ "context"
"io"
"github.com/gohugoio/hugo/common/hugio"
}
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
}
// 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
}
cr := renderer.(hooks.CodeBlockRenderer)
err := cr.RenderCodeblock(
+ ctx.RenderContext().Ctx,
w,
cbctx,
)
attrs := r.filterInternalAttributes(n.Attributes())
err := lr.RenderLink(
+ ctx.RenderContext().Ctx,
w,
imageLinkContext{
linkContext: linkContext{
ctx.Buffer.Truncate(pos)
err := lr.RenderLink(
+ ctx.RenderContext().Ctx,
w,
linkContext{
page: ctx.DocumentContext().Document,
}
err := lr.RenderLink(
+ ctx.RenderContext().Ctx,
w,
linkContext{
page: ctx.DocumentContext().Document,
anchor := anchori.([]byte)
err := hr.RenderHeading(
+ ctx.RenderContext().Ctx,
w,
headingContext{
page: ctx.DocumentContext().Document,
package highlight
import (
+ "context"
"fmt"
gohtml "html"
"html/template"
}, 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()
func (e *errorResource) Transform(...ResourceTransformation) (ResourceTransformer, error) {
panic(e.ResourceError)
}
+
+func (e *errorResource) TransformWithContext(context.Context, ...ResourceTransformation) (ResourceTransformer, error) {
+ panic(e.ResourceError)
+}
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
type Transformer interface {
Transform(...ResourceTransformation) (ResourceTransformer, error)
+ TransformWithContext(context.Context, ...ResourceTransformation) (ResourceTransformer, error)
}
func NewFeatureNotAvailableTransformer(key string, elements ...any) ResourceTransformation {
package templates
import (
+ "context"
"fmt"
"github.com/gohugoio/hugo/helpers"
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,
return &resourceAdapter{
resourceTransformations: &resourceTransformations{},
resourceAdapterInner: &resourceAdapterInner{
+ ctx: context.TODO(),
spec: spec,
publishOnce: po,
target: target,
}
type ResourceTransformationCtx struct {
+ // The context that started the transformation.
+ Ctx context.Context
+
// The content to transform.
From io.Reader
func (r resourceAdapter) cloneTo(targetPath string) resource.Resource {
newtTarget := r.target.cloneTo(targetPath)
newInner := &resourceAdapterInner{
+ ctx: r.ctx,
spec: r.spec,
target: newtTarget.(transformableResource),
}
}
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,
defer bp.PutBuffer(b2)
tctx := &ResourceTransformationCtx{
+ Ctx: r.ctx,
Data: make(map[string]any),
OpenResourcePublisher: r.target.openPublishFileForWriting,
}
}
type resourceAdapterInner struct {
+ // The context that started this transformation.
+ ctx context.Context
+
target transformableResource
spec *Spec
package cast
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
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)
}
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)
}
// 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)
}
package collections
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package compare
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/internal"
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,
package crypto
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package css
import (
+ "context"
+
"github.com/gohugoio/hugo/common/types/css"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
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
package data
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package debug
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package diagrams
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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
package encoding
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package fmt
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package hugo
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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.
package images
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package inflect
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
}
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)
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
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"go/doc"
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
if i != 0 {
buf.WriteString(",")
}
- b, err := ns.toJSON()
+ b, err := ns.toJSON(context.TODO())
if err != nil {
return nil, err
}
"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]
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] {
package js
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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
package lang
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/internal"
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,
package math
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package openapi3
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package os
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
--- /dev/null
+// 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)
+}
--- /dev/null
+// 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)
+
+
+
+$$$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.`)
+ }
+
+ })
+
+ }
+
+}
package partials
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package path
import (
+ "context"
"fmt"
"path/filepath"
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,
package reflect
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package resources
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package resources
import (
+ "context"
"fmt"
"sync"
- "github.com/gohugoio/hugo/common/herrors"
-
"errors"
"github.com/gohugoio/hugo/common/maps"
//
// 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)
// 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")
}
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
package safe
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package site
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
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 {
package strings
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
// 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
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 {
package templates
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package time
import (
+ "context"
"errors"
"github.com/gohugoio/hugo/deps"
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
}
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)
}
package transform
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,
package urls
import (
+ "context"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)
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,