filename_filter.go (4186B)
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 "path" 18 "path/filepath" 19 "strings" 20 21 "github.com/gobwas/glob" 22 ) 23 24 type FilenameFilter struct { 25 shouldInclude func(filename string) bool 26 inclusions []glob.Glob 27 dirInclusions []glob.Glob 28 exclusions []glob.Glob 29 isWindows bool 30 } 31 32 func normalizeFilenameGlobPattern(s string) string { 33 // Use Unix separators even on Windows. 34 s = filepath.ToSlash(s) 35 if !strings.HasPrefix(s, "/") { 36 s = "/" + s 37 } 38 return s 39 } 40 41 // NewFilenameFilter creates a new Glob where the Match method will 42 // return true if the file should be included. 43 // Note that the inclusions will be checked first. 44 func NewFilenameFilter(inclusions, exclusions []string) (*FilenameFilter, error) { 45 if inclusions == nil && exclusions == nil { 46 return nil, nil 47 } 48 filter := &FilenameFilter{isWindows: isWindows} 49 50 for _, include := range inclusions { 51 include = normalizeFilenameGlobPattern(include) 52 g, err := filenamesGlobCache.GetGlob(include) 53 if err != nil { 54 return nil, err 55 } 56 filter.inclusions = append(filter.inclusions, g) 57 58 // For mounts that do directory walking (e.g. content) we 59 // must make sure that all directories up to this inclusion also 60 // gets included. 61 dir := path.Dir(include) 62 parts := strings.Split(dir, "/") 63 for i, _ := range parts { 64 pattern := "/" + filepath.Join(parts[:i+1]...) 65 g, err := filenamesGlobCache.GetGlob(pattern) 66 if err != nil { 67 return nil, err 68 } 69 filter.dirInclusions = append(filter.dirInclusions, g) 70 } 71 } 72 73 for _, exclude := range exclusions { 74 exclude = normalizeFilenameGlobPattern(exclude) 75 g, err := filenamesGlobCache.GetGlob(exclude) 76 if err != nil { 77 return nil, err 78 } 79 filter.exclusions = append(filter.exclusions, g) 80 } 81 82 return filter, nil 83 } 84 85 // MustNewFilenameFilter invokes NewFilenameFilter and panics on error. 86 func MustNewFilenameFilter(inclusions, exclusions []string) *FilenameFilter { 87 filter, err := NewFilenameFilter(inclusions, exclusions) 88 if err != nil { 89 panic(err) 90 } 91 return filter 92 } 93 94 // NewFilenameFilterForInclusionFunc create a new filter using the provided inclusion func. 95 func NewFilenameFilterForInclusionFunc(shouldInclude func(filename string) bool) *FilenameFilter { 96 return &FilenameFilter{shouldInclude: shouldInclude, isWindows: isWindows} 97 } 98 99 // Match returns whether filename should be included. 100 func (f *FilenameFilter) Match(filename string, isDir bool) bool { 101 if f == nil { 102 return true 103 } 104 return f.doMatch(filename, isDir) 105 /*if f.shouldInclude == nil { 106 fmt.Printf("Match: %q (%t) => %t\n", filename, isDir, isMatch) 107 } 108 return isMatch*/ 109 } 110 111 func (f *FilenameFilter) doMatch(filename string, isDir bool) bool { 112 if f == nil { 113 return true 114 } 115 116 if !strings.HasPrefix(filename, filepathSeparator) { 117 filename = filepathSeparator + filename 118 } 119 120 if f.shouldInclude != nil { 121 if f.shouldInclude(filename) { 122 return true 123 } 124 if f.isWindows { 125 // The Glob matchers below handles this by themselves, 126 // for the shouldInclude we need to take some extra steps 127 // to make this robust. 128 winFilename := filepath.FromSlash(filename) 129 if filename != winFilename { 130 if f.shouldInclude(winFilename) { 131 return true 132 } 133 } 134 } 135 136 } 137 138 for _, inclusion := range f.inclusions { 139 if inclusion.Match(filename) { 140 return true 141 } 142 } 143 144 if isDir && f.inclusions != nil { 145 for _, inclusion := range f.dirInclusions { 146 if inclusion.Match(filename) { 147 return true 148 } 149 } 150 } 151 152 for _, exclusion := range f.exclusions { 153 if exclusion.Match(filename) { 154 return false 155 } 156 } 157 158 return f.inclusions == nil && f.shouldInclude == nil 159 }