github.com/appscode/helm@v3.0.0-alpha.1+incompatible/pkg/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 // Len returns the number of patterns in this rule set. 77 func (r *Rules) Len() int { 78 return len(r.patterns) 79 } 80 81 // Ignore evalutes the file at the given path, and returns true if it should be ignored. 82 // 83 // Ignore evaluates path against the rules in order. Evaluation stops when a match 84 // is found. Matching a negative rule will stop evaluation. 85 func (r *Rules) Ignore(path string, fi os.FileInfo) bool { 86 // Don't match on empty dirs. 87 if path == "" { 88 return false 89 } 90 91 // Disallow ignoring the current working directory. 92 // See issue: 93 // 1776 (New York City) Hamilton: "Pardon me, are you Aaron Burr, sir?" 94 if path == "." || path == "./" { 95 return false 96 } 97 for _, p := range r.patterns { 98 if p.match == nil { 99 log.Printf("ignore: no matcher supplied for %q", p.raw) 100 return false 101 } 102 103 // For negative rules, we need to capture and return non-matches, 104 // and continue for matches. 105 if p.negate { 106 if p.mustDir && !fi.IsDir() { 107 return true 108 } 109 if !p.match(path, fi) { 110 return true 111 } 112 continue 113 } 114 115 // If the rule is looking for directories, and this is not a directory, 116 // skip it. 117 if p.mustDir && !fi.IsDir() { 118 continue 119 } 120 if p.match(path, fi) { 121 return true 122 } 123 } 124 return false 125 } 126 127 // parseRule parses a rule string and creates a pattern, which is then stored in the Rules object. 128 func (r *Rules) parseRule(rule string) error { 129 rule = strings.TrimSpace(rule) 130 131 // Ignore blank lines 132 if rule == "" { 133 return nil 134 } 135 // Comment 136 if strings.HasPrefix(rule, "#") { 137 return nil 138 } 139 140 // Fail any rules that contain ** 141 if strings.Contains(rule, "**") { 142 return errors.New("double-star (**) syntax is not supported") 143 } 144 145 // Fail any patterns that can't compile. A non-empty string must be 146 // given to Match() to avoid optimization that skips rule evaluation. 147 if _, err := filepath.Match(rule, "abc"); err != nil { 148 return err 149 } 150 151 p := &pattern{raw: rule} 152 153 // Negation is handled at a higher level, so strip the leading ! from the 154 // string. 155 if strings.HasPrefix(rule, "!") { 156 p.negate = true 157 rule = rule[1:] 158 } 159 160 // Directory verification is handled by a higher level, so the trailing / 161 // is removed from the rule. That way, a directory named "foo" matches, 162 // even if the supplied string does not contain a literal slash character. 163 if strings.HasSuffix(rule, "/") { 164 p.mustDir = true 165 rule = strings.TrimSuffix(rule, "/") 166 } 167 168 if strings.HasPrefix(rule, "/") { 169 // Require path matches the root path. 170 p.match = func(n string, fi os.FileInfo) bool { 171 rule = strings.TrimPrefix(rule, "/") 172 ok, err := filepath.Match(rule, n) 173 if err != nil { 174 log.Printf("Failed to compile %q: %s", rule, err) 175 return false 176 } 177 return ok 178 } 179 } else if strings.Contains(rule, "/") { 180 // require structural match. 181 p.match = func(n string, fi os.FileInfo) bool { 182 ok, err := filepath.Match(rule, n) 183 if err != nil { 184 log.Printf("Failed to compile %q: %s", rule, err) 185 return false 186 } 187 return ok 188 } 189 } else { 190 p.match = func(n string, fi os.FileInfo) bool { 191 // When there is no slash in the pattern, we evaluate ONLY the 192 // filename. 193 n = filepath.Base(n) 194 ok, err := filepath.Match(rule, n) 195 if err != nil { 196 log.Printf("Failed to compile %q: %s", rule, err) 197 return false 198 } 199 return ok 200 } 201 } 202 203 r.patterns = append(r.patterns, p) 204 return nil 205 } 206 207 // matcher is a function capable of computing a match. 208 // 209 // It returns true if the rule matches. 210 type matcher func(name string, fi os.FileInfo) bool 211 212 // pattern describes a pattern to be matched in a rule set. 213 type pattern struct { 214 // raw is the unparsed string, with nothing stripped. 215 raw string 216 // match is the matcher function. 217 match matcher 218 // negate indicates that the rule's outcome should be negated. 219 negate bool 220 // mustDir indicates that the matched file must be a directory. 221 mustDir bool 222 }