github.com/keleustes/helm@v3.0.0-beta.3+incompatible/internal/ignore/rules.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package ignore 18 19 import ( 20 "bufio" 21 "io" 22 "log" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "github.com/pkg/errors" 28 ) 29 30 // HelmIgnore default name of an ignorefile. 31 const HelmIgnore = ".helmignore" 32 33 // Rules is a collection of path matching rules. 34 // 35 // Parse() and ParseFile() will construct and populate new Rules. 36 // Empty() will create an immutable empty ruleset. 37 type Rules struct { 38 patterns []*pattern 39 } 40 41 // Empty builds an empty ruleset. 42 func Empty() *Rules { 43 return &Rules{patterns: []*pattern{}} 44 } 45 46 // AddDefaults adds default ignore patterns. 47 // 48 // Ignore all dotfiles in "templates/" 49 func (r *Rules) AddDefaults() { 50 r.parseRule(`templates/.?*`) 51 } 52 53 // ParseFile parses a helmignore file and returns the *Rules. 54 func ParseFile(file string) (*Rules, error) { 55 f, err := os.Open(file) 56 if err != nil { 57 return nil, err 58 } 59 defer f.Close() 60 return Parse(f) 61 } 62 63 // Parse parses a rules file 64 func Parse(file io.Reader) (*Rules, error) { 65 r := &Rules{patterns: []*pattern{}} 66 67 s := bufio.NewScanner(file) 68 for s.Scan() { 69 if err := r.parseRule(s.Text()); err != nil { 70 return r, err 71 } 72 } 73 return r, s.Err() 74 } 75 76 // Ignore evalutes the file at the given path, and returns true if it should be ignored. 77 // 78 // Ignore evaluates path against the rules in order. Evaluation stops when a match 79 // is found. Matching a negative rule will stop evaluation. 80 func (r *Rules) Ignore(path string, fi os.FileInfo) bool { 81 // Don't match on empty dirs. 82 if path == "" { 83 return false 84 } 85 86 // Disallow ignoring the current working directory. 87 // See issue: 88 // 1776 (New York City) Hamilton: "Pardon me, are you Aaron Burr, sir?" 89 if path == "." || path == "./" { 90 return false 91 } 92 for _, p := range r.patterns { 93 if p.match == nil { 94 log.Printf("ignore: no matcher supplied for %q", p.raw) 95 return false 96 } 97 98 // For negative rules, we need to capture and return non-matches, 99 // and continue for matches. 100 if p.negate { 101 if p.mustDir && !fi.IsDir() { 102 return true 103 } 104 if !p.match(path, fi) { 105 return true 106 } 107 continue 108 } 109 110 // If the rule is looking for directories, and this is not a directory, 111 // skip it. 112 if p.mustDir && !fi.IsDir() { 113 continue 114 } 115 if p.match(path, fi) { 116 return true 117 } 118 } 119 return false 120 } 121 122 // parseRule parses a rule string and creates a pattern, which is then stored in the Rules object. 123 func (r *Rules) parseRule(rule string) error { 124 rule = strings.TrimSpace(rule) 125 126 // Ignore blank lines 127 if rule == "" { 128 return nil 129 } 130 // Comment 131 if strings.HasPrefix(rule, "#") { 132 return nil 133 } 134 135 // Fail any rules that contain ** 136 if strings.Contains(rule, "**") { 137 return errors.New("double-star (**) syntax is not supported") 138 } 139 140 // Fail any patterns that can't compile. A non-empty string must be 141 // given to Match() to avoid optimization that skips rule evaluation. 142 if _, err := filepath.Match(rule, "abc"); err != nil { 143 return err 144 } 145 146 p := &pattern{raw: rule} 147 148 // Negation is handled at a higher level, so strip the leading ! from the 149 // string. 150 if strings.HasPrefix(rule, "!") { 151 p.negate = true 152 rule = rule[1:] 153 } 154 155 // Directory verification is handled by a higher level, so the trailing / 156 // is removed from the rule. That way, a directory named "foo" matches, 157 // even if the supplied string does not contain a literal slash character. 158 if strings.HasSuffix(rule, "/") { 159 p.mustDir = true 160 rule = strings.TrimSuffix(rule, "/") 161 } 162 163 if strings.HasPrefix(rule, "/") { 164 // Require path matches the root path. 165 p.match = func(n string, fi os.FileInfo) bool { 166 rule = strings.TrimPrefix(rule, "/") 167 ok, err := filepath.Match(rule, n) 168 if err != nil { 169 log.Printf("Failed to compile %q: %s", rule, err) 170 return false 171 } 172 return ok 173 } 174 } else if strings.Contains(rule, "/") { 175 // require structural match. 176 p.match = func(n string, fi os.FileInfo) bool { 177 ok, err := filepath.Match(rule, n) 178 if err != nil { 179 log.Printf("Failed to compile %q: %s", rule, err) 180 return false 181 } 182 return ok 183 } 184 } else { 185 p.match = func(n string, fi os.FileInfo) bool { 186 // When there is no slash in the pattern, we evaluate ONLY the 187 // filename. 188 n = filepath.Base(n) 189 ok, err := filepath.Match(rule, n) 190 if err != nil { 191 log.Printf("Failed to compile %q: %s", rule, err) 192 return false 193 } 194 return ok 195 } 196 } 197 198 r.patterns = append(r.patterns, p) 199 return nil 200 } 201 202 // matcher is a function capable of computing a match. 203 // 204 // It returns true if the rule matches. 205 type matcher func(name string, fi os.FileInfo) bool 206 207 // pattern describes a pattern to be matched in a rule set. 208 type pattern struct { 209 // raw is the unparsed string, with nothing stripped. 210 raw string 211 // match is the matcher function. 212 match matcher 213 // negate indicates that the rule's outcome should be negated. 214 negate bool 215 // mustDir indicates that the matched file must be a directory. 216 mustDir bool 217 }