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