diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-01-20 18:07:41 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-01-22 10:28:12 +0100 |
commit | 9421380168f66620cb73203e1267814b3086d805 (patch) | |
tree | 0946c9982ebfd61bb74268e69302e2364171d6f2 /resource/resource.go | |
parent | 5d03086981b4a7d4bc450269a6a2e0fd22dbeed7 (diff) |
resource: Add Match and GetMatch
These methods takes a glob pattern as argument:
* by default matching from the bundle root
* matching is case insensitive and the separator is Unix style slashes: "/"
* the bundle root does (by default) not start with a leading slash
* if you renames the `Name` for the rsource in front matter (`src=...`), then that is the value used in `Match`.
* double asterisk matches beyond directory borders, so "**.jpg" will match any JPEG image in the bundle
See https://github.com/gobwas/glob
This commit also deprecates `ByPrefix` and `GetByPrefix`.
This should also be more effective, given a fair amount of reuse of the glob patterns:
```bash
BenchmarkResourcesByPrefix-4 300000 4284 ns/op 1130 B/op 7 allocs/op
BenchmarkResourcesMatch-4 300000 5220 ns/op 505 B/op 3 allocs/op
```
Fixes #4301
Diffstat (limited to 'resource/resource.go')
-rw-r--r-- | resource/resource.go | 91 |
1 files changed, 88 insertions, 3 deletions
diff --git a/resource/resource.go b/resource/resource.go index b3a92273d..a2128bdc7 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -21,13 +21,14 @@ import ( "path/filepath" "strconv" "strings" + "sync" "github.com/spf13/cast" + "github.com/gobwas/glob" + "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/media" "github.com/gohugoio/hugo/source" - - "github.com/gohugoio/hugo/helpers" ) var ( @@ -101,10 +102,21 @@ func (r Resources) ByType(tp string) Resources { return filtered } +const prefixDeprecatedMsg = `We have added the more flexible Resources.GetMatch (find one) and Resources.Match (many) to replace the "prefix" methods. + +These matches by a given globbing pattern, e.g. "*.jpg". + +Some examples: + +* To find all resources by its prefix in the root dir of the bundle: .Match image* +* To find one resource by its prefix in the root dir of the bundle: .GetMatch image* +* To find all JPEG images anywhere in the bundle: .Match **.jpg` + // GetBySuffix gets the first resource matching the given filename prefix, e.g // "logo" will match logo.png. It returns nil of none found. // In potential ambiguous situations, combine it with ByType. func (r Resources) GetByPrefix(prefix string) Resource { + helpers.Deprecated("Resources", "GetByPrefix", prefixDeprecatedMsg, false) prefix = strings.ToLower(prefix) for _, resource := range r { if matchesPrefix(resource, prefix) { @@ -117,6 +129,7 @@ func (r Resources) GetByPrefix(prefix string) Resource { // ByPrefix gets all resources matching the given base filename prefix, e.g // "logo" will match logo.png. func (r Resources) ByPrefix(prefix string) Resources { + helpers.Deprecated("Resources", "ByPrefix", prefixDeprecatedMsg, false) var matches Resources prefix = strings.ToLower(prefix) for _, resource := range r { @@ -127,10 +140,80 @@ func (r Resources) ByPrefix(prefix string) Resources { return matches } +// 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 := getGlob(pattern) + if err != nil { + return nil + } + + for _, resource := range r { + if g.Match(strings.ToLower(resource.Name())) { + return resource + } + } + + return nil +} + +// Match gets all resources matching the given base filename 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 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 := getGlob(pattern) + if err != nil { + return nil + } + + var matches Resources + for _, resource := range r { + if g.Match(strings.ToLower(resource.Name())) { + matches = append(matches, resource) + } + } + return matches +} + func matchesPrefix(r Resource, prefix string) bool { return strings.HasPrefix(strings.ToLower(r.Name()), prefix) } +var ( + globCache = make(map[string]glob.Glob) + globMu sync.RWMutex +) + +func getGlob(pattern string) (glob.Glob, error) { + pattern = strings.ToLower(pattern) + + var g glob.Glob + + globMu.RLock() + g, found := globCache[pattern] + globMu.RUnlock() + if !found { + var err error + g, err = glob.Compile(pattern, '/') + if err != nil { + return nil, err + } + + globMu.Lock() + globCache[pattern] = g + globMu.Unlock() + } + + return g, nil + +} + type Spec struct { *helpers.PathSpec mimeTypes media.Types @@ -390,11 +473,13 @@ func AssignMetadata(metadata []map[string]interface{}, resources ...Resource) er srcKey := strings.ToLower(cast.ToString(src)) - match, err := path.Match(srcKey, resourceSrcKey) + glob, err := getGlob(srcKey) if err != nil { return fmt.Errorf("failed to match resource with metadata: %s", err) } + match := glob.Match(resourceSrcKey) + if match { if !nameSet { name, found := meta["name"] |