mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-28 23:02:05 -05:00
resources: Create a common ResourceFinder interface
And make both .Resources and resources implement it. This gets us 2 new methods/functions, so you can now also do: * .Resources.Get * resources.ByType Note that GetRemote is not covered by this interface, as that is only available as a global template function. Fixes #8653
This commit is contained in:
parent
20162518c4
commit
e58a540895
7 changed files with 148 additions and 36 deletions
|
@ -21,7 +21,7 @@ import (
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStringTypes(t *testing.T) {
|
func TestRenderedString(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
// Validate that it will behave like a string in Hugo settings.
|
// Validate that it will behave like a string in Hugo settings.
|
||||||
|
|
|
@ -397,6 +397,8 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
|
||||||
c.Assert(bundleFr, qt.Not(qt.IsNil))
|
c.Assert(bundleFr, qt.Not(qt.IsNil))
|
||||||
c.Assert(len(bundleFr.Resources()), qt.Equals, 1)
|
c.Assert(len(bundleFr.Resources()), qt.Equals, 1)
|
||||||
logoFr := bundleFr.Resources().GetMatch("logo*")
|
logoFr := bundleFr.Resources().GetMatch("logo*")
|
||||||
|
logoFrGet := bundleFr.Resources().Get("logo.png")
|
||||||
|
c.Assert(logoFrGet, qt.Equals, logoFr)
|
||||||
c.Assert(logoFr, qt.Not(qt.IsNil))
|
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/index.html", "Resources: image/png: /blog/fr/bundles/b1/logo.png")
|
||||||
b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data")
|
b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data")
|
||||||
|
|
|
@ -553,7 +553,8 @@ HEADLESS {{< myShort >}}
|
||||||
|
|
||||||
headlessResources := headless.Resources()
|
headlessResources := headless.Resources()
|
||||||
c.Assert(len(headlessResources), qt.Equals, 3)
|
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*")
|
pageResource := headlessResources.GetMatch("p*")
|
||||||
c.Assert(pageResource, qt.Not(qt.IsNil))
|
c.Assert(pageResource, qt.Not(qt.IsNil))
|
||||||
p := pageResource.(page.Page)
|
p := pageResource.(page.Page)
|
||||||
|
|
|
@ -696,6 +696,8 @@ func TestResourcesMatch(t *testing.T) {
|
||||||
b.WithContent("page.md", "")
|
b.WithContent("page.md", "")
|
||||||
|
|
||||||
b.WithSourceFile(
|
b.WithSourceFile(
|
||||||
|
"assets/images/img1.png", "png",
|
||||||
|
"assets/images/img2.jpg", "jpg",
|
||||||
"assets/jsons/data1.json", "json1 content",
|
"assets/jsons/data1.json", "json1 content",
|
||||||
"assets/jsons/data2.json", "json2 content",
|
"assets/jsons/data2.json", "json2 content",
|
||||||
"assets/jsons/data3.xml", "xml content",
|
"assets/jsons/data3.xml", "xml content",
|
||||||
|
@ -704,7 +706,9 @@ func TestResourcesMatch(t *testing.T) {
|
||||||
b.WithTemplates("index.html", `
|
b.WithTemplates("index.html", `
|
||||||
{{ $jsons := (resources.Match "jsons/*.json") }}
|
{{ $jsons := (resources.Match "jsons/*.json") }}
|
||||||
{{ $json := (resources.GetMatch "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 }}
|
JSON: {{ $json.RelPermalink }}: {{ $json.Content }}
|
||||||
{{ range $jsons }}
|
{{ range $jsons }}
|
||||||
{{- .RelPermalink }}: {{ .Content }}
|
{{- .RelPermalink }}: {{ .Content }}
|
||||||
|
@ -715,7 +719,10 @@ JSON: {{ $json.RelPermalink }}: {{ $json.Content }}
|
||||||
|
|
||||||
b.AssertFileContent("public/index.html",
|
b.AssertFileContent("public/index.html",
|
||||||
"JSON: /jsons/data1.json: json1 content",
|
"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) {
|
func TestResourceMinifyDisabled(t *testing.T) {
|
||||||
|
|
|
@ -18,35 +18,64 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs/glob"
|
"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.
|
// Resources represents a slice of resources, which can be a mix of different types.
|
||||||
// I.e. both pages and images etc.
|
// I.e. both pages and images etc.
|
||||||
type Resources []Resource
|
type Resources []Resource
|
||||||
|
|
||||||
|
// var _ resource.ResourceFinder = (*Namespace)(nil)
|
||||||
// ResourcesConverter converts a given slice of Resource objects to Resources.
|
// ResourcesConverter converts a given slice of Resource objects to Resources.
|
||||||
type ResourcesConverter interface {
|
type ResourcesConverter interface {
|
||||||
ToResources() Resources
|
ToResources() Resources
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByType returns resources of a given resource type (ie. "image").
|
// ByType returns resources of a given resource type (e.g. "image").
|
||||||
func (r Resources) ByType(tp string) Resources {
|
func (r Resources) ByType(typ any) Resources {
|
||||||
|
tpstr, err := cast.ToStringE(typ)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
var filtered Resources
|
var filtered Resources
|
||||||
|
|
||||||
for _, resource := range r {
|
for _, resource := range r {
|
||||||
if resource.ResourceType() == tp {
|
if resource.ResourceType() == tpstr {
|
||||||
filtered = append(filtered, resource)
|
filtered = append(filtered, resource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filtered
|
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.
|
// 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.
|
// See Match for a more complete explanation about the rules used.
|
||||||
func (r Resources) GetMatch(pattern string) Resource {
|
func (r Resources) GetMatch(pattern any) Resource {
|
||||||
g, err := glob.GetGlob(pattern)
|
patternstr, err := cast.ToStringE(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := glob.GetGlob(patternstr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, resource := range r {
|
for _, resource := range r {
|
||||||
|
@ -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
|
// 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".
|
// 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.
|
// See https://github.com/gobwas/glob for the full rules set.
|
||||||
func (r Resources) Match(pattern string) Resources {
|
func (r Resources) Match(pattern any) Resources {
|
||||||
g, err := glob.GetGlob(pattern)
|
patternstr, err := cast.ToStringE(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := glob.GetGlob(patternstr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches Resources
|
var matches Resources
|
||||||
|
@ -121,3 +155,43 @@ func (r Resources) MergeByLanguageInterface(in any) (any, error) {
|
||||||
type Source interface {
|
type Source interface {
|
||||||
Publish() error
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
// Match gets the resources matching the given pattern from the assets filesystem.
|
||||||
func (c *Client) Match(pattern string) (resource.Resources, error) {
|
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.
|
// GetMatch gets first resource matching the given pattern from the assets filesystem.
|
||||||
func (c *Client) GetMatch(pattern string) (resource.Resource, error) {
|
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 {
|
if err != nil || len(res) == 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res[0], err
|
return res[0], err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) match(pattern string, firstOnly bool) (resource.Resources, error) {
|
func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource) bool, firstOnly bool) (resource.Resources, error) {
|
||||||
var name string
|
|
||||||
if firstOnly {
|
|
||||||
name = "__get-match"
|
|
||||||
} else {
|
|
||||||
name = "__match"
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern = glob.NormalizePath(pattern)
|
pattern = glob.NormalizePath(pattern)
|
||||||
partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
|
partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
|
||||||
if len(partitions) == 0 {
|
if len(partitions) == 0 {
|
||||||
|
@ -110,6 +111,10 @@ func (c *Client) match(pattern string, firstOnly bool) (resource.Resources, erro
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matchFunc != nil && !matchFunc(r) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
res = append(res, r)
|
res = append(res, r)
|
||||||
|
|
||||||
return firstOnly, nil
|
return firstOnly, nil
|
||||||
|
|
|
@ -16,9 +16,10 @@ package resources
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
@ -73,6 +74,8 @@ func New(deps *deps.Deps) (*Namespace, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ resource.ResourceFinder = (*Namespace)(nil)
|
||||||
|
|
||||||
// Namespace provides template functions for the "resources" namespace.
|
// Namespace provides template functions for the "resources" namespace.
|
||||||
type Namespace struct {
|
type Namespace struct {
|
||||||
deps *deps.Deps
|
deps *deps.Deps
|
||||||
|
@ -107,15 +110,19 @@ func (ns *Namespace) getscssClientDartSass() (*dartsass.Client, error) {
|
||||||
return ns.scssClientDartSass, err
|
return ns.scssClientDartSass, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get locates the filename given in Hugo's assets filesystem and
|
// Get locates the filename given in Hugo's assets filesystem
|
||||||
// creates a Resource object that can be used for
|
// and creates a Resource object that can be used for further transformations.
|
||||||
// further transformations.
|
func (ns *Namespace) Get(filename any) resource.Resource {
|
||||||
func (ns *Namespace) Get(filename any) (resource.Resource, error) {
|
|
||||||
filenamestr, err := cast.ToStringE(filename)
|
filenamestr, err := cast.ToStringE(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
panic(err)
|
||||||
}
|
}
|
||||||
return ns.createClient.Get(filepath.Clean(filenamestr))
|
r, err := ns.createClient.Get(filenamestr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRemote gets the URL (via HTTP(s)) in the first argument in args and creates Resource object that can be used for
|
// GetRemote gets the URL (via HTTP(s)) in the first argument in args and creates Resource object that can be used for
|
||||||
|
@ -168,13 +175,23 @@ func (ns *Namespace) GetRemote(args ...any) resource.Resource {
|
||||||
// It looks for files in the assets file system.
|
// It looks for files in the assets file system.
|
||||||
//
|
//
|
||||||
// See Match for a more complete explanation about the rules used.
|
// 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)
|
patternStr, err := cast.ToStringE(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ns.createClient.GetMatch(patternStr)
|
r, err := ns.createClient.GetMatch(patternStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// Match gets all resources matching the given base path prefix, e.g
|
||||||
|
@ -193,13 +210,19 @@ func (ns *Namespace) GetMatch(pattern any) (resource.Resource, error) {
|
||||||
// It looks for files in the assets file system.
|
// It looks for files in the assets file system.
|
||||||
//
|
//
|
||||||
// See Match for a more complete explanation about the rules used.
|
// 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)
|
patternStr, err := cast.ToStringE(pattern)
|
||||||
if err != nil {
|
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
|
// Concat concatenates a slice of Resource objects. These resources must
|
||||||
|
|
Loading…
Reference in a new issue