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