Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/hugofs
diff options
context:
space:
mode:
Diffstat (limited to 'hugofs')
-rw-r--r--hugofs/glob/glob.go168
-rw-r--r--hugofs/glob/glob_test.go35
2 files changed, 184 insertions, 19 deletions
diff --git a/hugofs/glob/glob.go b/hugofs/glob/glob.go
index 57115ddfa..6dd0df5ed 100644
--- a/hugofs/glob/glob.go
+++ b/hugofs/glob/glob.go
@@ -16,6 +16,7 @@ package glob
import (
"path"
"path/filepath"
+ "runtime"
"strings"
"sync"
@@ -23,46 +24,100 @@ import (
"github.com/gobwas/glob/syntax"
)
+var (
+ isWindows = runtime.GOOS == "windows"
+ defaultGlobCache = &globCache{
+ isCaseSensitive: false,
+ isWindows: isWindows,
+ cache: make(map[string]globErr),
+ }
+
+ filenamesGlobCache = &globCache{
+ isCaseSensitive: true, // TODO(bep) bench
+ isWindows: isWindows,
+ cache: make(map[string]globErr),
+ }
+)
+
type globErr struct {
glob glob.Glob
err error
}
-var (
- globCache = make(map[string]globErr)
- globMu sync.RWMutex
-)
+type globCache struct {
+ // Config
+ isCaseSensitive bool
+ isWindows bool
-type caseInsensitiveGlob struct {
- g glob.Glob
+ // Cache
+ sync.RWMutex
+ cache map[string]globErr
}
-func (g caseInsensitiveGlob) Match(s string) bool {
- return g.g.Match(strings.ToLower(s))
-
-}
-func GetGlob(pattern string) (glob.Glob, error) {
+func (gc *globCache) GetGlob(pattern string) (glob.Glob, error) {
var eg globErr
- globMu.RLock()
+ gc.RLock()
var found bool
- eg, found = globCache[pattern]
- globMu.RUnlock()
+ eg, found = gc.cache[pattern]
+ gc.RUnlock()
if found {
return eg.glob, eg.err
}
+ var g glob.Glob
var err error
- g, err := glob.Compile(strings.ToLower(pattern), '/')
- eg = globErr{caseInsensitiveGlob{g: g}, err}
- globMu.Lock()
- globCache[pattern] = eg
- globMu.Unlock()
+ pattern = filepath.ToSlash(pattern)
+
+ if gc.isCaseSensitive {
+ g, err = glob.Compile(pattern, '/')
+ } else {
+ g, err = glob.Compile(strings.ToLower(pattern), '/')
+
+ }
+
+ eg = globErr{
+ globDecorator{
+ g: g,
+ isCaseSensitive: gc.isCaseSensitive,
+ isWindows: gc.isWindows},
+ err,
+ }
+
+ gc.Lock()
+ gc.cache[pattern] = eg
+ gc.Unlock()
return eg.glob, eg.err
}
+type globDecorator struct {
+ // Whether both pattern and the strings to match will be matched
+ // by their original case.
+ isCaseSensitive bool
+
+ // On Windows we may get filenames with Windows slashes to match,
+ // which wee need to normalize.
+ isWindows bool
+
+ g glob.Glob
+}
+
+func (g globDecorator) Match(s string) bool {
+ if g.isWindows {
+ s = filepath.ToSlash(s)
+ }
+ if !g.isCaseSensitive {
+ s = strings.ToLower(s)
+ }
+ return g.g.Match(s)
+}
+
+func GetGlob(pattern string) (glob.Glob, error) {
+ return defaultGlobCache.GetGlob(pattern)
+}
+
func NormalizePath(p string) string {
return strings.Trim(path.Clean(filepath.ToSlash(strings.ToLower(p))), "/.")
}
@@ -106,3 +161,78 @@ func HasGlobChar(s string) bool {
}
return false
}
+
+type FilenameFilter struct {
+ shouldInclude func(filename string) bool
+ inclusions []glob.Glob
+ exclusions []glob.Glob
+ isWindows bool
+}
+
+// NewFilenameFilter creates a new Glob where the Match method will
+// return true if the file should be exluded.
+// Note that the inclusions will be checked first.
+func NewFilenameFilter(inclusions, exclusions []string) (*FilenameFilter, error) {
+ filter := &FilenameFilter{isWindows: isWindows}
+
+ for _, include := range inclusions {
+ g, err := filenamesGlobCache.GetGlob(filepath.FromSlash(include))
+ if err != nil {
+ return nil, err
+ }
+ filter.inclusions = append(filter.inclusions, g)
+ }
+ for _, exclude := range exclusions {
+ g, err := filenamesGlobCache.GetGlob(filepath.FromSlash(exclude))
+ if err != nil {
+ return nil, err
+ }
+ filter.exclusions = append(filter.exclusions, g)
+ }
+
+ return filter, nil
+}
+
+// NewFilenameFilterForInclusionFunc create a new filter using the provided inclusion func.
+func NewFilenameFilterForInclusionFunc(shouldInclude func(filename string) bool) *FilenameFilter {
+ return &FilenameFilter{shouldInclude: shouldInclude, isWindows: isWindows}
+}
+
+// Match returns whether filename should be included.
+func (f *FilenameFilter) Match(filename string) bool {
+ if f == nil {
+ return true
+ }
+
+ if f.shouldInclude != nil {
+ if f.shouldInclude(filename) {
+ return true
+ }
+ if f.isWindows {
+ // The Glob matchers below handles this by themselves,
+ // for the shouldInclude we need to take some extra steps
+ // to make this robust.
+ winFilename := filepath.FromSlash(filename)
+ if filename != winFilename {
+ if f.shouldInclude(winFilename) {
+ return true
+ }
+ }
+ }
+
+ }
+
+ for _, inclusion := range f.inclusions {
+ if inclusion.Match(filename) {
+ return true
+ }
+ }
+
+ for _, exclusion := range f.exclusions {
+ if exclusion.Match(filename) {
+ return false
+ }
+ }
+
+ return f.inclusions == nil && f.shouldInclude == nil
+}
diff --git a/hugofs/glob/glob_test.go b/hugofs/glob/glob_test.go
index cd64ba112..7ef3fbbed 100644
--- a/hugofs/glob/glob_test.go
+++ b/hugofs/glob/glob_test.go
@@ -15,6 +15,7 @@ package glob
import (
"path/filepath"
+ "strings"
"testing"
qt "github.com/frankban/quicktest"
@@ -72,6 +73,40 @@ func TestGetGlob(t *testing.T) {
c.Assert(g.Match("data/my.json"), qt.Equals, true)
}
+func TestFilenameFilter(t *testing.T) {
+ c := qt.New(t)
+
+ excludeAlmostAllJSON, err := NewFilenameFilter([]string{"a/b/c/foo.json"}, []string{"**.json"})
+ c.Assert(err, qt.IsNil)
+ c.Assert(excludeAlmostAllJSON.Match(filepath.FromSlash("data/my.json")), qt.Equals, false)
+ c.Assert(excludeAlmostAllJSON.Match(filepath.FromSlash("a/b/c/foo.json")), qt.Equals, true)
+ c.Assert(excludeAlmostAllJSON.Match(filepath.FromSlash("a/b/c/foo.bar")), qt.Equals, false)
+
+ nopFilter, err := NewFilenameFilter(nil, nil)
+ c.Assert(err, qt.IsNil)
+ c.Assert(nopFilter.Match("ab.txt"), qt.Equals, true)
+
+ includeOnlyFilter, err := NewFilenameFilter([]string{"**.json", "**.jpg"}, nil)
+ c.Assert(err, qt.IsNil)
+ c.Assert(includeOnlyFilter.Match("ab.json"), qt.Equals, true)
+ c.Assert(includeOnlyFilter.Match("ab.jpg"), qt.Equals, true)
+ c.Assert(includeOnlyFilter.Match("ab.gif"), qt.Equals, false)
+
+ exlcudeOnlyFilter, err := NewFilenameFilter(nil, []string{"**.json", "**.jpg"})
+ c.Assert(err, qt.IsNil)
+ c.Assert(exlcudeOnlyFilter.Match("ab.json"), qt.Equals, false)
+ c.Assert(exlcudeOnlyFilter.Match("ab.jpg"), qt.Equals, false)
+ c.Assert(exlcudeOnlyFilter.Match("ab.gif"), qt.Equals, true)
+
+ var nilFilter *FilenameFilter
+ c.Assert(nilFilter.Match("ab.gif"), qt.Equals, true)
+
+ funcFilter := NewFilenameFilterForInclusionFunc(func(s string) bool { return strings.HasSuffix(s, ".json") })
+ c.Assert(funcFilter.Match("ab.json"), qt.Equals, true)
+ c.Assert(funcFilter.Match("ab.bson"), qt.Equals, false)
+
+}
+
func BenchmarkGetGlob(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := GetGlob("**/foo")