github.com/zoumo/helm@v2.5.0+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  }