From 4a5e94087ba2fe1405ad2edbcbeeccac2a6147a6 Mon Sep 17 00:00:00 2001 From: Bjørn Erik Pedersen Date: Sun, 22 Dec 2024 17:59:03 +0100 Subject: Fix union, complement, symdiff, and intersect for transient resources Fixes #13181 --- resources/resource.go | 32 ++++++++++++++++--------- resources/resource/resourcetypes.go | 10 +++++++- resources/resource_spec.go | 1 + resources/transform.go | 6 +++++ tpl/collections/collections_integration_test.go | 29 ++++++++++++++++++++++ tpl/collections/reflect_helpers.go | 12 +++++++--- 6 files changed, 75 insertions(+), 15 deletions(-) diff --git a/resources/resource.go b/resources/resource.go index 4b81a478a..7ab10b0ae 100644 --- a/resources/resource.go +++ b/resources/resource.go @@ -47,6 +47,7 @@ var ( _ resource.Cloner = (*genericResource)(nil) _ resource.ResourcesLanguageMerger = (*resource.Resources)(nil) _ resource.Identifier = (*genericResource)(nil) + _ resource.TransientIdentifier = (*genericResource)(nil) _ targetPathProvider = (*genericResource)(nil) _ sourcePathProvider = (*genericResource)(nil) _ identity.IdentityGroupProvider = (*genericResource)(nil) @@ -359,6 +360,9 @@ func GetTestInfoForResource(r resource.Resource) GenericResourceTestInfo { type genericResource struct { publishInit *sync.Once + key string + keyInit *sync.Once + sd ResourceSourceDescriptor paths internal.ResourcePaths @@ -444,19 +448,24 @@ func (l *genericResource) Data() any { } func (l *genericResource) Key() string { - basePath := l.spec.Cfg.BaseURL().BasePathNoTrailingSlash - var key string - if basePath == "" { - key = l.RelPermalink() - } else { - key = strings.TrimPrefix(l.RelPermalink(), basePath) - } + l.keyInit.Do(func() { + basePath := l.spec.Cfg.BaseURL().BasePathNoTrailingSlash + if basePath == "" { + l.key = l.RelPermalink() + } else { + l.key = strings.TrimPrefix(l.RelPermalink(), basePath) + } - if l.spec.Cfg.IsMultihost() { - key = l.spec.Lang() + key - } + if l.spec.Cfg.IsMultihost() { + l.key = l.spec.Lang() + l.key + } + }) + + return l.key +} - return key +func (l *genericResource) TransientKey() string { + return l.Key() } func (l *genericResource) targetPath() string { @@ -623,6 +632,7 @@ func (rc *genericResource) cloneWithUpdates(u *transformationUpdate) (baseResour func (l genericResource) clone() *genericResource { l.publishInit = &sync.Once{} + l.keyInit = &sync.Once{} return &l } diff --git a/resources/resource/resourcetypes.go b/resources/resource/resourcetypes.go index 0fb87f371..b33750e80 100644 --- a/resources/resource/resourcetypes.go +++ b/resources/resource/resourcetypes.go @@ -170,11 +170,19 @@ type ResourcesLanguageMerger interface { // Identifier identifies a resource. type Identifier interface { - // Key is is mostly for internal use and should be considered opaque. + // Key is mostly for internal use and should be considered opaque. // This value may change between Hugo versions. Key() string } +// TransientIdentifier identifies a transient resource. +type TransientIdentifier interface { + // TransientKey is mostly for internal use and should be considered opaque. + // This value is implemented by transient resources where pointers may be short lived and + // not suitable for use as a map keys. + TransientKey() string +} + // WeightProvider provides a weight. type WeightProvider interface { Weight() int diff --git a/resources/resource_spec.go b/resources/resource_spec.go index d50edeb73..912a0d786 100644 --- a/resources/resource_spec.go +++ b/resources/resource_spec.go @@ -187,6 +187,7 @@ func (r *Spec) NewResource(rd ResourceSourceDescriptor) (resource.Resource, erro Staler: &AtomicStaler{}, h: &resourceHash{}, publishInit: &sync.Once{}, + keyInit: &sync.Once{}, paths: rp, spec: r, sd: rd, diff --git a/resources/transform.go b/resources/transform.go index 4214067bd..c5d240669 100644 --- a/resources/transform.go +++ b/resources/transform.go @@ -52,8 +52,10 @@ var ( _ identity.IdentityGroupProvider = (*resourceAdapterInner)(nil) _ resource.Source = (*resourceAdapter)(nil) _ resource.Identifier = (*resourceAdapter)(nil) + _ resource.TransientIdentifier = (*resourceAdapter)(nil) _ targetPathProvider = (*resourceAdapter)(nil) _ sourcePathProvider = (*resourceAdapter)(nil) + _ resource.Identifier = (*resourceAdapter)(nil) _ resource.ResourceNameTitleProvider = (*resourceAdapter)(nil) _ resource.WithResourceMetaProvider = (*resourceAdapter)(nil) _ identity.DependencyManagerProvider = (*resourceAdapter)(nil) @@ -279,6 +281,10 @@ func (r *resourceAdapter) Key() string { return r.target.(resource.Identifier).Key() } +func (r *resourceAdapter) TransientKey() string { + return r.Key() +} + func (r *resourceAdapter) targetPath() string { r.init(false, false) return r.target.(targetPathProvider).targetPath() diff --git a/tpl/collections/collections_integration_test.go b/tpl/collections/collections_integration_test.go index e39493b52..2aabee03e 100644 --- a/tpl/collections/collections_integration_test.go +++ b/tpl/collections/collections_integration_test.go @@ -249,3 +249,32 @@ tags: ['tag-b'] "2: Intersect: 1|\n2: Union: 3|\n2: SymDiff: 2|\n2: Uniq: 3|", ) } + +// Issue #13181 +func TestUnionResourcesMatch(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +disableKinds = ['rss','sitemap', 'taxonomy', 'term', 'page'] +-- layouts/index.html -- +{{ $a := resources.Match "*a*" }} +{{ $b := resources.Match "*b*" }} +{{ $union := $a | union $b }} +{{ range $i, $e := $union }} +{{ $i }}: {{ .Name }} +{{ end }}$ +-- assets/a1.html -- +
file1
+-- assets/a2.html -- +
file2
+-- assets/a3_b1.html -- +
file3
+-- assets/b2.html -- +
file4
+` + + b := hugolib.Test(t, files) + + b.AssertFileContentExact("public/index.html", "0: /a3_b1.html\n\n1: /b2.html\n\n2: /a1.html\n\n3: /a2.html\n$") +} diff --git a/tpl/collections/reflect_helpers.go b/tpl/collections/reflect_helpers.go index 4b222be15..6b986cbc4 100644 --- a/tpl/collections/reflect_helpers.go +++ b/tpl/collections/reflect_helpers.go @@ -20,11 +20,12 @@ import ( "github.com/gohugoio/hugo/common/hashing" "github.com/gohugoio/hugo/common/types" + "github.com/gohugoio/hugo/resources/resource" ) var ( zero reflect.Value - errorType = reflect.TypeOf((*error)(nil)).Elem() + errorType = reflect.TypeFor[error]() ) func numberToFloat(v reflect.Value) (float64, error) { @@ -56,7 +57,13 @@ func normalize(v reflect.Value) any { return f } } - return types.Unwrapv(v.Interface()) + + vv := types.Unwrapv(v.Interface()) + if ip, ok := vv.(resource.TransientIdentifier); ok { + return ip.TransientKey() + } + + return vv } // collects identities from the slices in seqs into a set. Numeric values are normalized, @@ -151,7 +158,6 @@ func convertNumber(v reflect.Value, to reflect.Kind) (reflect.Value, error) { case reflect.Uint64: n = reflect.ValueOf(uint64(i)) } - } if !n.IsValid() { -- cgit v1.2.3