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 }