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  }