-
-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathWildcardPattern.php
148 lines (128 loc) · 3.93 KB
/
WildcardPattern.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<?php
declare(strict_types=1);
namespace Yiisoft\Strings;
use function implode;
use function preg_match;
use function preg_quote;
use function preg_replace;
use function strtr;
/**
* A wildcard pattern to match strings against.
*
* - `\` escapes other special characters if usage of escape character is not turned off.
* - `*` matches any string including the empty string except it has a delimiter (`/` and `\` by default).
* - `**` matches any string including the empty string and delimiters.
* - `?` matches any single character.
* - `[seq]` matches any character in seq.
* - `[a-z]` matches any character from a to z.
* - `[!seq]` matches any character not in seq.
* - `[[:alnum:]]` matches POSIX style character classes,
* see {@see https://www.php.net/manual/en/regexp.reference.character-classes.php}.
*/
final class WildcardPattern
{
private bool $ignoreCase = false;
/**
* @psalm-var non-empty-string|null
*/
private ?string $patternPrepared = null;
/**
* @param string $pattern The shell wildcard pattern to match against.
* @param string[] $delimiters Delimiters to consider for "*" (`/` and `\` by default).
*/
public function __construct(
private string $pattern,
private array $delimiters = ['\\\\', '/'],
) {
}
/**
* Checks if the passed string would match the given shell wildcard pattern.
*
* @param string $string The tested string.
*
* @return bool Whether the string matches pattern or not.
*/
public function match(string $string): bool
{
if ($this->pattern === '**') {
return true;
}
return preg_match($this->getPatternPrepared(), $string) === 1;
}
/**
* Make pattern case insensitive.
*/
public function ignoreCase(bool $flag = true): self
{
$new = clone $this;
$new->patternPrepared = null;
$new->ignoreCase = $flag;
return $new;
}
/**
* Returns whether the pattern contains a dynamic part i.e.
* has unescaped "*", "{", "?", or "[" character.
*
* @param string $pattern The pattern to check.
*
* @return bool Whether the pattern contains a dynamic part.
*/
public static function isDynamic(string $pattern): bool
{
$pattern = preg_replace('/\\\\./', '', $pattern);
return preg_match('/[*{?\[]/', $pattern) === 1;
}
/**
* Escapes pattern characters in a string.
*
* @param string $string Source string.
*
* @return string String with pattern characters escaped.
*/
public static function quote(string $string): string
{
return preg_replace('#([\\\\?*\\[\\]])#', '\\\\$1', $string);
}
/**
* @return non-empty-string
*/
private function getPatternPrepared(): string
{
if ($this->patternPrepared !== null) {
return $this->patternPrepared;
}
$replacements = [
'\*\*' => '.*',
'\\\\\\\\' => '\\\\',
'\\\\\\*' => '[*]',
'\\\\\\?' => '[?]',
'\\\\\\[' => '[\[]',
'\\\\\\]' => '[\]]',
];
if ($this->delimiters === []) {
$replacements += [
'\*' => '.*',
'\?' => '?',
];
} else {
$notDelimiters = '[^' . preg_quote(implode('', $this->delimiters), '#') . ']';
$replacements += [
'\*' => "$notDelimiters*",
'\?' => $notDelimiters,
];
}
$replacements += [
'\[\!' => '[^',
'\[' => '[',
'\]' => ']',
'\-' => '-',
];
$pattern = strtr(preg_quote($this->pattern, '#'), $replacements);
$pattern = '#^' . $pattern . '$#us';
if ($this->ignoreCase) {
$pattern .= 'i';
}
$this->patternPrepared = $pattern;
return $this->patternPrepared;
}
}