mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
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" }} ```
This commit is contained in:
parent
f3560aa0e1
commit
281554ee97
4 changed files with 54 additions and 23 deletions
|
@ -26,14 +26,14 @@ import (
|
||||||
// Glob walks the fs and passes all matches to the handle func.
|
// Glob walks the fs and passes all matches to the handle func.
|
||||||
// The handle func can return true to signal a stop.
|
// The handle func can return true to signal a stop.
|
||||||
func Glob(fs afero.Fs, pattern string, handle func(fi FileMetaInfo) (bool, error)) error {
|
func Glob(fs afero.Fs, pattern string, handle func(fi FileMetaInfo) (bool, error)) error {
|
||||||
pattern = glob.NormalizePath(pattern)
|
pattern = glob.NormalizePathCaseSensitive(pattern)
|
||||||
if pattern == "" {
|
if pattern == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := glob.GetGlob(pattern)
|
g, err := glob.GetFilenamesGlob(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasSuperAsterisk := strings.Contains(pattern, "**")
|
hasSuperAsterisk := strings.Contains(pattern, "**")
|
||||||
|
|
|
@ -30,15 +30,13 @@ const filepathSeparator = string(os.PathSeparator)
|
||||||
var (
|
var (
|
||||||
isWindows = runtime.GOOS == "windows"
|
isWindows = runtime.GOOS == "windows"
|
||||||
defaultGlobCache = &globCache{
|
defaultGlobCache = &globCache{
|
||||||
isCaseSensitive: false,
|
isWindows: isWindows,
|
||||||
isWindows: isWindows,
|
cache: make(map[string]globErr),
|
||||||
cache: make(map[string]globErr),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filenamesGlobCache = &globCache{
|
filenamesGlobCache = &globCache{
|
||||||
isCaseSensitive: false, // As long as the search strings are all lower case, this does not allocate.
|
isWindows: isWindows,
|
||||||
isWindows: isWindows,
|
cache: make(map[string]globErr),
|
||||||
cache: make(map[string]globErr),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,8 +47,7 @@ type globErr struct {
|
||||||
|
|
||||||
type globCache struct {
|
type globCache struct {
|
||||||
// Config
|
// Config
|
||||||
isCaseSensitive bool
|
isWindows bool
|
||||||
isWindows bool
|
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
@ -72,19 +69,12 @@ func (gc *globCache) GetGlob(pattern string) (glob.Glob, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
pattern = filepath.ToSlash(pattern)
|
pattern = filepath.ToSlash(pattern)
|
||||||
|
g, err = glob.Compile(strings.ToLower(pattern), '/')
|
||||||
if gc.isCaseSensitive {
|
|
||||||
g, err = glob.Compile(pattern, '/')
|
|
||||||
} else {
|
|
||||||
g, err = glob.Compile(strings.ToLower(pattern), '/')
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
eg = globErr{
|
eg = globErr{
|
||||||
globDecorator{
|
globDecorator{
|
||||||
g: g,
|
g: g,
|
||||||
isCaseSensitive: gc.isCaseSensitive,
|
isWindows: gc.isWindows},
|
||||||
isWindows: gc.isWindows},
|
|
||||||
err,
|
err,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,14 +107,50 @@ func (g globDecorator) Match(s string) bool {
|
||||||
return g.g.Match(s)
|
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) {
|
func GetGlob(pattern string) (glob.Glob, error) {
|
||||||
return defaultGlobCache.GetGlob(pattern)
|
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 {
|
func NormalizePath(p string) string {
|
||||||
return strings.Trim(path.Clean(filepath.ToSlash(strings.ToLower(p))), "/.")
|
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
|
// ResolveRootDir takes a normalized path on the form "assets/**.json" and
|
||||||
// determines any root dir, i.e. any start path without any wildcards.
|
// determines any root dir, i.e. any start path without any wildcards.
|
||||||
func ResolveRootDir(p string) string {
|
func ResolveRootDir(p string) string {
|
||||||
|
|
|
@ -49,12 +49,17 @@ func TestGlob(t *testing.T) {
|
||||||
create("jsonfiles/sub/d3.json")
|
create("jsonfiles/sub/d3.json")
|
||||||
create("jsonfiles/d1.xml")
|
create("jsonfiles/d1.xml")
|
||||||
create("a/b/c/e/f.json")
|
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("**.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(""), qt.HasLen, 0)
|
||||||
c.Assert(collect("jsonfiles/*.json"), qt.HasLen, 2)
|
c.Assert(collect("jsonfiles/*.json"), qt.HasLen, 2)
|
||||||
c.Assert(collect("*.json"), qt.HasLen, 1)
|
c.Assert(collect("*.json"), qt.HasLen, 1)
|
||||||
c.Assert(collect("**.xml"), qt.HasLen, 1)
|
c.Assert(collect("**.xml"), qt.HasLen, 1)
|
||||||
c.Assert(collect(filepath.FromSlash("/jsonfiles/*.json")), qt.HasLen, 2)
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
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, "/"))
|
partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
|
||||||
if len(partitions) == 0 {
|
if len(partitions) == 0 {
|
||||||
partitions = []string{resources.CACHE_OTHER}
|
partitions = []string{resources.CACHE_OTHER}
|
||||||
|
|
Loading…
Reference in a new issue