diff options
Diffstat (limited to 'tpl')
52 files changed, 362 insertions, 408 deletions
diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go index 397ba0fdb..3d50395b9 100644 --- a/tpl/collections/apply.go +++ b/tpl/collections/apply.go @@ -67,7 +67,7 @@ func (ns *Namespace) Apply(ctx context.Context, c any, fname string, args ...any func applyFnToThis(ctx context.Context, fn, this reflect.Value, args ...any) (reflect.Value, error) { num := fn.Type().NumIn() - if num > 0 && fn.Type().In(0).Implements(hreflect.ContextInterface) { + if num > 0 && hreflect.IsContextType(fn.Type().In(0)) { args = append([]any{ctx}, args...) } diff --git a/tpl/collections/apply_test.go b/tpl/collections/apply_test.go index aa39923b7..0a5764264 100644 --- a/tpl/collections/apply_test.go +++ b/tpl/collections/apply_test.go @@ -22,6 +22,7 @@ import ( qt "github.com/frankban/quicktest" "github.com/gohugoio/hugo/config/testconfig" + "github.com/gohugoio/hugo/identity" "github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/output/layouts" "github.com/gohugoio/hugo/tpl" @@ -29,6 +30,10 @@ import ( type templateFinder int +func (templateFinder) GetIdentity(string) (identity.Identity, bool) { + return identity.StringIdentity("test"), true +} + func (templateFinder) Lookup(name string) (tpl.Template, bool) { return nil, false } diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index e34753f17..61fd138e9 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -35,11 +35,6 @@ import ( "github.com/spf13/cast" ) -func init() { - // htime.Now cannot be used here - rand.Seed(time.Now().UTC().UnixNano()) -} - // New returns a new instance of the collections-namespaced template functions. func New(deps *deps.Deps) *Namespace { language := deps.Conf.Language() @@ -149,7 +144,7 @@ func (ns *Namespace) Delimit(ctx context.Context, l, sep any, last ...any) (stri } default: - return "", fmt.Errorf("can't iterate over %v", l) + return "", fmt.Errorf("can't iterate over %T", l) } return str, nil diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index dcdd3bd5c..7dd518759 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -699,7 +699,6 @@ func TestShuffleRandomising(t *testing.T) { // of the sequence happens to be the same as the original sequence. However // the probability of the event is 10^-158 which is negligible. seqLen := 100 - rand.Seed(time.Now().UTC().UnixNano()) for _, test := range []struct { seq []int @@ -895,6 +894,7 @@ func (x TstX) TstRv2() string { return "r" + x.B } +//lint:ignore U1000 reflect test func (x TstX) unexportedMethod() string { return x.unexported } @@ -923,7 +923,7 @@ func (x TstX) String() string { type TstX struct { A, B string - unexported string + unexported string //lint:ignore U1000 reflect test } type TstParams struct { diff --git a/tpl/collections/integration_test.go b/tpl/collections/integration_test.go index a443755f8..24727a12c 100644 --- a/tpl/collections/integration_test.go +++ b/tpl/collections/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -97,11 +97,9 @@ func TestAppendSliceToASliceOfSlices(t *testing.T) { ).Build() b.AssertFileContent("public/index.html", "[[a] [b] [c]]") - } func TestAppendNilToSlice(t *testing.T) { - t.Parallel() files := ` @@ -123,11 +121,9 @@ func TestAppendNilToSlice(t *testing.T) { ).Build() b.AssertFileContent("public/index.html", "[a <nil>]") - } func TestAppendNilsToSliceWithNils(t *testing.T) { - t.Parallel() files := ` @@ -153,7 +149,6 @@ func TestAppendNilsToSliceWithNils(t *testing.T) { b.AssertFileContent("public/index.html", "[a <nil> c <nil>]") } - } // Issue 11234. diff --git a/tpl/collections/where.go b/tpl/collections/where.go index 07c2d3deb..bf3f75044 100644 --- a/tpl/collections/where.go +++ b/tpl/collections/where.go @@ -51,7 +51,7 @@ func (ns *Namespace) Where(ctx context.Context, c, key any, args ...any) (any, e case reflect.Map: return ns.checkWhereMap(ctxv, seqv, kv, mv, path, op) default: - return nil, fmt.Errorf("can't iterate over %v", c) + return nil, fmt.Errorf("can't iterate over %T", c) } } @@ -320,7 +320,7 @@ func evaluateSubElem(ctx, obj reflect.Value, elemName string) (reflect.Value, er mt := objPtr.Type().Method(index) num := mt.Type.NumIn() maxNumIn := 1 - if num > 1 && mt.Type.In(1).Implements(hreflect.ContextInterface) { + if num > 1 && hreflect.IsContextType(mt.Type.In(1)) { args = []reflect.Value{ctx} maxNumIn = 2 } diff --git a/tpl/data/data.go b/tpl/data/data.go index 380c25685..b6b0515e8 100644 --- a/tpl/data/data.go +++ b/tpl/data/data.go @@ -24,6 +24,7 @@ import ( "net/http" "strings" + "github.com/gohugoio/hugo/cache/filecache" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/config/security" @@ -33,7 +34,6 @@ import ( "github.com/spf13/cast" - "github.com/gohugoio/hugo/cache/filecache" "github.com/gohugoio/hugo/deps" ) @@ -108,7 +108,7 @@ func (ns *Namespace) GetJSON(args ...any) (any, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { - return nil, fmt.Errorf("Failed to create request for getJSON resource %s: %w", url, err) + return nil, fmt.Errorf("failed to create request for getJSON resource %s: %w", url, err) } unmarshal := func(b []byte) (bool, error) { diff --git a/tpl/data/resources.go b/tpl/data/resources.go index 45764dae7..3a3701d60 100644 --- a/tpl/data/resources.go +++ b/tpl/data/resources.go @@ -23,7 +23,6 @@ import ( "time" "github.com/gohugoio/hugo/cache/filecache" - "github.com/gohugoio/hugo/helpers" "github.com/spf13/afero" ) @@ -68,7 +67,7 @@ func (ns *Namespace) getRemote(cache *filecache.Cache, unmarshal func([]byte) (b res.Body.Close() if isHTTPError(res) { - return nil, fmt.Errorf("Failed to retrieve remote file: %s, body: %q", http.StatusText(res.StatusCode), b) + return nil, fmt.Errorf("failed to retrieve remote file: %s, body: %q", http.StatusText(res.StatusCode), b) } retry, err = unmarshal(b) diff --git a/tpl/data/resources_test.go b/tpl/data/resources_test.go index d452a2a43..b8003bf43 100644 --- a/tpl/data/resources_test.go +++ b/tpl/data/resources_test.go @@ -15,9 +15,6 @@ package data import ( "bytes" - - "github.com/gohugoio/hugo/common/loggers" - "net/http" "net/http/httptest" "net/url" @@ -26,12 +23,14 @@ import ( "testing" "time" + "github.com/gohugoio/hugo/cache/filecache" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/config/testconfig" "github.com/gohugoio/hugo/helpers" qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/cache/filecache" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/hugofs" diff --git a/tpl/debug/integration_test.go b/tpl/debug/integration_test.go index 3d120580d..9a36e2d12 100644 --- a/tpl/debug/integration_test.go +++ b/tpl/debug/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -41,5 +41,5 @@ disableKinds = ["taxonomy", "term"] }, ).Build() - b.AssertLogContains("imer: name \"foo\" count '\\x05' duration") + b.AssertLogContains("timer: name foo count 5 duration") } diff --git a/tpl/diagrams/diagrams.go b/tpl/diagrams/diagrams.go index dfa29a978..6a58bcfe4 100644 --- a/tpl/diagrams/diagrams.go +++ b/tpl/diagrams/diagrams.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. diff --git a/tpl/diagrams/goat.go b/tpl/diagrams/goat.go index f3d4f4bfb..fe156f1e8 100644 --- a/tpl/diagrams/goat.go +++ b/tpl/diagrams/goat.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. diff --git a/tpl/diagrams/init.go b/tpl/diagrams/init.go index e6356ce9c..0cbec7e1b 100644 --- a/tpl/diagrams/init.go +++ b/tpl/diagrams/init.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. diff --git a/tpl/fmt/integration_test.go b/tpl/fmt/integration_test.go index 5010fa90e..40bfefcdc 100644 --- a/tpl/fmt/integration_test.go +++ b/tpl/fmt/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -41,5 +41,4 @@ ignoreErrors = ['error-b'] b.BuildE() b.AssertLogMatches(`^ERROR a\nYou can suppress this error by adding the following to your site configuration:\nignoreErrors = \['error-a'\]\n$`) - } diff --git a/tpl/images/integration_test.go b/tpl/images/integration_test.go index ad810ad92..81f35e39c 100644 --- a/tpl/images/integration_test.go +++ b/tpl/images/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Hugo Authors. All rights reserved. +// Copyright 2024 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. diff --git a/tpl/internal/go_templates/staticcheck.conf b/tpl/internal/go_templates/staticcheck.conf new file mode 100644 index 000000000..9cf5a78a4 --- /dev/null +++ b/tpl/internal/go_templates/staticcheck.conf @@ -0,0 +1 @@ +checks = ["none"]
\ No newline at end of file diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go index 78be55e18..4db40ce82 100644 --- a/tpl/internal/go_templates/texttemplate/hugo_template.go +++ b/tpl/internal/go_templates/texttemplate/hugo_template.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -59,23 +59,6 @@ func NewExecuter(helper ExecHelper) Executer { return &executer{helper: helper} } -type ( - pageContextKeyType string - hasLockContextKeyType string - stackContextKeyType string - callbackContextKeyType string -) - -const ( - // 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") - - // Used to pass down a callback function to nested templates. - CallbackContextKey = callbackContextKeyType("callback") -) - // Note: The context is currently not fully implemented 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 { diff --git a/tpl/internal/go_templates/texttemplate/hugo_template_test.go b/tpl/internal/go_templates/texttemplate/hugo_template_test.go index cc88151e3..c68b747dd 100644 --- a/tpl/internal/go_templates/texttemplate/hugo_template_test.go +++ b/tpl/internal/go_templates/texttemplate/hugo_template_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -56,7 +56,7 @@ func (e *execHelper) GetMapValue(ctx context.Context, tmpl Preparer, m, key refl return m.MapIndex(key), true } -func (e *execHelper) GetMethod(ctx context.Context, tmpl Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) { +func (e *execHelper) GetMethod(ctx context.Context, tmpl Preparer, receiver reflect.Value, name string) (reflect.Value, reflect.Value) { if name != "Hello1" { return zero, zero } diff --git a/tpl/internal/templatefuncsRegistry.go b/tpl/internal/templatefuncsRegistry.go index c1b01f5a5..fc02a6ef9 100644 --- a/tpl/internal/templatefuncsRegistry.go +++ b/tpl/internal/templatefuncsRegistry.go @@ -170,7 +170,7 @@ func (namespaces TemplateFuncsNamespaces) MarshalJSON() ([]byte, error) { for i, ns := range namespaces { - b, err := ns.toJSON(context.TODO()) + b, err := ns.toJSON(context.Background()) if err != nil { return nil, err } diff --git a/tpl/js/js.go b/tpl/js/js.go index bb8d20966..63a676532 100644 --- a/tpl/js/js.go +++ b/tpl/js/js.go @@ -34,7 +34,6 @@ func New(deps *deps.Deps) *Namespace { // Namespace provides template functions for the "js" namespace. type Namespace struct { - deps *deps.Deps client *js.Client } diff --git a/tpl/lang/lang_test.go b/tpl/lang/lang_test.go index 8d5430f6f..6ec40cab3 100644 --- a/tpl/lang/lang_test.go +++ b/tpl/lang/lang_test.go @@ -41,8 +41,8 @@ func TestNumFmt(t *testing.T) { {6, -12345.6789, "-|,| ", "|", "-12 345,678900"}, // Arabic, ar_AE - {6, -12345.6789, "- ٫ ٬", "", "-12٬345٫678900"}, - {6, -12345.6789, "-|٫| ", "|", "-12 345٫678900"}, + {6, -12345.6789, "\u200f- ٫ ٬", "", "\u200f-12٬345٫678900"}, + {6, -12345.6789, "\u200f-|٫| ", "|", "\u200f-12 345٫678900"}, } for _, cas := range cases { @@ -65,7 +65,6 @@ func TestNumFmt(t *testing.T) { } func TestFormatNumbers(t *testing.T) { - c := qt.New(t) nsNn := New(&deps.Deps{}, translators.GetTranslator("nn")) @@ -103,12 +102,10 @@ func TestFormatNumbers(t *testing.T) { c.Assert(err, qt.IsNil) c.Assert(got, qt.Equals, "$20,000.00") }) - } // Issue 9446 func TestLanguageKeyFormat(t *testing.T) { - c := qt.New(t) nsUnderscoreUpper := New(&deps.Deps{}, translators.GetTranslator("es_ES")) @@ -134,7 +131,5 @@ func TestLanguageKeyFormat(t *testing.T) { got, err = nsHyphenLower.FormatNumber(3, pi) c.Assert(err, qt.IsNil) c.Assert(got, qt.Equals, "3,142") - }) - } diff --git a/tpl/math/math_test.go b/tpl/math/math_test.go index 5b54b6ac8..4cde3fb85 100644 --- a/tpl/math/math_test.go +++ b/tpl/math/math_test.go @@ -335,7 +335,7 @@ func TestRound(t *testing.T) { {0.5, 1.0}, {1.1, 1.0}, {1.5, 2.0}, - {-0.1, -0.0}, + {-0.1, 0.0}, {-0.5, -1.0}, {-1.1, -1.0}, {-1.5, -2.0}, @@ -524,7 +524,6 @@ func TestSum(t *testing.T) { _, err := ns.Sum() c.Assert(err, qt.Not(qt.IsNil)) - } func TestProduct(t *testing.T) { @@ -547,5 +546,4 @@ func TestProduct(t *testing.T) { _, err := ns.Product() c.Assert(err, qt.Not(qt.IsNil)) - } diff --git a/tpl/openapi/openapi3/integration_test.go b/tpl/openapi/openapi3/integration_test.go index d3be0eda9..6914a60b3 100644 --- a/tpl/openapi/openapi3/integration_test.go +++ b/tpl/openapi/openapi3/integration_test.go @@ -67,7 +67,7 @@ API: {{ $api.Info.Title | safeHTML }} b.AssertFileContent("public/index.html", `API: Sample API`) b. - EditFileReplace("assets/api/myapi.yaml", func(s string) string { return strings.ReplaceAll(s, "Sample API", "Hugo API") }). + EditFileReplaceFunc("assets/api/myapi.yaml", func(s string) string { return strings.ReplaceAll(s, "Sample API", "Hugo API") }). Build() b.AssertFileContent("public/index.html", `API: Hugo API`) diff --git a/tpl/openapi/openapi3/openapi3.go b/tpl/openapi/openapi3/openapi3.go index 38857dd98..f929c7f62 100644 --- a/tpl/openapi/openapi3/openapi3.go +++ b/tpl/openapi/openapi3/openapi3.go @@ -15,44 +15,42 @@ package openapi3 import ( + "errors" "fmt" "io" gyaml "github.com/ghodss/yaml" - "errors" - kopenapi3 "github.com/getkin/kin-openapi/openapi3" - "github.com/gohugoio/hugo/cache/namedmemcache" + "github.com/gohugoio/hugo/cache/dynacache" "github.com/gohugoio/hugo/deps" + "github.com/gohugoio/hugo/identity" "github.com/gohugoio/hugo/parser/metadecoders" "github.com/gohugoio/hugo/resources/resource" ) // New returns a new instance of the openapi3-namespaced template functions. func New(deps *deps.Deps) *Namespace { - // TODO(bep) consolidate when merging that "other branch" -- but be aware of the keys. - cache := namedmemcache.New() - deps.BuildStartListeners.Add( - func() { - cache.Clear() - }) - return &Namespace{ - cache: cache, + cache: dynacache.GetOrCreatePartition[string, *OpenAPIDocument](deps.MemCache, "/tmpl/openapi3", dynacache.OptionsPartition{Weight: 30, ClearWhen: dynacache.ClearOnChange}), deps: deps, } } // Namespace provides template functions for the "openapi3". type Namespace struct { - cache *namedmemcache.Cache + cache *dynacache.Partition[string, *OpenAPIDocument] deps *deps.Deps } // OpenAPIDocument represents an OpenAPI 3 document. type OpenAPIDocument struct { *kopenapi3.T + identityGroup identity.Identity +} + +func (o *OpenAPIDocument) GetIdentityGroup() identity.Identity { + return o.identityGroup } // Unmarshal unmarshals the given resource into an OpenAPI 3 document. @@ -62,7 +60,7 @@ func (ns *Namespace) Unmarshal(r resource.UnmarshableResource) (*OpenAPIDocument return nil, errors.New("no Key set in Resource") } - v, err := ns.cache.GetOrCreate(key, func() (any, error) { + v, err := ns.cache.GetOrCreate(key, func(string) (*OpenAPIDocument, error) { f := metadecoders.FormatFromStrings(r.MediaType().Suffixes()...) if f == "" { return nil, fmt.Errorf("MIME %q not supported", r.MediaType()) @@ -92,11 +90,11 @@ func (ns *Namespace) Unmarshal(r resource.UnmarshableResource) (*OpenAPIDocument err = kopenapi3.NewLoader().ResolveRefsIn(s, nil) - return &OpenAPIDocument{T: s}, err + return &OpenAPIDocument{T: s, identityGroup: identity.FirstIdentity(r)}, err }) if err != nil { return nil, err } - return v.(*OpenAPIDocument), nil + return v, nil } diff --git a/tpl/os/integration_test.go b/tpl/os/integration_test.go index d08374f8f..58e0ef70a 100644 --- a/tpl/os/integration_test.go +++ b/tpl/os/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. diff --git a/tpl/page/init.go b/tpl/page/init.go index 52aeaafd6..826aa45d3 100644 --- a/tpl/page/init.go +++ b/tpl/page/init.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -32,7 +32,7 @@ func init() { ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func(ctx context.Context, args ...interface{}) (interface{}, error) { - v := tpl.GetPageFromContext(ctx) + v := tpl.Context.Page.Get(ctx) if v == nil { // The multilingual sitemap does not have a page as its context. return nil, nil diff --git a/tpl/page/integration_test.go b/tpl/page/integration_test.go index 74788377d..632c3b64e 100644 --- a/tpl/page/integration_test.go +++ b/tpl/page/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -112,11 +112,11 @@ Bundled page: {{ $p2_1.Content }} -- 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 }} +{{ 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 }} +{{ with page }}SitemapIndex OK: {{ .Kind }}{{ else }}Failed.{{ end }} ` @@ -167,15 +167,12 @@ Shortcode in bundled page 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.`) + b.AssertFileContent("public/sitemap.xml", `SitemapIndex OK: sitemapindex`) } else { b.AssertFileContent("public/sitemap.xml", `Sitemap OK.`) } - }) - } - } // Issue 10791. @@ -207,5 +204,23 @@ title: "P1" ).Build() b.AssertFileContent("public/p1/index.html", "<nav id=\"TableOfContents\"></nav> \n<h1 id=\"heading-1\">Heading 1</h1>") +} + +func TestFromStringRunning(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableLiveReload = true +-- layouts/index.html -- +{{ with resources.FromString "foo" "{{ seq 3 }}" }} +{{ with resources.ExecuteAsTemplate "bar" $ . }} + {{ .Content | safeHTML }} +{{ end }} +{{ end }} + ` + + b := hugolib.TestRunning(t, files) + b.AssertFileContent("public/index.html", "1\n2\n3") } diff --git a/tpl/partials/integration_test.go b/tpl/partials/integration_test.go index 3dbaf2ce4..e48f3bb20 100644 --- a/tpl/partials/integration_test.go +++ b/tpl/partials/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -297,7 +297,6 @@ timeout = '200ms' b.Assert(err, qt.Not(qt.IsNil)) b.Assert(err.Error(), qt.Contains, "timed out") - } func TestIncludeCachedTimeout(t *testing.T) { @@ -322,7 +321,6 @@ timeout = '200ms' b.Assert(err, qt.Not(qt.IsNil)) b.Assert(err.Error(), qt.Contains, "timed out") - } // See Issue #10789 @@ -350,5 +348,4 @@ BAR ).Build() b.AssertFileContent("public/index.html", "OO:BAR") - } diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go index 3834529ce..8e36e21b9 100644 --- a/tpl/partials/partials.go +++ b/tpl/partials/partials.go @@ -40,9 +40,10 @@ type partialCacheKey struct { Variants []any } type includeResult struct { - name string - result any - err error + name string + result any + mangager identity.Manager + err error } func (k partialCacheKey) Key() string { @@ -65,7 +66,7 @@ type partialCache struct { } func (p *partialCache) clear() { - p.cache.DeleteFunc(func(string, includeResult) bool { + p.cache.DeleteFunc(func(s string, r includeResult) bool { return true }) } @@ -75,7 +76,7 @@ func New(deps *deps.Deps) *Namespace { // This lazycache was introduced in Hugo 0.111.0. // We're going to expand and consolidate all memory caches in Hugo using this, // so just set a high limit for now. - lru := lazycache.New[string, includeResult](lazycache.Options{MaxEntries: 1000}) + lru := lazycache.New(lazycache.Options[string, includeResult]{MaxEntries: 1000}) cache := &partialCache{cache: lru} deps.BuildStartListeners.Add( @@ -142,11 +143,11 @@ func (ns *Namespace) includWithTimeout(ctx context.Context, name string, dataLis case <-timeoutCtx.Done(): err := timeoutCtx.Err() if err == context.DeadlineExceeded { + //lint:ignore ST1005 end user message. err = fmt.Errorf("partial %q timed out after %s. This is most likely due to infinite recursion. If this is just a slow template, you can try to increase the 'timeout' config setting.", name, ns.deps.Conf.Timeout()) } return includeResult{err: err} } - } // include is a helper function that lookups and executes the named partial. @@ -215,7 +216,6 @@ func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) name: templ.Name(), result: result, } - } // IncludeCached executes and caches partial templates. The cache is created with name+variants as the key. @@ -226,12 +226,22 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any Name: name, Variants: variants, } + depsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx) r, found, err := ns.cachedPartials.cache.GetOrCreate(key.Key(), func(string) (includeResult, error) { + var depsManagerShared identity.Manager + if ns.deps.Conf.Watching() { + // We need to create a shared dependency manager to pass downwards + // and add those same dependencies to any cached invocation of this partial. + depsManagerShared = identity.NewManager("partials") + ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, depsManagerShared.(identity.DependencyManagerScopedProvider)) + } r := ns.includWithTimeout(ctx, key.Name, context) + if ns.deps.Conf.Watching() { + r.mangager = depsManagerShared + } return r, r.err }) - if err != nil { return nil, err } @@ -242,10 +252,13 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any // We need to track the time spent in the cache to // get the totals correct. ns.deps.Metrics.MeasureSince(key.templateName(), start) - } ns.deps.Metrics.TrackValue(key.templateName(), r.result, found) } + if r.mangager != nil && depsManagerIn != nil { + depsManagerIn.AddIdentity(r.mangager) + } + return r.result, nil } diff --git a/tpl/reflect/reflect_test.go b/tpl/reflect/reflect_test.go index f85af87dd..84ffe813b 100644 --- a/tpl/reflect/reflect_test.go +++ b/tpl/reflect/reflect_test.go @@ -21,8 +21,6 @@ import ( var ns = New() -type tstNoStringer struct{} - func TestIsMap(t *testing.T) { c := qt.New(t) for _, test := range []struct { diff --git a/tpl/resources/integration_test.go b/tpl/resources/integration_test.go index 0e0a29a98..02aa5d29d 100644 --- a/tpl/resources/integration_test.go +++ b/tpl/resources/integration_test.go @@ -72,10 +72,9 @@ Copy3: /blog/js/copies/moo.a677329fc6c4ad947e0c7116d91f37a2.min.js|text/javascri `) - b.AssertDestinationExists("images/copy2.png", true) + b.AssertFileExists("public/images/copy2.png", true) // No permalink used. - b.AssertDestinationExists("images/copy3.png", false) - + b.AssertFileExists("public/images/copy3.png", false) } func TestCopyPageShouldFail(t *testing.T) { @@ -96,7 +95,6 @@ func TestCopyPageShouldFail(t *testing.T) { }).BuildE() b.Assert(err, qt.IsNotNil) - } func TestGet(t *testing.T) { @@ -125,5 +123,4 @@ Image OK Empty string not found `) - } diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go index d18797ebc..04af756ef 100644 --- a/tpl/resources/resources.go +++ b/tpl/resources/resources.go @@ -16,16 +16,15 @@ package resources import ( "context" + "errors" "fmt" "sync" - "errors" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/tpl/internal/resourcehelpers" - "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/resources/postpub" "github.com/gohugoio/hugo/deps" @@ -104,7 +103,6 @@ func (ns *Namespace) getscssClientDartSass() (*dartsass.Client, error) { return } ns.deps.BuildClosers.Add(ns.scssClientDartSass) - }) return ns.scssClientDartSass, err @@ -122,7 +120,6 @@ func (ns *Namespace) Copy(s any, r resource.Resource) (resource.Resource, error) // Get locates the filename given in Hugo's assets filesystem // and creates a Resource object that can be used for further transformations. func (ns *Namespace) Get(filename any) resource.Resource { - filenamestr, err := cast.ToStringE(filename) if err != nil { panic(err) @@ -172,7 +169,6 @@ func (ns *Namespace) GetRemote(args ...any) resource.Resource { } return ns.createClient.FromRemote(urlstr, options) - } r, err := get(args...) @@ -183,10 +179,8 @@ func (ns *Namespace) GetRemote(args ...any) resource.Resource { default: return resources.NewErrorResource(resource.NewResourceError(fmt.Errorf("error calling resources.GetRemote: %w", err), make(map[string]any))) } - } return r - } // GetMatch finds the first Resource matching the given pattern, or nil if none found. @@ -344,7 +338,6 @@ func (ns *Namespace) Minify(r resources.ResourceTransformer) (resource.Resource, // as second argument. As an option, you can e.g. specify e.g. the target path (string) // for the converted CSS resource. func (ns *Namespace) ToCSS(args ...any) (resource.Resource, error) { - if len(args) > 2 { return nil, errors.New("must not provide more arguments than resource object and options") } @@ -389,7 +382,7 @@ func (ns *Namespace) ToCSS(args ...any) (resource.Resource, error) { if transpiler == transpilerLibSass { var options scss.Options if targetPath != "" { - options.TargetPath = helpers.ToSlashTrimLeading(targetPath) + options.TargetPath = paths.ToSlashTrimLeading(targetPath) } else if m != nil { options, err = scss.DecodeOptions(m) if err != nil { @@ -413,12 +406,10 @@ func (ns *Namespace) ToCSS(args ...any) (resource.Resource, error) { } return client.ToCSS(r, m) - } // PostCSS processes the given Resource with PostCSS func (ns *Namespace) PostCSS(args ...any) (resource.Resource, error) { - if len(args) > 2 { return nil, errors.New("must not provide more arguments than resource object and options") } @@ -438,7 +429,6 @@ func (ns *Namespace) PostProcess(r resource.Resource) (postpub.PostPublishedReso // Babel processes the given Resource with Babel. func (ns *Namespace) Babel(args ...any) (resource.Resource, error) { - if len(args) > 2 { return nil, errors.New("must not provide more arguments than resource object and options") } diff --git a/tpl/safe/init.go b/tpl/safe/init.go index 8fc0e82ea..3b498e6df 100644 --- a/tpl/safe/init.go +++ b/tpl/safe/init.go @@ -70,11 +70,6 @@ func init() { }, ) - ns.AddMethodMapping(ctx.SanitizeURL, - []string{"sanitizeURL", "sanitizeurl"}, - [][2]string{}, - ) - return ns } diff --git a/tpl/safe/safe.go b/tpl/safe/safe.go index d1a2e8d4e..81b4e0480 100644 --- a/tpl/safe/safe.go +++ b/tpl/safe/safe.go @@ -18,7 +18,6 @@ package safe import ( "html/template" - "github.com/gohugoio/hugo/helpers" "github.com/spf13/cast" ) @@ -65,9 +64,3 @@ func (ns *Namespace) URL(s any) (template.URL, error) { ss, err := cast.ToStringE(s) return template.URL(ss), err } - -// SanitizeURL returns the string s as html/template URL content. -func (ns *Namespace) SanitizeURL(s any) (string, error) { - ss, err := cast.ToStringE(s) - return helpers.SanitizeURL(ss), err -} diff --git a/tpl/safe/safe_test.go b/tpl/safe/safe_test.go index 81fa40fd8..f2a54755d 100644 --- a/tpl/safe/safe_test.go +++ b/tpl/safe/safe_test.go @@ -182,30 +182,3 @@ func TestURL(t *testing.T) { c.Assert(result, qt.Equals, test.expect) } } - -func TestSanitizeURL(t *testing.T) { - t.Parallel() - c := qt.New(t) - - ns := New() - - for _, test := range []struct { - a any - expect any - }{ - {"http://foo/../../bar", "http://foo/bar"}, - // errors - {tstNoStringer{}, false}, - } { - - result, err := ns.SanitizeURL(test.a) - - if b, ok := test.expect.(bool); ok && !b { - c.Assert(err, qt.Not(qt.IsNil)) - continue - } - - c.Assert(err, qt.IsNil) - c.Assert(result, qt.Equals, test.expect) - } -} diff --git a/tpl/site/init.go b/tpl/site/init.go index 1c018e14e..1fcb309a0 100644 --- a/tpl/site/init.go +++ b/tpl/site/init.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Hugo Authors. All rights reserved. +// Copyright 2024 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. diff --git a/tpl/strings/strings.go b/tpl/strings/strings.go index 9f16f1581..cd233b0a4 100644 --- a/tpl/strings/strings.go +++ b/tpl/strings/strings.go @@ -47,7 +47,7 @@ type Namespace struct { func (ns *Namespace) CountRunes(s any) (int, error) { ss, err := cast.ToStringE(s) if err != nil { - return 0, fmt.Errorf("Failed to convert content to string: %w", err) + return 0, fmt.Errorf("failed to convert content to string: %w", err) } counter := 0 @@ -64,7 +64,7 @@ func (ns *Namespace) CountRunes(s any) (int, error) { func (ns *Namespace) RuneCount(s any) (int, error) { ss, err := cast.ToStringE(s) if err != nil { - return 0, fmt.Errorf("Failed to convert content to string: %w", err) + return 0, fmt.Errorf("failed to convert content to string: %w", err) } return utf8.RuneCountInString(ss), nil } @@ -73,12 +73,12 @@ func (ns *Namespace) RuneCount(s any) (int, error) { func (ns *Namespace) CountWords(s any) (int, error) { ss, err := cast.ToStringE(s) if err != nil { - return 0, fmt.Errorf("Failed to convert content to string: %w", err) + return 0, fmt.Errorf("failed to convert content to string: %w", err) } isCJKLanguage, err := regexp.MatchString(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`, ss) if err != nil { - return 0, fmt.Errorf("Failed to match regex pattern against string: %w", err) + return 0, fmt.Errorf("failed to match regex pattern against string: %w", err) } if !isCJKLanguage { @@ -103,11 +103,11 @@ func (ns *Namespace) CountWords(s any) (int, error) { func (ns *Namespace) Count(substr, s any) (int, error) { substrs, err := cast.ToStringE(substr) if err != nil { - return 0, fmt.Errorf("Failed to convert substr to string: %w", err) + return 0, fmt.Errorf("failed to convert substr to string: %w", err) } ss, err := cast.ToStringE(s) if err != nil { - return 0, fmt.Errorf("Failed to convert s to string: %w", err) + return 0, fmt.Errorf("failed to convert s to string: %w", err) } return strings.Count(ss, substrs), nil } diff --git a/tpl/template.go b/tpl/template.go index 1f0127c66..e9725bd74 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -23,6 +23,8 @@ import ( "unicode" bp "github.com/gohugoio/hugo/bufferpool" + "github.com/gohugoio/hugo/common/hcontext" + "github.com/gohugoio/hugo/identity" "github.com/gohugoio/hugo/output/layouts" "github.com/gohugoio/hugo/output" @@ -69,6 +71,7 @@ type TemplateHandler interface { ExecuteWithContext(ctx context.Context, t Template, wr io.Writer, data any) error LookupLayout(d layouts.LayoutDescriptor, f output.Format) (Template, bool, error) HasTemplate(name string) bool + GetIdentity(name string) (identity.Identity, bool) } type TemplateLookup interface { @@ -95,6 +98,27 @@ type Template interface { Prepare() (*texttemplate.Template, error) } +// AddIdentity checks if t is an identity.Identity and returns it if so. +// Else it wraps it in a templateIdentity using its name as the base. +func AddIdentity(t Template) Template { + if _, ok := t.(identity.IdentityProvider); ok { + return t + } + return templateIdentityProvider{ + Template: t, + id: identity.StringIdentity(t.Name()), + } +} + +type templateIdentityProvider struct { + Template + id identity.Identity +} + +func (t templateIdentityProvider) GetIdentity() identity.Identity { + return t.id +} + // TemplateParser is used to parse ad-hoc templates, e.g. in the Resource chain. type TemplateParser interface { Parse(name, tpl string) (Template, error) @@ -111,18 +135,6 @@ type TemplateDebugger interface { Debug() } -// templateInfo wraps a Template with some additional information. -type templateInfo struct { - Template - Info -} - -// templateInfo wraps a Template with some additional information. -type templateInfoManager struct { - Template - InfoManager -} - // TemplatesProvider as implemented by deps.Deps. type TemplatesProvider interface { Tmpl() TemplateHandler @@ -144,34 +156,38 @@ type TemplateFuncGetter interface { GetFunc(name string) (reflect.Value, bool) } -// GetPageFromContext returns the top level Page. -func GetPageFromContext(ctx context.Context) any { - return ctx.Value(texttemplate.PageContextKey) +type contextKey string + +// Context manages values passed in the context to templates. +var Context = struct { + DependencyManagerScopedProvider hcontext.ContextDispatcher[identity.DependencyManagerScopedProvider] + GetDependencyManagerInCurrentScope func(context.Context) identity.Manager + SetDependencyManagerInCurrentScope func(context.Context, identity.Manager) context.Context + DependencyScope hcontext.ContextDispatcher[int] + Page hcontext.ContextDispatcher[page] +}{ + DependencyManagerScopedProvider: hcontext.NewContextDispatcher[identity.DependencyManagerScopedProvider](contextKey("DependencyManagerScopedProvider")), + DependencyScope: hcontext.NewContextDispatcher[int](contextKey("DependencyScope")), + Page: hcontext.NewContextDispatcher[page](contextKey("Page")), } -// SetPageInContext sets the top level Page. -func SetPageInContext(ctx context.Context, p page) context.Context { - return context.WithValue(ctx, texttemplate.PageContextKey, p) +func init() { + Context.GetDependencyManagerInCurrentScope = func(ctx context.Context) identity.Manager { + idmsp := Context.DependencyManagerScopedProvider.Get(ctx) + if idmsp != nil { + return idmsp.GetDependencyManagerForScope(Context.DependencyScope.Get(ctx)) + } + return nil + } } type page interface { IsNode() bool } -func GetCallbackFunctionFromContext(ctx context.Context) any { - return ctx.Value(texttemplate.CallbackContextKey) -} - -func SetCallbackFunctionInContext(ctx context.Context, fn any) context.Context { - return context.WithValue(ctx, texttemplate.CallbackContextKey, fn) -} - const hugoNewLinePlaceholder = "___hugonl_" -var ( - stripHTMLReplacerPre = strings.NewReplacer("\n", " ", "</p>", hugoNewLinePlaceholder, "<br>", hugoNewLinePlaceholder, "<br />", hugoNewLinePlaceholder) - whitespaceRe = regexp.MustCompile(`\s+`) -) +var stripHTMLReplacerPre = strings.NewReplacer("\n", " ", "</p>", hugoNewLinePlaceholder, "<br>", hugoNewLinePlaceholder, "<br />", hugoNewLinePlaceholder) // StripHTML strips out all HTML tags in s. func StripHTML(s string) string { diff --git a/tpl/template_info.go b/tpl/template_info.go index 5f748d682..b27debf1f 100644 --- a/tpl/template_info.go +++ b/tpl/template_info.go @@ -13,18 +13,11 @@ package tpl -import ( - "github.com/gohugoio/hugo/identity" -) - // Increments on breaking changes. const TemplateVersion = 2 type Info interface { ParseInfo() ParseInfo - - // Identifies this template and its dependencies. - identity.Provider } type FileInfo interface { @@ -32,13 +25,6 @@ type FileInfo interface { Filename() string } -type InfoManager interface { - ParseInfo() ParseInfo - - // Identifies and manages this template and its dependencies. - identity.Manager -} - type ParseInfo struct { // Set for shortcode templates with any {{ .Inner }} IsInner bool diff --git a/tpl/template_test.go b/tpl/template_test.go index d989b7158..333513a3d 100644 --- a/tpl/template_test.go +++ b/tpl/template_test.go @@ -67,5 +67,3 @@ More text here.</p> } } } - -const tstHTMLContent = "<!DOCTYPE html><html><head><script src=\"http://two/foobar.js\"></script></head><body><nav><ul><li hugo-nav=\"section_0\"></li><li hugo-nav=\"section_1\"></li></ul></nav><article>content <a href=\"http://two/foobar\">foobar</a>. Follow up</article><p>This is some text.<br>And some more.</p></body></html>" diff --git a/tpl/templates/integration_test.go b/tpl/templates/integration_test.go index 7935fa5e3..7e0bcc824 100644 --- a/tpl/templates/integration_test.go +++ b/tpl/templates/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Hugo Authors. All rights reserved. +// Copyright 2024 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. diff --git a/tpl/time/init.go b/tpl/time/init.go index 01783270f..5f9dd77bf 100644 --- a/tpl/time/init.go +++ b/tpl/time/init.go @@ -51,7 +51,7 @@ func init() { // 3 or more arguments. Currently not supported. default: - return nil, errors.New("Invalid arguments supplied to `time`. Refer to time documentation: https://gohugo.io/functions/time/") + return nil, errors.New("invalid arguments supplied to `time`") } }, } diff --git a/tpl/time/time.go b/tpl/time/time.go index cd78b83aa..57b115f35 100644 --- a/tpl/time/time.go +++ b/tpl/time/time.go @@ -17,7 +17,6 @@ package time import ( "fmt" "time" - _time "time" "github.com/gohugoio/hugo/common/htime" @@ -47,14 +46,13 @@ func (ns *Namespace) AsTime(v any, args ...any) (any, error) { if err != nil { return nil, err } - loc, err = _time.LoadLocation(locStr) + loc, err = time.LoadLocation(locStr) if err != nil { return nil, err } } return htime.ToTimeInDefaultLocationE(v, loc) - } // Format converts the textual representation of the datetime string in v into @@ -69,7 +67,7 @@ func (ns *Namespace) Format(layout string, v any) (string, error) { } // Now returns the current local time or `clock` time -func (ns *Namespace) Now() _time.Time { +func (ns *Namespace) Now() time.Time { return htime.Now() } @@ -79,34 +77,34 @@ func (ns *Namespace) Now() _time.Time { // such as "300ms", "-1.5h" or "2h45m". // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". // See https://golang.org/pkg/time/#ParseDuration -func (ns *Namespace) ParseDuration(s any) (_time.Duration, error) { +func (ns *Namespace) ParseDuration(s any) (time.Duration, error) { ss, err := cast.ToStringE(s) if err != nil { return 0, err } - return _time.ParseDuration(ss) + return time.ParseDuration(ss) } -var durationUnits = map[string]_time.Duration{ - "nanosecond": _time.Nanosecond, - "ns": _time.Nanosecond, - "microsecond": _time.Microsecond, - "us": _time.Microsecond, - "µs": _time.Microsecond, - "millisecond": _time.Millisecond, - "ms": _time.Millisecond, - "second": _time.Second, - "s": _time.Second, - "minute": _time.Minute, - "m": _time.Minute, - "hour": _time.Hour, - "h": _time.Hour, +var durationUnits = map[string]time.Duration{ + "nanosecond": time.Nanosecond, + "ns": time.Nanosecond, + "microsecond": time.Microsecond, + "us": time.Microsecond, + "µs": time.Microsecond, + "millisecond": time.Millisecond, + "ms": time.Millisecond, + "second": time.Second, + "s": time.Second, + "minute": time.Minute, + "m": time.Minute, + "hour": time.Hour, + "h": time.Hour, } // Duration converts the given number to a time.Duration. // Unit is one of nanosecond/ns, microsecond/us/µs, millisecond/ms, second/s, minute/m or hour/h. -func (ns *Namespace) Duration(unit any, number any) (_time.Duration, error) { +func (ns *Namespace) Duration(unit any, number any) (time.Duration, error) { unitStr, err := cast.ToStringE(unit) if err != nil { return 0, err @@ -119,5 +117,5 @@ func (ns *Namespace) Duration(unit any, number any) (_time.Duration, error) { if err != nil { return 0, err } - return _time.Duration(n) * unitDuration, nil + return time.Duration(n) * unitDuration, nil } diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go index 053b53b53..a8ba6815d 100644 --- a/tpl/tplimpl/template.go +++ b/tpl/tplimpl/template.go @@ -42,7 +42,6 @@ import ( "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/hugofs" - "github.com/gohugoio/hugo/hugofs/files" htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" @@ -121,10 +120,6 @@ func needsBaseTemplate(templ string) bool { return baseTemplateDefineRe.MatchString(templ[idx:]) } -func newIdentity(name string) identity.Manager { - return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)) -} - func newStandaloneTextTemplate(funcs map[string]any) tpl.TemplateParseFinder { return &textTemplateWrapperWithLock{ RWMutex: &sync.RWMutex{}, @@ -147,7 +142,6 @@ func newTemplateHandlers(d *deps.Deps) (*tpl.TemplateHandlers, error) { h := &templateHandler{ nameBaseTemplateName: make(map[string]string), transformNotFound: make(map[string]*templateState), - identityNotFound: make(map[string][]identity.Manager), shortcodes: make(map[string]*shortcodeTemplates), templateInfo: make(map[string]tpl.Info), @@ -187,7 +181,6 @@ func newTemplateHandlers(d *deps.Deps) (*tpl.TemplateHandlers, error) { Tmpl: e, TxtTmpl: newStandaloneTextTemplate(funcMap), }, nil - } func newTemplateNamespace(funcs map[string]any) *templateNamespace { @@ -200,13 +193,16 @@ func newTemplateNamespace(funcs map[string]any) *templateNamespace { } } -func newTemplateState(templ tpl.Template, info templateInfo) *templateState { +func newTemplateState(templ tpl.Template, info templateInfo, id identity.Identity) *templateState { + if id == nil { + id = info + } return &templateState{ info: info, typ: info.resolveType(), Template: templ, - Manager: newIdentity(info.name), parseInfo: tpl.DefaultParseInfo, + id: id, } } @@ -288,7 +284,7 @@ func (t *templateExec) UnusedTemplates() []tpl.FileInfo { for _, ts := range t.main.templates { ti := ts.info - if strings.HasPrefix(ti.name, "_internal/") || ti.realFilename == "" { + if strings.HasPrefix(ti.name, "_internal/") || ti.meta == nil { continue } @@ -346,9 +342,6 @@ type templateHandler struct { // AST transformation pass. transformNotFound map[string]*templateState - // Holds identities of templates not found during first pass. - identityNotFound map[string][]identity.Manager - // shortcodes maps shortcode name to template variants // (language, output format etc.) of that shortcode. shortcodes map[string]*shortcodeTemplates @@ -405,7 +398,6 @@ func (t *templateHandler) LookupLayout(d layouts.LayoutDescriptor, f output.Form cacheVal := layoutCacheEntry{found: found, templ: templ, err: err} t.layoutTemplateCache[key] = cacheVal return cacheVal.templ, cacheVal.found, cacheVal.err - } // This currently only applies to shortcodes and what we get here is the @@ -456,6 +448,22 @@ func (t *templateHandler) HasTemplate(name string) bool { return found } +func (t *templateHandler) GetIdentity(name string) (identity.Identity, bool) { + if _, found := t.needsBaseof[name]; found { + return identity.StringIdentity(name), true + } + + if _, found := t.baseof[name]; found { + return identity.StringIdentity(name), true + } + + tt, found := t.Lookup(name) + if !found { + return nil, false + } + return tt.(identity.IdentityProvider).GetIdentity(), found +} + func (t *templateHandler) findLayout(d layouts.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) { d.OutputFormatName = f.Name d.Suffix = f.MediaType.FirstSuffix.Suffix @@ -488,13 +496,10 @@ func (t *templateHandler) findLayout(d layouts.LayoutDescriptor, f output.Format return nil, false, err } - ts := newTemplateState(templ, overlay) + ts := newTemplateState(templ, overlay, identity.Or(base, overlay)) if found { ts.baseInfo = base - - // Add the base identity to detect changes - ts.Add(identity.NewPathIdentity(files.ComponentFolderLayouts, base.name)) } t.applyTemplateTransformers(t.main, ts) @@ -510,13 +515,6 @@ func (t *templateHandler) findLayout(d layouts.LayoutDescriptor, f output.Format return nil, false, nil } -func (t *templateHandler) findTemplate(name string) *templateState { - if templ, found := t.Lookup(name); found { - return templ.(*templateState) - } - return nil -} - func (t *templateHandler) newTemplateInfo(name, tpl string) templateInfo { var isText bool name, isText = t.nameIsText(name) @@ -539,9 +537,8 @@ func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error identifiers := t.extractIdentifiers(inerr.Error()) - //lint:ignore ST1008 the error is the main result checkFilename := func(info templateInfo, inErr error) (error, bool) { - if info.filename == "" { + if info.meta == nil { return inErr, false } @@ -560,13 +557,13 @@ func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error return -1 } - f, err := t.layoutsFs.Open(info.filename) + f, err := info.meta.Open() if err != nil { return inErr, false } defer f.Close() - fe := herrors.NewFileErrorFromName(inErr, info.realFilename) + fe := herrors.NewFileErrorFromName(inErr, info.meta.Filename) fe.UpdateContent(f, lineMatcher) if !fe.ErrorContext().Position.IsValid() { @@ -621,37 +618,33 @@ func (t *templateHandler) addShortcodeVariant(ts *templateState) { } } -func (t *templateHandler) addTemplateFile(name, path string) error { - getTemplate := func(filename string) (templateInfo, error) { - fs := t.Layouts.Fs - b, err := afero.ReadFile(fs, filename) +func (t *templateHandler) addTemplateFile(name string, fim hugofs.FileMetaInfo) error { + getTemplate := func(fim hugofs.FileMetaInfo) (templateInfo, error) { + meta := fim.Meta() + f, err := meta.Open() if err != nil { - return templateInfo{filename: filename, fs: fs}, err + return templateInfo{meta: meta}, err + } + defer f.Close() + b, err := io.ReadAll(f) + if err != nil { + return templateInfo{meta: meta}, err } s := removeLeadingBOM(string(b)) - realFilename := filename - if fi, err := fs.Stat(filename); err == nil { - if fim, ok := fi.(hugofs.FileMetaInfo); ok { - realFilename = fim.Meta().Filename - } - } - var isText bool name, isText = t.nameIsText(name) return templateInfo{ - name: name, - isText: isText, - template: s, - filename: filename, - realFilename: realFilename, - fs: fs, + name: name, + isText: isText, + template: s, + meta: meta, }, nil } - tinfo, err := getTemplate(path) + tinfo, err := getTemplate(fim) if err != nil { return err } @@ -741,11 +734,6 @@ func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *t for k := range c.templateNotFound { t.transformNotFound[k] = ts - t.identityNotFound[k] = append(t.identityNotFound[k], c.t) - } - - for k := range c.identityNotFound { - t.identityNotFound[k] = append(t.identityNotFound[k], c.t) } return c, err @@ -804,9 +792,9 @@ func (t *templateHandler) loadEmbedded() error { } func (t *templateHandler) loadTemplates() error { - walker := func(path string, fi hugofs.FileMetaInfo, err error) error { - if err != nil || fi.IsDir() { - return err + walker := func(path string, fi hugofs.FileMetaInfo) error { + if fi.IsDir() { + return nil } if isDotFile(path) || isBackupFile(path) { @@ -822,14 +810,14 @@ func (t *templateHandler) loadTemplates() error { name = textTmplNamePrefix + name } - if err := t.addTemplateFile(name, path); err != nil { + if err := t.addTemplateFile(name, fi); err != nil { return err } return nil } - if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil { + if err := helpers.Walk(t.Layouts.Fs, "", walker); err != nil { if !herrors.IsNotExist(err) { return err } @@ -861,7 +849,7 @@ func (t *templateHandler) extractPartials(templ tpl.Template) error { continue } - ts := newTemplateState(templ, templateInfo{name: templ.Name()}) + ts := newTemplateState(templ, templateInfo{name: templ.Name()}, nil) ts.typ = templatePartial t.main.mu.RLock() @@ -927,15 +915,6 @@ func (t *templateHandler) postTransform() error { } } - for k, v := range t.identityNotFound { - ts := t.findTemplate(k) - if ts != nil { - for _, im := range v { - im.Add(ts) - } - } - } - for _, v := range t.shortcodes { sort.Slice(v.variants, func(i, j int) bool { v1, v2 := v.variants[i], v.variants[j] @@ -1008,7 +987,7 @@ func (t *templateNamespace) newTemplateLookup(in *templateState) func(name strin return templ } if templ, found := findTemplateIn(name, in); found { - return newTemplateState(templ, templateInfo{name: templ.Name()}) + return newTemplateState(templ, templateInfo{name: templ.Name()}, nil) } return nil } @@ -1026,7 +1005,7 @@ func (t *templateNamespace) parse(info templateInfo) (*templateState, error) { return nil, err } - ts := newTemplateState(templ, info) + ts := newTemplateState(templ, info, nil) t.templates[info.name] = ts @@ -1040,7 +1019,7 @@ func (t *templateNamespace) parse(info templateInfo) (*templateState, error) { return nil, err } - ts := newTemplateState(templ, info) + ts := newTemplateState(templ, info, nil) t.templates[info.name] = ts @@ -1052,12 +1031,16 @@ type templateState struct { typ templateType parseInfo tpl.ParseInfo - identity.Manager + id identity.Identity info templateInfo baseInfo templateInfo // Set when a base template is used. } +func (t *templateState) GetIdentity() identity.Identity { + return t.id +} + func (t *templateState) ParseInfo() tpl.ParseInfo { return t.parseInfo } @@ -1066,6 +1049,10 @@ func (t *templateState) isText() bool { return isText(t.Template) } +func (t *templateState) String() string { + return t.Name() +} + func isText(templ tpl.Template) bool { _, isText := templ.(*texttemplate.Template) return isText @@ -1076,11 +1063,6 @@ type templateStateMap struct { templates map[string]*templateState } -type templateWrapperWithLock struct { - *sync.RWMutex - tpl.Template -} - type textTemplateWrapperWithLock struct { *sync.RWMutex *texttemplate.Template diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go index 8d5d8d1b3..92558a903 100644 --- a/tpl/tplimpl/template_ast_transformers.go +++ b/tpl/tplimpl/template_ast_transformers.go @@ -14,17 +14,14 @@ package tplimpl import ( + "errors" "fmt" - "regexp" - "strings" htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse" - "errors" - "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/tpl" "github.com/mitchellh/mapstructure" @@ -41,7 +38,6 @@ const ( type templateContext struct { visited map[string]bool templateNotFound map[string]bool - identityNotFound map[string]bool lookupFn func(name string) *templateState // The last error encountered. @@ -74,19 +70,20 @@ func (c templateContext) getIfNotVisited(name string) *templateState { func newTemplateContext( t *templateState, - lookupFn func(name string) *templateState) *templateContext { + lookupFn func(name string) *templateState, +) *templateContext { return &templateContext{ t: t, lookupFn: lookupFn, visited: make(map[string]bool), templateNotFound: make(map[string]bool), - identityNotFound: make(map[string]bool), } } func applyTemplateTransformers( t *templateState, - lookupFn func(name string) *templateState) (*templateContext, error) { + lookupFn func(name string) *templateState, +) (*templateContext, error) { if t == nil { return nil, errors.New("expected template, but none provided") } @@ -179,7 +176,6 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) { } case *parse.CommandNode: - c.collectPartialInfo(x) c.collectInner(x) keep := c.collectReturnNode(x) @@ -280,39 +276,6 @@ func (c *templateContext) collectInner(n *parse.CommandNode) { } } -var partialRe = regexp.MustCompile(`^partial(Cached)?$|^partials\.Include(Cached)?$`) - -func (c *templateContext) collectPartialInfo(x *parse.CommandNode) { - if len(x.Args) < 2 { - return - } - - first := x.Args[0] - var id string - switch v := first.(type) { - case *parse.IdentifierNode: - id = v.Ident - case *parse.ChainNode: - id = v.String() - } - - if partialRe.MatchString(id) { - partialName := strings.Trim(x.Args[1].String(), "\"") - if !strings.Contains(partialName, ".") { - partialName += ".html" - } - partialName = "partials/" + partialName - info := c.lookupFn(partialName) - - if info != nil { - c.t.Add(info) - } else { - // Delay for later - c.identityNotFound[partialName] = true - } - } -} - func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool { if c.t.typ != templatePartial || c.returnNode != nil { return true diff --git a/tpl/tplimpl/template_ast_transformers_test.go b/tpl/tplimpl/template_ast_transformers_test.go index 90ca325ab..bd889b832 100644 --- a/tpl/tplimpl/template_ast_transformers_test.go +++ b/tpl/tplimpl/template_ast_transformers_test.go @@ -52,6 +52,7 @@ func newTestTemplate(templ tpl.Template) *templateState { templateInfo{ name: templ.Name(), }, + nil, ) } diff --git a/tpl/tplimpl/template_errors.go b/tpl/tplimpl/template_errors.go index ac8a72df5..34e73a07a 100644 --- a/tpl/tplimpl/template_errors.go +++ b/tpl/tplimpl/template_errors.go @@ -17,22 +17,22 @@ import ( "fmt" "github.com/gohugoio/hugo/common/herrors" - "github.com/spf13/afero" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/identity" ) +var _ identity.Identity = (*templateInfo)(nil) + type templateInfo struct { name string template string isText bool // HTML or plain text template. - // Used to create some error context in error situations - fs afero.Fs - - // The filename relative to the fs above. - filename string + meta *hugofs.FileMeta +} - // The real filename (if possible). Used for logging. - realFilename string +func (t templateInfo) IdentifierBase() string { + return t.name } func (t templateInfo) Name() string { @@ -40,7 +40,7 @@ func (t templateInfo) Name() string { } func (t templateInfo) Filename() string { - return t.realFilename + return t.meta.Filename } func (t templateInfo) IsZero() bool { @@ -53,12 +53,11 @@ func (t templateInfo) resolveType() templateType { func (info templateInfo) errWithFileContext(what string, err error) error { err = fmt.Errorf(what+": %w", err) - fe := herrors.NewFileErrorFromName(err, info.realFilename) - f, err := info.fs.Open(info.filename) + fe := herrors.NewFileErrorFromName(err, info.meta.Filename) + f, err := info.meta.Open() if err != nil { return err } defer f.Close() return fe.UpdateContent(f, nil) - } diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go index 97d1b40dd..8997c83d6 100644 --- a/tpl/tplimpl/template_funcs.go +++ b/tpl/tplimpl/template_funcs.go @@ -22,6 +22,7 @@ import ( "github.com/gohugoio/hugo/common/hreflect" "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/identity" "github.com/gohugoio/hugo/tpl" template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" @@ -65,9 +66,8 @@ import ( ) var ( - _ texttemplate.ExecHelper = (*templateExecHelper)(nil) - zero reflect.Value - contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem() + _ texttemplate.ExecHelper = (*templateExecHelper)(nil) + zero reflect.Value ) type templateExecHelper struct { @@ -81,7 +81,7 @@ func (t *templateExecHelper) GetFunc(ctx context.Context, tmpl texttemplate.Prep if fn, found := t.funcs[name]; found { if fn.Type().NumIn() > 0 { first := fn.Type().In(0) - if first.Implements(contextInterface) { + if hreflect.IsContextType(first) { // TODO(bep) check if we can void this conversion every time -- and if that matters. // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down // contextual information, e.g. the top level data context (e.g. Page). @@ -95,6 +95,13 @@ func (t *templateExecHelper) GetFunc(ctx context.Context, tmpl texttemplate.Prep } func (t *templateExecHelper) Init(ctx context.Context, tmpl texttemplate.Preparer) { + if t.running { + _, ok := tmpl.(identity.IdentityProvider) + if ok { + t.trackDependencies(ctx, tmpl, "", reflect.Value{}) + } + + } } func (t *templateExecHelper) GetMapValue(ctx context.Context, tmpl texttemplate.Preparer, receiver, key reflect.Value) (reflect.Value, bool) { @@ -116,22 +123,14 @@ func (t *templateExecHelper) GetMapValue(ctx context.Context, tmpl texttemplate. var typeParams = reflect.TypeOf(maps.Params{}) func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) { - if t.running { - switch name { - case "GetPage", "Render": - if info, ok := tmpl.(tpl.Info); ok { - if m := receiver.MethodByName(name + "WithTemplateInfo"); m.IsValid() { - return m, reflect.ValueOf(info) - } - } - } - } - if strings.EqualFold(name, "mainsections") && receiver.Type() == typeParams && receiver.Pointer() == t.siteParams.Pointer() { - // MOved to site.MainSections in Hugo 0.112.0. + // Moved to site.MainSections in Hugo 0.112.0. receiver = t.site name = "MainSections" + } + if t.running { + ctx = t.trackDependencies(ctx, tmpl, name, receiver) } fn := hreflect.GetMethodByName(receiver, name) @@ -141,7 +140,7 @@ func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Pr if fn.Type().NumIn() > 0 { first := fn.Type().In(0) - if first.Implements(contextInterface) { + if hreflect.IsContextType(first) { // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down // contextual information, e.g. the top level data context (e.g. Page). return fn, reflect.ValueOf(ctx) @@ -151,6 +150,43 @@ func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Pr return fn, zero } +func (t *templateExecHelper) trackDependencies(ctx context.Context, tmpl texttemplate.Preparer, name string, receiver reflect.Value) context.Context { + if tmpl == nil { + panic("must provide a template") + } + + idm := tpl.Context.GetDependencyManagerInCurrentScope(ctx) + if idm == nil { + return ctx + } + + if info, ok := tmpl.(identity.IdentityProvider); ok { + idm.AddIdentity(info.GetIdentity()) + } + + // The receive is the "." in the method execution or map lookup, e.g. the Page in .Resources. + if hreflect.IsValid(receiver) { + in := receiver.Interface() + + if idlp, ok := in.(identity.ForEeachIdentityByNameProvider); ok { + // This will skip repeated .RelPermalink usage on transformed resources + // which is not fingerprinted, e.g. to + // prevent all HTML pages to be re-rendered on a small CSS change. + idlp.ForEeachIdentityByName(name, func(id identity.Identity) bool { + idm.AddIdentity(id) + return false + }) + } else { + identity.WalkIdentitiesShallow(in, func(level int, id identity.Identity) bool { + idm.AddIdentity(id) + return false + }) + } + } + + return ctx +} + func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflect.Value) { funcs := createFuncMap(d) funcsv := make(map[string]reflect.Value) diff --git a/tpl/transform/integration_test.go b/tpl/transform/integration_test.go index f035ec719..351420a67 100644 --- a/tpl/transform/integration_test.go +++ b/tpl/transform/integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Hugo Authors. All rights reserved. +// Copyright 2024 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. @@ -77,7 +77,7 @@ disableKinds = ['section','sitemap','taxonomy','term'] --- title: p1 --- -a **b** c +a **b** ` + "\v" + ` c <!--more--> ` b := hugolib.Test(t, files) diff --git a/tpl/transform/transform.go b/tpl/transform/transform.go index 8078bc0ce..7054c6988 100644 --- a/tpl/transform/transform.go +++ b/tpl/transform/transform.go @@ -22,10 +22,11 @@ import ( "html/template" "strings" - "github.com/gohugoio/hugo/cache/namedmemcache" + "github.com/gohugoio/hugo/cache/dynacache" "github.com/gohugoio/hugo/markup/converter/hooks" "github.com/gohugoio/hugo/markup/highlight" "github.com/gohugoio/hugo/markup/highlight/chromalexers" + "github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/tpl" "github.com/gohugoio/hugo/deps" @@ -35,21 +36,23 @@ import ( // New returns a new instance of the transform-namespaced template functions. func New(deps *deps.Deps) *Namespace { - cache := namedmemcache.New() - deps.BuildStartListeners.Add( - func() { - cache.Clear() - }) + if deps.MemCache == nil { + panic("must provide MemCache") + } return &Namespace{ - cache: cache, - deps: deps, + deps: deps, + cache: dynacache.GetOrCreatePartition[string, *resources.StaleValue[any]]( + deps.MemCache, + "/tmpl/transform", + dynacache.OptionsPartition{Weight: 30, ClearWhen: dynacache.ClearOnChange}, + ), } } // Namespace provides template functions for the "transform" namespace. type Namespace struct { - cache *namedmemcache.Cache + cache *dynacache.Partition[string, *resources.StaleValue[any]] deps *deps.Deps } @@ -154,7 +157,6 @@ func (ns *Namespace) XMLEscape(s any) (string, error) { // Markdownify renders s from Markdown to HTML. func (ns *Namespace) Markdownify(ctx context.Context, s any) (template.HTML, error) { - home := ns.deps.Site.Home() if home == nil { panic("home must not be nil") diff --git a/tpl/transform/unmarshal.go b/tpl/transform/unmarshal.go index 3936126ca..d876c88d7 100644 --- a/tpl/transform/unmarshal.go +++ b/tpl/transform/unmarshal.go @@ -14,18 +14,18 @@ package transform import ( + "errors" "fmt" "io" "strings" + "github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/common/types" "github.com/mitchellh/mapstructure" - "errors" - "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/parser/metadecoders" @@ -71,7 +71,7 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) { key += decoder.OptionsKey() } - return ns.cache.GetOrCreate(key, func() (any, error) { + v, err := ns.cache.GetOrCreate(key, func(string) (*resources.StaleValue[any], error) { f := metadecoders.FormatFromStrings(r.MediaType().Suffixes()...) if f == "" { return nil, fmt.Errorf("MIME %q not supported", r.MediaType()) @@ -88,8 +88,24 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) { return nil, err } - return decoder.Unmarshal(b, f) + v, err := decoder.Unmarshal(b, f) + if err != nil { + return nil, err + } + + return &resources.StaleValue[any]{ + Value: v, + IsStaleFunc: func() bool { + return resource.IsStaleAny(r) + }, + }, nil }) + if err != nil { + return nil, err + } + + return v.Value, nil + } dataStr, err := types.ToStringE(data) @@ -103,14 +119,29 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) { key := helpers.MD5String(dataStr) - return ns.cache.GetOrCreate(key, func() (any, error) { + v, err := ns.cache.GetOrCreate(key, func(string) (*resources.StaleValue[any], error) { f := decoder.FormatFromContentString(dataStr) if f == "" { return nil, errors.New("unknown format") } - return decoder.Unmarshal([]byte(dataStr), f) + v, err := decoder.Unmarshal([]byte(dataStr), f) + if err != nil { + return nil, err + } + + return &resources.StaleValue[any]{ + Value: v, + IsStaleFunc: func() bool { + return false + }, + }, nil }) + if err != nil { + return nil, err + } + + return v.Value, nil } func decodeDecoder(m map[string]any) (metadecoders.Decoder, error) { diff --git a/tpl/transform/unmarshal_test.go b/tpl/transform/unmarshal_test.go index 12774298a..1b976c449 100644 --- a/tpl/transform/unmarshal_test.go +++ b/tpl/transform/unmarshal_test.go @@ -14,6 +14,7 @@ package transform_test import ( + "context" "fmt" "math/rand" "strings" @@ -193,9 +194,11 @@ func BenchmarkUnmarshalString(b *testing.B) { jsons[i] = strings.Replace(testJSON, "ROOT_KEY", fmt.Sprintf("root%d", i), 1) } + ctx := context.Background() + b.ResetTimer() for i := 0; i < b.N; i++ { - result, err := ns.Unmarshal(jsons[rand.Intn(numJsons)]) + result, err := ns.Unmarshal(ctx, jsons[rand.Intn(numJsons)]) if err != nil { b.Fatal(err) } @@ -220,9 +223,11 @@ func BenchmarkUnmarshalResource(b *testing.B) { jsons[i] = testContentResource{key: key, content: strings.Replace(testJSON, "ROOT_KEY", key, 1), mime: media.Builtin.JSONType} } + ctx := context.Background() + b.ResetTimer() for i := 0; i < b.N; i++ { - result, err := ns.Unmarshal(jsons[rand.Intn(numJsons)]) + result, err := ns.Unmarshal(ctx, jsons[rand.Intn(numJsons)]) if err != nil { b.Fatal(err) } |