Passed
Pull Request — master (#67)
by Alexander
02:06
created

WildcardPattern::withoutEscape()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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