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.

Note that GetRemote is not covered by this interface, as that is only available as a global template function.

Fixes gohugoio#8653
  • Loading branch information
bep committed Apr 4, 2022
1 parent ed9aa37 commit 3345c36
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 35 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 @@ -696,6 +696,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 @@ -704,7 +706,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 @@ -715,7 +719,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 TestResourceMinifyDisabled(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(typ any) Resources {
tpstr, err := cast.ToStringE(typ)
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 any) 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 any) 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 any) 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 any) (any, error) {
type Source interface {
Publish() error
}

// ResourceFinder provides methods to find Resources.
// Note that GetRemote (as found in resources.GetRemote) is
// not covered by this interface, as this is only available as a global template function.
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 name is invalid.
Get(name any) 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 any) 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.
//
// 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 any) Resources

// ByType returns resources of a given resource type (e.g. "image").
// It returns nil if no Resources could found, panics if typ is invalid.
ByType(typ any) 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 @@ -65,26 +65,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 @@ -110,6 +111,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
49 changes: 36 additions & 13 deletions tpl/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ package resources

import (
"fmt"
"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 +74,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 @@ -107,15 +110,19 @@ func (ns *Namespace) getscssClientDartSass() (*dartsass.Client, error) {
return ns.scssClientDartSass, err
}

// 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, 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 {
return nil, err
panic(err)
}
r, err := ns.createClient.Get(filenamestr)
if err != nil {
panic(err)
}
return ns.createClient.Get(filepath.Clean(filenamestr))

return r
}

// GetRemote gets the URL (via HTTP(s)) in the first argument in args and creates Resource object that can be used for
Expand Down Expand Up @@ -168,13 +175,23 @@ func (ns *Namespace) GetRemote(args ...any) resource.Resource {
// 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 any) (resource.Resource, error) {
func (ns *Namespace) GetMatch(pattern any) 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(typ any) resource.Resources {
return ns.createClient.ByType(cast.ToString(typ))
}

// Match gets all resources matching the given base path prefix, e.g
Expand All @@ -193,13 +210,19 @@ func (ns *Namespace) GetMatch(pattern any) (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 any) (resource.Resources, error) {
func (ns *Namespace) Match(pattern any) resource.Resources {
defer herrors.Recover()
patternStr, err := cast.ToStringE(pattern)
if err != nil {
return nil, err
panic(err)
}

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

return r
}

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

0 comments on commit 3345c36

Please sign in to comment.