Skip to content

Commit

Permalink
resources: Create a ResourceFinder interface
Browse files Browse the repository at this point in the history
And make both .Resources and resources implement it.

This commit:

* Preserves the `interface{}` arguments, which is unfortunate, but probably needed. Maybe Go 1.18's generics can fix that.
* Removes any `error` in return values; all errors are in the "should not happen" category, we return `nil` on not found.

Fixes gohugoio#8653
  • Loading branch information
bep committed Oct 30, 2021
1 parent dce49d1 commit cca66a8
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 31 deletions.
2 changes: 2 additions & 0 deletions hugolib/hugo_sites_build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
c.Assert(bundleFr, qt.Not(qt.IsNil))
c.Assert(len(bundleFr.Resources()), qt.Equals, 1)
logoFr := bundleFr.Resources().GetMatch("logo*")
logoFrGet := bundleFr.Resources().Get("logo.png")
c.Assert(logoFrGet, qt.Equals, logoFr)
c.Assert(logoFr, qt.Not(qt.IsNil))
b.AssertFileContent("public/fr/bundles/b1/index.html", "Resources: image/png: /blog/fr/bundles/b1/logo.png")
b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data")
Expand Down
3 changes: 2 additions & 1 deletion hugolib/pagebundler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,8 @@ HEADLESS {{< myShort >}}

