github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/filter/filter.go (about)

     1  package filter
     2  
     3  import (
     4  	"path/filepath"
     5  	"strings"
     6  
     7  	"github.com/restic/restic/internal/errors"
     8  )
     9  
    10  // ErrBadString is returned when Match is called with the empty string as the
    11  // second argument.
    12  var ErrBadString = errors.New("filter.Match: string is empty")
    13  
    14  // Match returns true if str matches the pattern. When the pattern is
    15  // malformed, filepath.ErrBadPattern is returned. The empty pattern matches
    16  // everything, when str is the empty string ErrBadString is returned.
    17  //
    18  // Pattern can be a combination of patterns suitable for filepath.Match, joined
    19  // by filepath.Separator.
    20  //
    21  // In addition patterns suitable for filepath.Match, pattern accepts a
    22  // recursive wildcard '**', which greedily matches an arbitrary number of
    23  // intermediate directories.
    24  func Match(pattern, str string) (matched bool, err error) {
    25  	if pattern == "" {
    26  		return true, nil
    27  	}
    28  
    29  	pattern = filepath.Clean(pattern)
    30  
    31  	if str == "" {
    32  		return false, ErrBadString
    33  	}
    34  
    35  	// convert file path separator to '/'
    36  	if filepath.Separator != '/' {
    37  		pattern = strings.Replace(pattern, string(filepath.Separator), "/", -1)
    38  		str = strings.Replace(str, string(filepath.Separator), "/", -1)
    39  	}
    40  
    41  	patterns := strings.Split(pattern, "/")
    42  	strs := strings.Split(str, "/")
    43  
    44  	return match(patterns, strs)
    45  }
    46  
    47  // ChildMatch returns true if children of str can match the pattern. When the pattern is
    48  // malformed, filepath.ErrBadPattern is returned. The empty pattern matches
    49  // everything, when str is the empty string ErrBadString is returned.
    50  //
    51  // Pattern can be a combination of patterns suitable for filepath.Match, joined
    52  // by filepath.Separator.
    53  //
    54  // In addition patterns suitable for filepath.Match, pattern accepts a
    55  // recursive wildcard '**', which greedily matches an arbitrary number of
    56  // intermediate directories.
    57  func ChildMatch(pattern, str string) (matched bool, err error) {
    58  	if pattern == "" {
    59  		return true, nil
    60  	}
    61  
    62  	pattern = filepath.Clean(pattern)
    63  
    64  	if str == "" {
    65  		return false, ErrBadString
    66  	}
    67  
    68  	// convert file path separator to '/'
    69  	if filepath.Separator != '/' {
    70  		pattern = strings.Replace(pattern, string(filepath.Separator), "/", -1)
    71  		str = strings.Replace(str, string(filepath.Separator), "/", -1)
    72  	}
    73  
    74  	patterns := strings.Split(pattern, "/")
    75  	strs := strings.Split(str, "/")
    76  
    77  	return childMatch(patterns, strs)
    78  }
    79  
    80  func childMatch(patterns, strs []string) (matched bool, err error) {
    81  	if patterns[0] != "" {
    82  		// relative pattern can always be nested down
    83  		return true, nil
    84  	}
    85  
    86  	// match path against absolute pattern prefix
    87  	l := 0
    88  	if len(strs) > len(patterns) {
    89  		l = len(patterns)
    90  	} else {
    91  		l = len(strs)
    92  	}
    93  	return match(patterns[0:l], strs)
    94  }
    95  
    96  func hasDoubleWildcard(list []string) (ok bool, pos int) {
    97  	for i, item := range list {
    98  		if item == "**" {
    99  			return true, i
   100  		}
   101  	}
   102  
   103  	return false, 0
   104  }
   105  
   106  func match(patterns, strs []string) (matched bool, err error) {
   107  	if ok, pos := hasDoubleWildcard(patterns); ok {
   108  		// gradually expand '**' into separate wildcards
   109  		for i := 0; i <= len(strs)-len(patterns)+1; i++ {
   110  			newPat := make([]string, pos)
   111  			copy(newPat, patterns[:pos])
   112  			for k := 0; k < i; k++ {
   113  				newPat = append(newPat, "*")
   114  			}
   115  			newPat = append(newPat, patterns[pos+1:]...)
   116  
   117  			matched, err := match(newPat, strs)
   118  			if err != nil {
   119  				return false, err
   120  			}
   121  
   122  			if matched {
   123  				return true, nil
   124  			}
   125  		}
   126  
   127  		return false, nil
   128  	}
   129  
   130  	if len(patterns) == 0 && len(strs) == 0 {
   131  		return true, nil
   132  	}
   133  
   134  	if len(patterns) <= len(strs) {
   135  	outer:
   136  		for offset := len(strs) - len(patterns); offset >= 0; offset-- {
   137  
   138  			for i := len(patterns) - 1; i >= 0; i-- {
   139  				ok, err := filepath.Match(patterns[i], strs[offset+i])
   140  				if err != nil {
   141  					return false, errors.Wrap(err, "Match")
   142  				}
   143  
   144  				if !ok {
   145  					continue outer
   146  				}
   147  			}
   148  
   149  			return true, nil
   150  		}
   151  	}
   152  
   153  	return false, nil
   154  }
   155  
   156  // List returns true if str matches one of the patterns. Empty patterns are
   157  // ignored.
   158  func List(patterns []string, str string) (matched bool, childMayMatch bool, err error) {
   159  	for _, pat := range patterns {
   160  		if pat == "" {
   161  			continue
   162  		}
   163  
   164  		m, err := Match(pat, str)
   165  		if err != nil {
   166  			return false, false, err
   167  		}
   168  
   169  		c, err := ChildMatch(pat, str)
   170  		if err != nil {
   171  			return false, false, err
   172  		}
   173  
   174  		matched = matched || m
   175  		childMayMatch = childMayMatch || c
   176  
   177  		if matched && childMayMatch {
   178  			return true, true, nil
   179  		}
   180  	}
   181  
   182  	return matched, childMayMatch, nil
   183  }