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 }