From 376704d382df163c7a0db066900f021ea5f7894d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 1 Mar 2022 11:30:11 +0100 Subject: [PATCH] tpl/collections: Fix apply when function have Context as first arg As introduced in `partial` and `partialCached` in Hugo 0.93.0. Fixes #9585 --- common/hreflect/helpers.go | 3 ++ tpl/collections/apply.go | 17 ++++++----- tpl/collections/apply_test.go | 12 ++++---- tpl/collections/integration_test.go | 45 +++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 tpl/collections/integration_test.go diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go index 0cc42ecc5..beab182bb 100644 --- a/common/hreflect/helpers.go +++ b/common/hreflect/helpers.go @@ -17,6 +17,7 @@ package hreflect import ( + "context" "reflect" "github.com/gohugoio/hugo/common/types" @@ -124,3 +125,5 @@ func indirectInterface(v reflect.Value) reflect.Value { } return v.Elem() } + +var ContextInterface = reflect.TypeOf((*context.Context)(nil)).Elem() diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go index 6eedb4b63..0833e5507 100644 --- a/tpl/collections/apply.go +++ b/tpl/collections/apply.go @@ -14,16 +14,18 @@ package collections import ( + "context" "errors" "fmt" "reflect" "strings" + "github.com/gohugoio/hugo/common/hreflect" "github.com/gohugoio/hugo/tpl" ) // Apply takes a map, array, or slice and returns a new slice with the function fname applied over it. -func (ns *Namespace) Apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) { +func (ns *Namespace) Apply(ctx context.Context, seq interface{}, fname string, args ...interface{}) (interface{}, error) { if seq == nil { return make([]interface{}, 0), nil } @@ -43,15 +45,13 @@ func (ns *Namespace) Apply(seq interface{}, fname string, args ...interface{}) ( return nil, errors.New("can't find function " + fname) } - // fnv := reflect.ValueOf(fn) - switch seqv.Kind() { case reflect.Array, reflect.Slice: r := make([]interface{}, seqv.Len()) for i := 0; i < seqv.Len(); i++ { vv := seqv.Index(i) - vvv, err := applyFnToThis(fnv, vv, args...) + vvv, err := applyFnToThis(ctx, fnv, vv, args...) if err != nil { return nil, err } @@ -65,7 +65,12 @@ func (ns *Namespace) Apply(seq interface{}, fname string, args ...interface{}) ( } } -func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value, error) { +func applyFnToThis(ctx context.Context, fn, this reflect.Value, args ...interface{}) (reflect.Value, error) { + num := fn.Type().NumIn() + if num > 0 && fn.Type().In(0).Implements(hreflect.ContextInterface) { + args = append([]interface{}{ctx}, args...) + } + n := make([]reflect.Value, len(args)) for i, arg := range args { if arg == "." { @@ -75,8 +80,6 @@ func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value, } } - num := fn.Type().NumIn() - if fn.Type().IsVariadic() { num-- } diff --git a/tpl/collections/apply_test.go b/tpl/collections/apply_test.go index 1afb66808..9ad0f405d 100644 --- a/tpl/collections/apply_test.go +++ b/tpl/collections/apply_test.go @@ -73,21 +73,23 @@ func TestApply(t *testing.T) { strings := []interface{}{"a\n", "b\n"} - result, err := ns.Apply(strings, "print", "a", "b", "c") + ctx := context.Background() + + result, err := ns.Apply(ctx, strings, "print", "a", "b", "c") c.Assert(err, qt.IsNil) c.Assert(result, qt.DeepEquals, []interface{}{"abc", "abc"}) - _, err = ns.Apply(strings, "apply", ".") + _, err = ns.Apply(ctx, strings, "apply", ".") c.Assert(err, qt.Not(qt.IsNil)) var nilErr *error - _, err = ns.Apply(nilErr, "chomp", ".") + _, err = ns.Apply(ctx, nilErr, "chomp", ".") c.Assert(err, qt.Not(qt.IsNil)) - _, err = ns.Apply(strings, "dobedobedo", ".") + _, err = ns.Apply(ctx, strings, "dobedobedo", ".") c.Assert(err, qt.Not(qt.IsNil)) - _, err = ns.Apply(strings, "foo.Chomp", "c\n") + _, err = ns.Apply(ctx, strings, "foo.Chomp", "c\n") if err == nil { t.Errorf("apply with unknown func should fail") } diff --git a/tpl/collections/integration_test.go b/tpl/collections/integration_test.go new file mode 100644 index 000000000..d767c384c --- /dev/null +++ b/tpl/collections/integration_test.go @@ -0,0 +1,45 @@ +// 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 collections_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +// Issue 9585 +func TestApplyWithContext(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +{{ apply (seq 3) "partial" "foo.html"}} +-- layouts/partials/foo.html -- +{{ return "foo"}} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` + [foo foo foo] +`) +}