headlessResources := headless.Resources()
c.Assert(len(headlessResources), qt.Equals, 3)
c.Assert(len(headlessResources.Match("l*")), qt.Equals, 2)
res := headlessResources.Match("l*")
c.Assert(len(res), qt.Equals, 2)
pageResource := headlessResources.GetMatch("p*")
c.Assert(pageResource, qt.Not(qt.IsNil))
p := pageResource.(page.Page)
Expand Down
11 changes: 9 additions & 2 deletions hugolib/resource_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,8 @@ func TestResourcesMatch(t *testing.T) {
b.WithContent("page.md", "")

b.WithSourceFile(
"assets/images/img1.png", "png",
"assets/images/img2.jpg", "jpg",
"assets/jsons/data1.json", "json1 content",
"assets/jsons/data2.json", "json2 content",
"assets/jsons/data3.xml", "xml content",
Expand All @@ -895,7 +897,9 @@ func TestResourcesMatch(t *testing.T) {
b.WithTemplates("index.html", `
{{ $jsons := (resources.Match "jsons/*.json") }}
{{ $json := (resources.GetMatch "jsons/*.json") }}
{{ printf "JSONS: %d" (len $jsons) }}
{{ printf "jsonsMatch: %d" (len $jsons) }}
{{ printf "imagesByType: %d" (len (resources.ByType "image") ) }}
{{ printf "applicationByType: %d" (len (resources.ByType "application") ) }}
JSON: {{ $json.RelPermalink }}: {{ $json.Content }}
{{ range $jsons }}
{{- .RelPermalink }}: {{ .Content }}
Expand All @@ -906,7 +910,10 @@ JSON: {{ $json.RelPermalink }}: {{ $json.Content }}

b.AssertFileContent("public/index.html",
"JSON: /jsons/data1.json: json1 content",
"JSONS: 2", "/jsons/data1.json: json1 content")
"jsonsMatch: 2",
"imagesByType: 2",
"applicationByType: 3",
"/jsons/data1.json: json1 content")
}

func TestExecuteAsTemplateWithLanguage(t *testing.T) {
Expand Down
92 changes: 83 additions & 9 deletions resources/resource/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,64 @@ import (
"strings"

"github.com/gohugoio/hugo/hugofs/glob"
"github.com/spf13/cast"
)

var _ ResourceFinder = (*Resources)(nil)

// Resources represents a slice of resources, which can be a mix of different types.
// I.e. both pages and images etc.
type Resources []Resource

// var _ resource.ResourceFinder = (*Namespace)(nil)
// ResourcesConverter converts a given slice of Resource objects to Resources.
type ResourcesConverter interface {
ToResources() Resources
}

// ByType returns resources of a given resource type (ie. "image").
func (r Resources) ByType(tp string) Resources {
// ByType returns resources of a given resource type (e.g. "image").
func (r Resources) ByType(tp interface{}) Resources {
tpstr, err := cast.ToStringE(tp)
if err != nil {
panic(err)
}
var filtered Resources

for _, resource := range r {
if resource.ResourceType() == tp {
if resource.ResourceType() == tpstr {
filtered = append(filtered, resource)
}
}
return filtered
}

// Get locates the name given in Resources.
// The search is case insensitive.
func (r Resources) Get(name interface{}) Resource {
namestr, err := cast.ToStringE(name)
if err != nil {
panic(err)
}
namestr = strings.ToLower(namestr)
for _, resource := range r {
if strings.EqualFold(namestr, resource.Name()) {
return resource
}
}
return nil
}

// GetMatch finds the first Resource matching the given pattern, or nil if none found.
// See Match for a more complete explanation about the rules used.
func (r Resources) GetMatch(pattern string) Resource {
g, err := glob.GetGlob(pattern)
func (r Resources) GetMatch(pattern interface{}) Resource {
patternstr, err := cast.ToStringE(pattern)
if err != nil {
panic(err)
}

g, err := glob.GetGlob(patternstr)
if err != nil {
return nil
panic(err)
}

for _, resource := range r {
Expand All @@ -67,10 +96,15 @@ func (r Resources) GetMatch(pattern string) Resource {
// Match matches by using the value of Resource.Name, which, by default, is a filename with
// path relative to the bundle root with Unix style slashes (/) and no leading slash, e.g. "images/logo.png".
// See https://github.com/gobwas/glob for the full rules set.
func (r Resources) Match(pattern string) Resources {
g, err := glob.GetGlob(pattern)
func (r Resources) Match(pattern interface{}) Resources {
patternstr, err := cast.ToStringE(pattern)
if err != nil {
return nil
panic(err)
}

g, err := glob.GetGlob(patternstr)
if err != nil {
panic(err)
}

var matches Resources
Expand Down Expand Up @@ -121,3 +155,43 @@ func (r Resources) MergeByLanguageInterface(in interface{}) (interface{}, error)
type Source interface {
Publish() error
}

// ResourceFinder provides methods to find Resources.
type ResourceFinder interface {

// Get locates the Resource with the given name in the current context (e.g. in .Page.Resources).
//
// It returns nil if no Resource could found, panics if filename is invalid.
Get(name interface{}) Resource

// GetMatch finds the first Resource matching the given pattern, or nil if none found.
//
// See Match for a more complete explanation about the rules used.
//
// It returns nil if no Resource could found, panics if pattern is invalid.
GetMatch(pattern interface{}) Resource

// Match gets all resources matching the given base path prefix, e.g
// "*.png" will match all png files. The "*" does not match path delimiters (/),
// so if you organize your resources in sub-folders, you need to be explicit about it, e.g.:
// "images/*.png". To match any PNG image anywhere in the bundle you can do "**.png", and
// to match all PNG images below the images folder, use "images/**.jpg".
//
// The matching is case insensitive.
//
// Match matches by using a relative pathwith Unix style slashes (/) and no
// leading slash, e.g. "images/logo.png".
//
// See https://github.com/gobwas/glob for the full rules set.
//
// It looks for files in the assets file system.
//
// See Match for a more complete explanation about the rules used.
//
// It returns nil if no Resources could found, panics if pattern is invalid.
Match(pattern interface{}) Resources

// ByType returns resources of a given resource type (e.g. "image").
// It returns nil if no Resources could found, panics if tp is invalid.
ByType(tp interface{}) Resources
}
25 changes: 15 additions & 10 deletions resources/resource_factories/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,27 @@ func (c *Client) Get(filename string) (resource.Resource, error) {

// Match gets the resources matching the given pattern from the assets filesystem.
func (c *Client) Match(pattern string) (resource.Resources, error) {
return c.match(pattern, false)
return c.match("__match", pattern, nil, false)
}

func (c *Client) ByType(tp string) resource.Resources {
res, err := c.match(path.Join("_byType", tp), "**", func(r resource.Resource) bool { return r.ResourceType() == tp }, false)
if err != nil {
panic(err)
}
return res
}

// GetMatch gets first resource matching the given pattern from the assets filesystem.
func (c *Client) GetMatch(pattern string) (resource.Resource, error) {
res, err := c.match(pattern, true)
res, err := c.match("__get-match", pattern, nil, true)
if err != nil || len(res) == 0 {
return nil, err
}
return res[0], err
}

func (c *Client) match(pattern string, firstOnly bool) (resource.Resources, error) {
var name string
if firstOnly {
name = "__get-match"
} else {
name = "__match"
}

func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource) bool, firstOnly bool) (resource.Resources, error) {
pattern = glob.NormalizePath(pattern)
partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
if len(partitions) == 0 {
Expand All @@ -99,6 +100,10 @@ func (c *Client) match(pattern string, firstOnly bool) (resource.Resources, erro
return true, err
}

if matchFunc != nil && !matchFunc(r) {
return false, nil
}

res = append(res, r)

return firstOnly, nil
Expand Down
43 changes: 34 additions & 9 deletions tpl/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"path/filepath"
"sync"

"github.com/gohugoio/hugo/common/herrors"

"github.com/gohugoio/hugo/common/maps"
"github.com/pkg/errors"

Expand Down Expand Up @@ -73,6 +75,8 @@ func New(deps *deps.Deps) (*Namespace, error) {
}, nil
}

var _ resource.ResourceFinder = (*Namespace)(nil)

// Namespace provides template functions for the "resources" namespace.
type Namespace struct {
deps *deps.Deps
Expand Down Expand Up @@ -109,29 +113,44 @@ func (ns *Namespace) getscssClientDartSass() (*dartsass.Client, 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 interface{}) (resource.Resource, error) {
func (ns *Namespace) Get(filename interface{}) resource.Resource {
filenamestr, err := cast.ToStringE(filename)
if err != nil {
return nil, err
panic(err)
}

filenamestr = filepath.Clean(filenamestr)

return ns.createClient.Get(filenamestr)
r, err := ns.createClient.Get(filenamestr)
if err != nil {
panic(err)
}

return r
}

// GetMatch finds the first Resource matching the given pattern, or nil if none found.
//
// It looks for files in the assets file system.
//
// See Match for a more complete explanation about the rules used.
func (ns *Namespace) GetMatch(pattern interface{}) (resource.Resource, error) {
func (ns *Namespace) GetMatch(pattern interface{}) resource.Resource {
patternStr, err := cast.ToStringE(pattern)
if err != nil {
return nil, err
panic(err)
}

r, err := ns.createClient.GetMatch(patternStr)
if err != nil {
panic(err)
}

return ns.createClient.GetMatch(patternStr)
return r
}

// ByType returns resources of a given resource type (e.g. "image").
func (ns *Namespace) ByType(tp interface{}) resource.Resources {
return ns.createClient.ByType(cast.ToString(tp))
}

// Match gets all resources matching the given base path prefix, e.g
Expand All @@ -150,13 +169,19 @@ func (ns *Namespace) GetMatch(pattern interface{}) (resource.Resource, error) {
// It looks for files in the assets file system.
//
// See Match for a more complete explanation about the rules used.
func (ns *Namespace) Match(pattern interface{}) (resource.Resources, error) {
func (ns *Namespace) Match(pattern interface{}) resource.Resources {
defer herrors.Recover()
patternStr, err := cast.ToStringE(pattern)
if err != nil {
return nil, err
panic(err)
}

r, err := ns.createClient.Match(patternStr)
if err != nil {
panic(err)
}

return ns.createClient.Match(patternStr)
return r
}

// Concat concatenates a slice of Resource objects. These resources must
Expand Down

0 comments on commit cca66a8

Please sign in to comment.