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