2016-11-21 18:38:39 +00:00
|
|
|
package filepathfilter
|
|
|
|
|
2016-11-21 21:34:45 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2016-11-21 23:44:41 +00:00
|
|
|
type Pattern interface {
|
2016-11-21 21:34:45 +00:00
|
|
|
Match(filename string) bool
|
|
|
|
}
|
2016-11-21 18:38:39 +00:00
|
|
|
|
|
|
|
type Filter struct {
|
2016-11-21 23:44:41 +00:00
|
|
|
include []Pattern
|
|
|
|
exclude []Pattern
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewFromPatterns(include, exclude []Pattern) *Filter {
|
|
|
|
return &Filter{include: include, exclude: exclude}
|
2016-11-21 18:38:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func New(include, exclude []string) *Filter {
|
2016-11-21 23:44:41 +00:00
|
|
|
return NewFromPatterns(convertToPatterns(include), convertToPatterns(exclude))
|
2016-11-21 18:38:39 +00:00
|
|
|
}
|
|
|
|
|
2016-11-21 21:34:45 +00:00
|
|
|
func (f *Filter) Allows(filename string) bool {
|
2016-11-21 18:38:39 +00:00
|
|
|
if f == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-11-21 21:34:45 +00:00
|
|
|
if len(f.include)+len(f.exclude) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanedName := filepath.Clean(filename)
|
|
|
|
|
|
|
|
if len(f.include) > 0 {
|
|
|
|
matched := false
|
|
|
|
for _, inc := range f.include {
|
|
|
|
matched = inc.Match(cleanedName)
|
|
|
|
if matched {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !matched {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(f.exclude) > 0 {
|
|
|
|
for _, ex := range f.exclude {
|
|
|
|
if ex.Match(cleanedName) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-11-21 23:44:41 +00:00
|
|
|
func NewPattern(rawpattern string) Pattern {
|
2016-11-21 21:57:51 +00:00
|
|
|
cleanpattern := filepath.Clean(rawpattern)
|
|
|
|
|
|
|
|
// Special case local dir, matches all (inc subpaths)
|
|
|
|
if _, local := localDirSet[cleanpattern]; local {
|
|
|
|
return noOpMatcher{}
|
|
|
|
}
|
|
|
|
|
2017-05-18 13:14:10 +00:00
|
|
|
sep := string(filepath.Separator)
|
|
|
|
hasPathSep := strings.Contains(cleanpattern, sep)
|
|
|
|
ext := filepath.Ext(cleanpattern)
|
|
|
|
plen := len(cleanpattern)
|
|
|
|
if plen > 1 && !hasPathSep && strings.HasPrefix(cleanpattern, "*") && cleanpattern[1:plen] == ext {
|
|
|
|
return &simpleExtPattern{ext: ext}
|
|
|
|
}
|
2016-11-21 21:57:51 +00:00
|
|
|
|
|
|
|
// special case * when there are no path separators
|
|
|
|
// filepath.Match never allows * to match a path separator, which is correct
|
|
|
|
// for gitignore IF the pattern includes a path separator, but not otherwise
|
|
|
|
// So *.txt should match in any subdir, as should test*, but sub/*.txt would
|
|
|
|
// only match directly in the sub dir
|
|
|
|
// Don't need to test cross-platform separators as both cleaned above
|
|
|
|
if !hasPathSep && strings.Contains(cleanpattern, "*") {
|
|
|
|
pattern := regexp.QuoteMeta(cleanpattern)
|
|
|
|
regpattern := fmt.Sprintf("^%s$", strings.Replace(pattern, "\\*", ".*", -1))
|
|
|
|
return &pathlessWildcardPattern{
|
|
|
|
rawPattern: cleanpattern,
|
|
|
|
wildcardRE: regexp.MustCompile(regpattern),
|
|
|
|
}
|
2017-05-18 13:14:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Also support ** with path separators
|
|
|
|
if hasPathSep && strings.Contains(cleanpattern, "**") {
|
2016-11-21 21:57:51 +00:00
|
|
|
pattern := regexp.QuoteMeta(cleanpattern)
|
|
|
|
regpattern := fmt.Sprintf("^%s$", strings.Replace(pattern, "\\*\\*", ".*", -1))
|
|
|
|
return &doubleWildcardPattern{
|
|
|
|
rawPattern: cleanpattern,
|
|
|
|
wildcardRE: regexp.MustCompile(regpattern),
|
|
|
|
}
|
2017-05-18 13:14:10 +00:00
|
|
|
}
|
|
|
|
|
2017-05-18 17:27:04 +00:00
|
|
|
if hasPathSep && strings.HasPrefix(cleanpattern, sep) {
|
|
|
|
rel := cleanpattern[1:len(cleanpattern)]
|
|
|
|
prefix := rel
|
|
|
|
if strings.HasSuffix(rel, sep) {
|
|
|
|
rel = rel[0 : len(rel)-1]
|
|
|
|
} else {
|
|
|
|
prefix += sep
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pathPrefixPattern{
|
|
|
|
rawPattern: cleanpattern,
|
|
|
|
relative: rel,
|
|
|
|
prefix: prefix,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-18 13:14:10 +00:00
|
|
|
return &pathPattern{
|
|
|
|
rawPattern: cleanpattern,
|
|
|
|
prefix: cleanpattern + sep,
|
|
|
|
suffix: sep + cleanpattern,
|
|
|
|
inner: sep + cleanpattern + sep,
|
2016-11-21 21:57:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-21 23:44:41 +00:00
|
|
|
func convertToPatterns(rawpatterns []string) []Pattern {
|
|
|
|
patterns := make([]Pattern, len(rawpatterns))
|
2016-11-21 21:34:45 +00:00
|
|
|
for i, raw := range rawpatterns {
|
2016-11-21 23:44:41 +00:00
|
|
|
patterns[i] = NewPattern(raw)
|
2016-11-21 21:34:45 +00:00
|
|
|
}
|
|
|
|
return patterns
|
|
|
|
}
|
|
|
|
|
2017-05-18 17:27:04 +00:00
|
|
|
type pathPrefixPattern struct {
|
|
|
|
rawPattern string
|
|
|
|
relative string
|
|
|
|
prefix string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Match is a revised version of filepath.Match which makes it behave more
|
|
|
|
// like gitignore
|
|
|
|
func (p *pathPrefixPattern) Match(name string) bool {
|
2017-05-18 17:29:25 +00:00
|
|
|
if name == p.relative || strings.HasPrefix(name, p.prefix) {
|
|
|
|
return true
|
|
|
|
}
|
2017-05-18 17:27:04 +00:00
|
|
|
matched, _ := filepath.Match(p.rawPattern, name)
|
2017-05-18 17:29:25 +00:00
|
|
|
return matched
|
2017-05-18 17:27:04 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 23:28:51 +00:00
|
|
|
// String returns a string representation of the underlying pattern for which
|
|
|
|
// this *pathPrefixPattern is matching.
|
|
|
|
func (p *pathPrefixPattern) String() string {
|
|
|
|
return p.rawPattern
|
|
|
|
}
|
|
|
|
|
2017-05-18 13:14:10 +00:00
|
|
|
type pathPattern struct {
|
2016-11-21 21:34:45 +00:00
|
|
|
rawPattern string
|
2017-05-18 12:51:12 +00:00
|
|
|
prefix string
|
|
|
|
suffix string
|
|
|
|
inner string
|
2016-11-21 18:38:39 +00:00
|
|
|
}
|
2016-11-21 21:34:45 +00:00
|
|
|
|
|
|
|
// Match is a revised version of filepath.Match which makes it behave more
|
|
|
|
// like gitignore
|
2017-05-18 13:14:10 +00:00
|
|
|
func (p *pathPattern) Match(name string) bool {
|
2017-05-18 17:29:25 +00:00
|
|
|
if strings.HasPrefix(name, p.prefix) || strings.HasSuffix(name, p.suffix) || strings.Contains(name, p.inner) {
|
|
|
|
return true
|
|
|
|
}
|
2016-11-21 21:34:45 +00:00
|
|
|
matched, _ := filepath.Match(p.rawPattern, name)
|
2017-05-18 17:29:25 +00:00
|
|
|
return matched
|
2016-11-21 21:34:45 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 23:25:01 +00:00
|
|
|
// String returns a string representation of the underlying pattern for which
|
|
|
|
// this *pathPattern is matching.
|
|
|
|
func (p *pathPattern) String() string {
|
|
|
|
return p.rawPattern
|
|
|
|
}
|
|
|
|
|
2017-05-18 13:14:10 +00:00
|
|
|
type simpleExtPattern struct {
|
|
|
|
ext string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *simpleExtPattern) Match(name string) bool {
|
|
|
|
return strings.HasSuffix(name, p.ext)
|
|
|
|
}
|
|
|
|
|
2017-06-20 23:25:19 +00:00
|
|
|
// String returns a string representation of the underlying pattern for which
|
|
|
|
// this *simpleExtPattern is matching.
|
|
|
|
func (p *simpleExtPattern) String() string {
|
|
|
|
return fmt.Sprintf("*%s", p.ext)
|
|
|
|
}
|
|
|
|
|
2016-11-21 21:34:45 +00:00
|
|
|
type pathlessWildcardPattern struct {
|
|
|
|
rawPattern string
|
|
|
|
wildcardRE *regexp.Regexp
|
|
|
|
}
|
|
|
|
|
|
|
|
// Match is a revised version of filepath.Match which makes it behave more
|
|
|
|
// like gitignore
|
|
|
|
func (p *pathlessWildcardPattern) Match(name string) bool {
|
|
|
|
matched, _ := filepath.Match(p.rawPattern, name)
|
|
|
|
// Match the whole of the base name but allow matching in folders if no path
|
|
|
|
return matched || p.wildcardRE.MatchString(filepath.Base(name))
|
|
|
|
}
|
|
|
|
|
2017-06-20 23:25:45 +00:00
|
|
|
// String returns a string representation of the underlying pattern for which
|
|
|
|
// this *pathlessWildcardPattern is matching.
|
|
|
|
func (p *pathlessWildcardPattern) String() string {
|
|
|
|
return p.rawPattern
|
|
|
|
}
|
|
|
|
|
2016-11-21 21:34:45 +00:00
|
|
|
type doubleWildcardPattern struct {
|
|
|
|
rawPattern string
|
|
|
|
wildcardRE *regexp.Regexp
|
|
|
|
}
|
|
|
|
|
|
|
|
// Match is a revised version of filepath.Match which makes it behave more
|
|
|
|
// like gitignore
|
|
|
|
func (p *doubleWildcardPattern) Match(name string) bool {
|
|
|
|
matched, _ := filepath.Match(p.rawPattern, name)
|
|
|
|
// Match the whole of the base name but allow matching in folders if no path
|
|
|
|
return matched || p.wildcardRE.MatchString(name)
|
|
|
|
}
|
|
|
|
|
2017-06-20 23:26:01 +00:00
|
|
|
// String returns a string representation of the underlying pattern for which
|
|
|
|
// this *doubleWildcardPattern is matching.
|
|
|
|
func (p *doubleWildcardPattern) String() string {
|
|
|
|
return p.rawPattern
|
|
|
|
}
|
|
|
|
|
2016-11-21 21:34:45 +00:00
|
|
|
type noOpMatcher struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n noOpMatcher) Match(name string) bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-11-21 23:34:35 +00:00
|
|
|
var localDirSet = map[string]struct{}{
|
|
|
|
".": struct{}{},
|
|
|
|
"./": struct{}{},
|
|
|
|
".\\": struct{}{},
|
|
|
|
}
|