• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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