hugo

Unnamed repository; edit this file 'description' to name the repository.

git clone git://git.shimmy1996.com/hugo.git
commit 2dbdf38a5411c554146ddd915a0aea3b2eaf4407
parent f8c4e1690a462a2dcafa05aeaab65d1fad6df61e
Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date:   Mon, 11 Apr 2022 10:34:08 +0200

resources: Add `key` to reources.GetRemote options map

If set, `key` will be used as the only cache key element for the resource.

The default behaviour is to calculate the key based on the URL and all the options.

This means that you can now do:

```
{{ $cacheKey := print $url (now.Format "2006-01-02") }}
{{ $resource := resource.GetRemote $url (dict "key" $cacheKey) }}
```

Fixes #9755

Diffstat:
Mcommon/maps/maps.go | 14++++++++++++++
Mcommon/maps/maps_test.go | 23+++++++++++++++++++++++
Mdocs/content/en/hugo-pipes/introduction.md | 13+++++++++++++
Mresources/resource_factories/create/remote.go | 10+++++++++-
Mresources/resource_factories/create/remote_test.go | 11+++++++++++
5 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/common/maps/maps.go b/common/maps/maps.go
@@ -109,6 +109,20 @@ func ToSliceStringMap(in any) ([]map[string]any, error) {
 	}
 }
 
+// LookupEqualFold finds key in m with case insensitive equality checks.
+func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) {
+	if v, found := m[key]; found {
+		return v, true
+	}
+	for k, v := range m {
+		if strings.EqualFold(k, key) {
+			return v, true
+		}
+	}
+	var s T
+	return s, false
+}
+
 type keyRename struct {
 	pattern glob.Glob
 	newKey  string
diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go
@@ -171,3 +171,26 @@ func TestRenameKeys(t *testing.T) {
 		t.Errorf("Expected\n%#v, got\n%#v\n", expected, m)
 	}
 }
+
+func TestLookupEqualFold(t *testing.T) {
+	c := qt.New(t)
+
+	m1 := map[string]any{
+		"a": "av",
+		"B": "bv",
+	}
+
+	v, found := LookupEqualFold(m1, "b")
+	c.Assert(found, qt.IsTrue)
+	c.Assert(v, qt.Equals, "bv")
+
+	m2 := map[string]string{
+		"a": "av",
+		"B": "bv",
+	}
+
+	v, found = LookupEqualFold(m2, "b")
+	c.Assert(found, qt.IsTrue)
+	c.Assert(v, qt.Equals, "bv")
+
+}
diff --git a/docs/content/en/hugo-pipes/introduction.md b/docs/content/en/hugo-pipes/introduction.md
@@ -53,6 +53,19 @@ With `resources.GetRemote`, the first argument is a remote URL:
 
 `resources.Get` and `resources.GetRemote` return `nil` if the resource is not found.
 
+### Caching
+
+By default, Hugo calculates a cache key based on the `URL` and the `options` (e.g. headers) given.
+
+
+{{< new-in "0.97.0" >}} You can override this by setting a `key` in the options map. This can be used to get more fine grained control over how often a remote resource is fetched, e.g.:
+
+
+```go-html-template
+{{ $cacheKey := print $url (now.Format "2006-01-02") }}
+{{ $resource := resource.GetRemote $url (dict "key" $cacheKey) }}
+```
+
 ### Error Handling
 
 {{< new-in "0.91.0" >}}
diff --git a/resources/resource_factories/create/remote.go b/resources/resource_factories/create/remote.go
@@ -27,6 +27,7 @@ import (
 	"strings"
 
 	"github.com/gohugoio/hugo/common/hugio"
+	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/common/types"
 	"github.com/gohugoio/hugo/helpers"
 	"github.com/gohugoio/hugo/media"
@@ -79,7 +80,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou
 		return nil, errors.Wrapf(err, "failed to parse URL for resource %s", uri)
 	}
 
-	resourceID := helpers.HashString(uri, optionsm)
+	resourceID := calculateResourceID(uri, optionsm)
 
 	_, httpResponse, err := c.cacheGetResource.GetOrCreate(resourceID, func() (io.ReadCloser, error) {
 		options, err := decodeRemoteOptions(optionsm)
@@ -199,6 +200,13 @@ func (c *Client) validateFromRemoteArgs(uri string, options fromRemoteOptions) e
 	return nil
 }
 
+func calculateResourceID(uri string, optionsm map[string]any) string {
+	if key, found := maps.LookupEqualFold(optionsm, "key"); found {
+		return helpers.HashString(key)
+	}
+	return helpers.HashString(uri, optionsm)
+}
+
 func addDefaultHeaders(req *http.Request, accepts ...string) {
 	for _, accept := range accepts {
 		if !hasHeaderValue(req.Header, "Accept", accept) {
diff --git a/resources/resource_factories/create/remote_test.go b/resources/resource_factories/create/remote_test.go
@@ -83,3 +83,14 @@ func TestDecodeRemoteOptions(t *testing.T) {
 	}
 
 }
+
+func TestCalculateResourceID(t *testing.T) {
+	c := qt.New(t)
+
+	c.Assert(calculateResourceID("foo", nil), qt.Equals, "5917621528921068675")
+	c.Assert(calculateResourceID("foo", map[string]any{"bar": "baz"}), qt.Equals, "7294498335241413323")
+
+	c.Assert(calculateResourceID("foo", map[string]any{"key": "1234", "bar": "baz"}), qt.Equals, "14904296279238663669")
+	c.Assert(calculateResourceID("asdf", map[string]any{"key": "1234", "bar": "asdf"}), qt.Equals, "14904296279238663669")
+	c.Assert(calculateResourceID("asdf", map[string]any{"key": "12345", "bar": "asdf"}), qt.Equals, "12191037851845371770")
+}