aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorsatotake <[email protected]>2022-09-21 15:01:54 +0000
committerBjørn Erik Pedersen <[email protected]>2022-09-23 13:12:57 +0200
commit281554ee97eb243af097611aaf6bfec8940ad6d1 (patch)
treee7581707bac6026c78ecc77a23e378e2fc30e68e
parentf3560aa0e170f8bbc1e5ab41f1e54bd819da5cc3 (diff)
downloadhugo-281554ee97eb243af097611aaf6bfec8940ad6d1.tar.gz
hugo-281554ee97eb243af097611aaf6bfec8940ad6d1.zip
hugofs: Fix glob case-sensitivity bug
On Linux, `hugofs.Glob` does not hit any directories which includes uppercase letters. (This does not happen on macOS.) Since `resources.GetMatch/Match` uses `Glob`, ``` {{ resources.GetMatch "Foo/bar.css" }} ``` this does not match `assets/Foo/bar.css` . On the other hand, you can get it with ``` {{ resources.Get "Foo/bar.css" }} ```
-rw-r--r--hugofs/glob.go6
-rw-r--r--hugofs/glob/glob.go62
-rw-r--r--hugofs/glob_test.go7
-rw-r--r--resources/resource_factories/create/create.go2
4 files changed, 54 insertions, 23 deletions
diff --git a/hugofs/glob.go b/hugofs/glob.go
index 147b6b9f1..e691cdc10 100644
--- a/hugofs/glob.go
+++ b/hugofs/glob.go
@@ -26,14 +26,14 @@ import (
// Glob walks the fs and passes all matches to the handle func.
// The handle func can return true to signal a stop.
func Glob(fs afero.Fs, pattern string, handle func(fi FileMetaInfo) (bool, error)) error {
- pattern = glob.NormalizePath(pattern)
+ pattern = glob.NormalizePathCaseSensitive(pattern)
if pattern == "" {
return nil
}
- g, err := glob.GetGlob(pattern)
+ g, err := glob.GetFilenamesGlob(pattern)
if err != nil {
- return nil
+ return err
}
hasSuperAsterisk := strings.Contains(pattern, "**")
diff --git a/hugofs/glob/glob.go b/hugofs/glob/glob.go
index 9e928ec32..87619802e 100644
--- a/hugofs/glob/glob.go
+++ b/hugofs/glob/glob.go
@@ -30,15 +30,13 @@ const filepathSeparator = string(os.PathSeparator)
var (
isWindows = runtime.GOOS == "windows"
defaultGlobCache = &globCache{
- isCaseSensitive: false,
- isWindows: isWindows,
- cache: make(map[string]globErr),
+ isWindows: isWindows,
+ cache: make(map[string]globErr),
}
filenamesGlobCache = &globCache{
- isCaseSensitive: false, // As long as the search strings are all lower case, this does not allocate.
- isWindows: isWindows,
- cache: make(map[string]globErr),
+ isWindows: isWindows,
+ cache: make(map[string]globErr),
}
)
@@ -49,8 +47,7 @@ type globErr struct {
type globCache struct {
// Config
- isCaseSensitive bool
- isWindows bool
+ isWindows bool
// Cache
sync.RWMutex
@@ -72,19 +69,12 @@ func (gc *globCache) GetGlob(pattern string) (glob.Glob, error) {
var err error
pattern = filepath.ToSlash(pattern)
-
- if gc.isCaseSensitive {
- g, err = glob.Compile(pattern, '/')
- } else {
- g, err = glob.Compile(strings.ToLower(pattern), '/')
-
- }
+ g, err = glob.Compile(strings.ToLower(pattern), '/')
eg = globErr{
globDecorator{
- g: g,
- isCaseSensitive: gc.isCaseSensitive,
- isWindows: gc.isWindows},
+ g: g,
+ isWindows: gc.isWindows},
err,
}
@@ -117,14 +107,50 @@ func (g globDecorator) Match(s string) bool {
return g.g.Match(s)
}
+type globDecoratorDouble struct {
+ lowerCase glob.Glob
+ originalCase glob.Glob
+}
+
+func (g globDecoratorDouble) Match(s string) bool {
+ return g.lowerCase.Match(s) || g.originalCase.Match(s)
+}
+
func GetGlob(pattern string) (glob.Glob, error) {
return defaultGlobCache.GetGlob(pattern)
}
+func GetFilenamesGlob(pattern string) (glob.Glob, error) {
+ lowered := strings.ToLower(pattern)
+ hasUpperCase := pattern != lowered
+ gLowered, err := filenamesGlobCache.GetGlob(lowered)
+ if err != nil {
+ return nil, err
+ }
+
+ if !hasUpperCase {
+ return gLowered, nil
+ }
+
+ gSensitive, err := filenamesGlobCache.GetGlob(pattern)
+ if err != nil {
+ return nil, err
+ }
+ return globDecoratorDouble{
+ lowerCase: gLowered,
+ originalCase: gSensitive,
+ }, nil
+
+}
+
func NormalizePath(p string) string {
return strings.Trim(path.Clean(filepath.ToSlash(strings.ToLower(p))), "/.")
}
+func NormalizePathCaseSensitive(p string) string {
+ return strings.Trim(path.Clean(filepath.ToSlash(p)), "/.")
+}
+
// ResolveRootDir takes a normalized path on the form "assets/**.json" and
// determines any root dir, i.e. any start path without any wildcards.
func ResolveRootDir(p string) string {
diff --git a/hugofs/glob_test.go b/hugofs/glob_test.go
index 29cd1e0ca..fbb276445 100644
--- a/hugofs/glob_test.go
+++ b/hugofs/glob_test.go
@@ -49,12 +49,17 @@ func TestGlob(t *testing.T) {
create("jsonfiles/sub/d3.json")
create("jsonfiles/d1.xml")
create("a/b/c/e/f.json")
+ create("UPPER/sub/style.css")
+ create("root/UPPER/sub/style.css")
c.Assert(collect("**.json"), qt.HasLen, 5)
- c.Assert(collect("**"), qt.HasLen, 6)
+ c.Assert(collect("**"), qt.HasLen, 8)
c.Assert(collect(""), qt.HasLen, 0)
c.Assert(collect("jsonfiles/*.json"), qt.HasLen, 2)
c.Assert(collect("*.json"), qt.HasLen, 1)
c.Assert(collect("**.xml"), qt.HasLen, 1)
c.Assert(collect(filepath.FromSlash("/jsonfiles/*.json")), qt.HasLen, 2)
+ c.Assert(collect("UPPER/sub/style.css"), qt.HasLen, 1)
+ c.Assert(collect("root/UPPER/sub/style.css"), qt.HasLen, 1)
+
}
diff --git a/resources/resource_factories/create/create.go b/resources/resource_factories/create/create.go
index 075d25736..508c141f6 100644
--- a/resources/resource_factories/create/create.go
+++ b/resources/resource_factories/create/create.go
@@ -93,7 +93,7 @@ func (c *Client) GetMatch(pattern string) (resource.Resource, error) {
}
func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource) bool, firstOnly bool) (resource.Resources, error) {
- pattern = glob.NormalizePath(pattern)
+ pattern = glob.NormalizePathCaseSensitive(pattern)
partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
if len(partitions) == 0 {
partitions = []string{resources.CACHE_OTHER}