1// Copyright 2020 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package match provides functions for performing filepath [?,*,**] wildcard 16// matching. 17package match 18 19import ( 20 "fmt" 21 "regexp" 22 "strings" 23) 24 25// Test is the match predicate returned by New. 26type Test func(path string) bool 27 28// New returns a Test function that returns true iff the path matches the 29// provided pattern. 30// 31// pattern uses forward-slashes for directory separators '/', and may use the 32// following wildcards: 33// ? - matches any single non-separator character 34// * - matches any sequence of non-separator characters 35// ** - matches any sequence of characters including separators 36func New(pattern string) (Test, error) { 37 // Transform pattern into a regex by replacing the uses of `?`, `*`, `**` 38 // with corresponding regex patterns. 39 // As the pattern may contain other regex sequences, the string has to be 40 // escaped. So: 41 // a) Replace the patterns of `?`, `*`, `**` with unique placeholder tokens. 42 // b) Escape the expression so that other sequences don't confuse the regex 43 // parser. 44 // c) Replace the placeholder tokens with the corresponding regex tokens. 45 46 // Temporary placeholder tokens 47 const ( 48 starstar = "••" 49 star = "•" 50 questionmark = "¿" 51 ) 52 // Check pattern doesn't contain any of our placeholder tokens 53 for _, r := range []rune{'•', '¿'} { 54 if strings.ContainsRune(pattern, r) { 55 return nil, fmt.Errorf("Pattern must not contain '%c'", r) 56 } 57 } 58 // Replace **, * and ? with placeholder tokens 59 subbed := pattern 60 subbed = strings.ReplaceAll(subbed, "**", starstar) 61 subbed = strings.ReplaceAll(subbed, "*", star) 62 subbed = strings.ReplaceAll(subbed, "?", questionmark) 63 // Escape any remaining regex characters 64 escaped := regexp.QuoteMeta(subbed) 65 // Insert regex matchers for the substituted tokens 66 regex := "^" + escaped + "$" 67 regex = strings.ReplaceAll(regex, starstar, ".*") 68 regex = strings.ReplaceAll(regex, star, "[^/]*") 69 regex = strings.ReplaceAll(regex, questionmark, "[^/]") 70 71 re, err := regexp.Compile(regex) 72 if err != nil { 73 return nil, fmt.Errorf(`Failed to compile regex "%v" for pattern "%v": %w`, regex, pattern, err) 74 } 75 return re.MatchString, nil 76} 77