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