package ignore import ( "bufio" "errors" "io" "log" "os" "path/filepath" "strings" ) // HelmIgnore default name of an ignorefile. const HelmIgnore = ".helmignore" // Rules is a collection of path matching rules. // // Parse() and ParseFile() will construct and populate new Rules. // Empty() will create an immutable empty ruleset. type Rules struct { patterns []*pattern } // Empty builds an empty ruleset. func Empty() *Rules { return &Rules{patterns: []*pattern{}} } // ParseFile parses a helmignore file and returns the *Rules. func ParseFile(file string) (*Rules, error) { f, err := os.Open(file) if err != nil { return nil, err } defer f.Close() return Parse(f) } // Parse parses a rules file func Parse(file io.Reader) (*Rules, error) { r := &Rules{patterns: []*pattern{}} s := bufio.NewScanner(file) for s.Scan() { if err := r.parseRule(s.Text()); err != nil { return r, err } } if err := s.Err(); err != nil { return r, err } return r, nil } // Len returns the number of patterns in this rule set. func (r *Rules) Len() int { return len(r.patterns) } // Ignore evalutes the file at the given path, and returns true if it should be ignored. // // Ignore evaluates path against the rules in order. Evaluation stops when a match // is found. Matching a negative rule will stop evaluation. func (r *Rules) Ignore(path string, fi os.FileInfo) bool { for _, p := range r.patterns { if p.match == nil { log.Printf("ignore: no matcher supplied for %q", p.raw) return false } // For negative rules, we need to capture and return non-matches, // and continue for matches. if p.negate { if p.mustDir && !fi.IsDir() { return true } if !p.match(path, fi) { return true } continue } if p.mustDir && !fi.IsDir() { return false } if p.match(path, fi) { return true } } return false } // parseRule parses a rule string and creates a pattern, which is then stored in the Rules object. func (r *Rules) parseRule(rule string) error { rule = strings.TrimSpace(rule) // Ignore blank lines if rule == "" { return nil } // Comment if strings.HasPrefix(rule, "#") { return nil } // Fail any rules that contain ** if strings.Contains(rule, "**") { return errors.New("double-star (**) syntax is not supported") } // Fail any patterns that can't compile. A non-empty string must be // given to Match() to avoid optimization that skips rule evaluation. if _, err := filepath.Match(rule, "abc"); err != nil { return err } p := &pattern{raw: rule} // Negation is handled at a higher level, so strip the leading ! from the // string. if strings.HasPrefix(rule, "!") { p.negate = true rule = rule[1:] } // Directory verification is handled by a higher level, so the trailing / // is removed from the rule. That way, a directory named "foo" matches, // even if the supplied string does not contain a literal slash character. if strings.HasSuffix(rule, "/") { p.mustDir = true rule = strings.TrimSuffix(rule, "/") } if strings.HasPrefix(rule, "/") { // Require path matches the root path. p.match = func(n string, fi os.FileInfo) bool { rule = strings.TrimPrefix(rule, "/") ok, err := filepath.Match(rule, n) if err != nil { log.Printf("Failed to compile %q: %s", rule, err) return false } return ok } } else if strings.Contains(rule, "/") { // require structural match. p.match = func(n string, fi os.FileInfo) bool { ok, err := filepath.Match(rule, n) if err != nil { log.Printf("Failed to compile %q: %s", rule, err) return false } return ok } } else { p.match = func(n string, fi os.FileInfo) bool { // When there is no slash in the pattern, we evaluate ONLY the // filename. n = filepath.Base(n) ok, err := filepath.Match(rule, n) if err != nil { log.Printf("Failed to compile %q: %s", rule, err) return false } return ok } } r.patterns = append(r.patterns, p) return nil } // matcher is a function capable of computing a match. // // It returns true if the rule matches. type matcher func(name string, fi os.FileInfo) bool // pattern describes a pattern to be matched in a rule set. type pattern struct { // raw is the unparsed string, with nothing stripped. raw string // match is the matcher function. match matcher // negate indicates that the rule's outcome should be negated. negate bool // mustDir indicates that the matched file must be a directory. mustDir bool }