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

WildcardPattern::isDynamic()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
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 $matchLeadingPeriodExactly = false;
24
    private bool $ignoreCase = false;
25
    private string $pattern;
26
    private array $delimiters;
27
28
    /**
29
     * @param string $pattern The shell wildcard pattern to match against.
30
     * @param array $delimiters Delimiters to consider for "*" (`/` and `\` by default).
31
     */
32 62
    public function __construct(string $pattern, array $delimiters = ['\\\\', '/'])
33
    {
34 62
        $this->pattern = $pattern;
35 62
        $this->delimiters = $delimiters;
36 62
    }
37
38
    /**
39
     * Checks if the passed string would match the given shell wildcard pattern.
40
     *
41
     * @param string $string The tested string.
42
     *
43
     * @return bool Whether the string matches pattern or not.
44
     */
45 61
    public function match(string $string): bool
46
    {
47 61
        if ($this->pattern === '**' && !$this->matchLeadingPeriodExactly) {
48
            return true;
49
        }
50
51 61
        $pattern = $this->pattern;
52
53 61
        if ($this->matchLeadingPeriodExactly) {
54 3
            $pattern = preg_replace('/^[*?]/', '[!.]', $pattern);
55
        }
56
57 61
        $notDelimiters = '[^' . preg_quote(implode('', $this->delimiters), '#') . ']';
58
59
        $replacements = [
60 61
            '\*\*' => '.*',
61 61
            '\\\\\\\\' => '\\\\',
62 61
            '\\\\\\*' => '[*]',
63 61
            '\\\\\\?' => '[?]',
64 61
            '\*' => "$notDelimiters*",
65 61
            '\?' => $notDelimiters,
66 61
            '\[\!' => '[^',
67 61
            '\[' => '[',
68 61
            '\]' => ']',
69 61
            '\-' => '-',
70
        ];
71
72 61
        if ($this->withoutEscape) {
73 5
            unset($replacements['\\\\\\\\'], $replacements['\\\\\\*'], $replacements['\\\\\\?']);
74
        }
75
76 61
        $pattern = strtr(preg_quote($pattern, '#'), $replacements);
77 61
        $pattern = '#^' . $pattern . '$#us';
78
79 61
        if ($this->ignoreCase) {
80 1
            $pattern .= 'i';
81
        }
82
83 61
        return preg_match($pattern, $string) === 1;
84
    }
85
86
    /**
87
     * Disables using `\` to escape following special character. `\` becomes regular character.
88
     *
89
     * @param bool $flag
90
     *
91
     * @return self
92
     */
93 7
    public function withoutEscape(bool $flag = true): self
94
    {
95 7
        $new = clone $this;
96 7
        $new->withoutEscape = $flag;
97 7
        return $new;
98
    }
99
100
    /**
101
     * Make pattern case insensitive.
102
     *
103
     * @param bool $flag
104
     *
105
     * @return self
106
     */
107 3
    public function ignoreCase(bool $flag = true): self
108
    {
109 3
        $new = clone $this;
110 3
        $new->ignoreCase = $flag;
111 3
        return $new;
112
    }
113
114
    /**
115
     * Do not match `.` character at the beginning of string with wildcards.
116
     * Useful for matching file paths.
117
     *
118
     * @param bool $flag
119
     *
120
     * @return self
121
     */
122 5
    public function exactLeadingPeriod(bool $flag = true): self
123
    {
124 5
        $new = clone $this;
125 5
        $new->matchLeadingPeriodExactly = $flag;
126 5
        return $new;
127
    }
128
129
    /**
130
     * Returns whether the pattern contains a dynamic part i.e.
131
     * has unescaped "*",  "{", "?", or "[" character.
132
     *
133
     * @param string $pattern The pattern to check.
134
     *
135
     * @return bool Whether the pattern contains a dynamic part.
136
     */
137 5
    public static function isDynamic(string $pattern): bool
138
    {
139 5
        $pattern = preg_replace('/\\\\./', '', $pattern);
140 5
        return (bool)preg_match('/[*{?\[]/', $pattern);
141
    }
142
}
143