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
This commit is contained in:
Bjørn Erik Pedersen 2022-04-11 10:34:08 +02:00
parent f8c4e1690a
commit 2dbdf38a54
5 changed files with 70 additions and 1 deletions

View file

@ -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 { type keyRename struct {
pattern glob.Glob pattern glob.Glob
newKey string newKey string

View file

@ -171,3 +171,26 @@ func TestRenameKeys(t *testing.T) {
t.Errorf("Expected\n%#v, got\n%#v\n", expected, m) 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")
}

View file

@ -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. `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 ### Error Handling
{{< new-in "0.91.0" >}} {{< new-in "0.91.0" >}}

View file

@ -27,6 +27,7 @@ import (
"strings" "strings"
"github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/media" "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) 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) { _, httpResponse, err := c.cacheGetResource.GetOrCreate(resourceID, func() (io.ReadCloser, error) {
options, err := decodeRemoteOptions(optionsm) options, err := decodeRemoteOptions(optionsm)
@ -199,6 +200,13 @@ func (c *Client) validateFromRemoteArgs(uri string, options fromRemoteOptions) e
return nil 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) { func addDefaultHeaders(req *http.Request, accepts ...string) {
for _, accept := range accepts { for _, accept := range accepts {
if !hasHeaderValue(req.Header, "Accept", accept) { if !hasHeaderValue(req.Header, "Accept", accept) {

View file

@ -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")
}