]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
hugofs: Fix glob case-sensitivity bug
authorsatotake <doublequotation@gmail.com>
Wed, 21 Sep 2022 15:01:54 +0000 (15:01 +0000)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 23 Sep 2022 11:12:57 +0000 (13:12 +0200)
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" }}
```

hugofs/glob.go
hugofs/glob/glob.go
hugofs/glob_test.go
resources/resource_factories/create/create.go

index 147b6b9f134b657b079895f0d24181953dfbf4db..e691cdc10545c0ccff4f4c3733bce9f2adda202e 100644 (file)
@@ -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, "**")
index 9e928ec32349518fa02f07e656b3ab41b595fe04..87619802e6f2ebdb1408b8db6127a1463b3ed418 100644 (file)
@@ -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 {
index 29cd1e0ca4f038b10664478ebb5713927813986d..fbb276445d4d973e6c05b1ed783ec8162624cac7 100644 (file)
@@ -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)
+
 }
index 075d257362bd6eeaa39dc3115581fc0e6f833c81..508c141f6b1a32d9dcd63d59938d20bc8d34be4c 100644 (file)
@@ -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}