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  }