github.com/SuCicada/su-hugo@v1.0.0/hugofs/glob/glob.go (about) 1 // Copyright 2021 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package glob 15 16 import ( 17 "os" 18 "path" 19 "path/filepath" 20 "runtime" 21 "strings" 22 "sync" 23 24 "github.com/gobwas/glob" 25 "github.com/gobwas/glob/syntax" 26 ) 27 28 const filepathSeparator = string(os.PathSeparator) 29 30 var ( 31 isWindows = runtime.GOOS == "windows" 32 defaultGlobCache = &globCache{ 33 isWindows: isWindows, 34 cache: make(map[string]globErr), 35 } 36 ) 37 38 type globErr struct { 39 glob glob.Glob 40 err error 41 } 42 43 type globCache struct { 44 // Config 45 isWindows bool 46 47 // Cache 48 sync.RWMutex 49 cache map[string]globErr 50 } 51 52 func (gc *globCache) GetGlob(pattern string) (glob.Glob, error) { 53 var eg globErr 54 55 gc.RLock() 56 var found bool 57 eg, found = gc.cache[pattern] 58 gc.RUnlock() 59 if found { 60 return eg.glob, eg.err 61 } 62 63 var g glob.Glob 64 var err error 65 66 pattern = filepath.ToSlash(pattern) 67 g, err = glob.Compile(strings.ToLower(pattern), '/') 68 69 eg = globErr{ 70 globDecorator{ 71 g: g, 72 isWindows: gc.isWindows}, 73 err, 74 } 75 76 gc.Lock() 77 gc.cache[pattern] = eg 78 gc.Unlock() 79 80 return eg.glob, eg.err 81 } 82 83 type globDecorator struct { 84 // On Windows we may get filenames with Windows slashes to match, 85 // which wee need to normalize. 86 isWindows bool 87 88 g glob.Glob 89 } 90 91 func (g globDecorator) Match(s string) bool { 92 if g.isWindows { 93 s = filepath.ToSlash(s) 94 } 95 s = strings.ToLower(s) 96 return g.g.Match(s) 97 } 98 99 type globDecoratorDouble struct { 100 lowerCase glob.Glob 101 originalCase glob.Glob 102 } 103 104 func (g globDecoratorDouble) Match(s string) bool { 105 return g.lowerCase.Match(s) || g.originalCase.Match(s) 106 } 107 108 func GetGlob(pattern string) (glob.Glob, error) { 109 return defaultGlobCache.GetGlob(pattern) 110 } 111 112 func NormalizePath(p string) string { 113 return strings.ToLower(NormalizePathNoLower(p)) 114 } 115 116 func NormalizePathNoLower(p string) string { 117 return strings.Trim(path.Clean(filepath.ToSlash(p)), "/.") 118 } 119 120 // ResolveRootDir takes a normalized path on the form "assets/**.json" and 121 // determines any root dir, i.e. any start path without any wildcards. 122 func ResolveRootDir(p string) string { 123 parts := strings.Split(path.Dir(p), "/") 124 var roots []string 125 for _, part := range parts { 126 if HasGlobChar(part) { 127 break 128 } 129 roots = append(roots, part) 130 } 131 132 if len(roots) == 0 { 133 return "" 134 } 135 136 return strings.Join(roots, "/") 137 } 138 139 // FilterGlobParts removes any string with glob wildcard. 140 func FilterGlobParts(a []string) []string { 141 b := a[:0] 142 for _, x := range a { 143 if !HasGlobChar(x) { 144 b = append(b, x) 145 } 146 } 147 return b 148 } 149 150 // HasGlobChar returns whether s contains any glob wildcards. 151 func HasGlobChar(s string) bool { 152 for i := 0; i < len(s); i++ { 153 if syntax.Special(s[i]) { 154 return true 155 } 156 } 157 return false 158 }