Passed
Pull Request — master (#67)
by Alexander
02:21
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 65
    public function __construct(string $pattern, array $delimiters = ['\\\\', '/'])
33
    {
34 65
        $this->pattern = $pattern;
35 65
        $this->delimiters = $delimiters;
36 65
    }
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 64
    public function match(string $string): bool
46
    {
47 64
        if ($this->pattern === '**' && !$this->matchLeadingPeriodExactly) {
48
            return true;
49
        }
50
51 64
        $pattern = $this->pattern;
52
53 64
        if ($this->matchLeadingPeriodExactly) {
54 3
            $pattern = preg_replace('/^[*?]/', '[!.]', $pattern);
55
        }
56
57
        $replacements = [
58 64
            '\*\*' => '.*',
59
            '\\\\\\\\' => '\\\\',
60
            '\\\\\\*' => '[*]',
61
            '\\\\\\?' => '[?]',
62
        ];
63
64 64
        if ($this->delimiters === []) {
65
            $replacements += [
66 1
                '\*' => '.*',
67
                '\?' => '?',
68
            ];
69
        } else {
70 63
            $notDelimiters = '[^' . preg_quote(implode('', $this->delimiters), '#') . ']';
71
            $replacements += [
72 63
                '\*' => "$notDelimiters*",
73 63
                '\?' => $notDelimiters,
74
            ];
75
        }
76
77
        $replacements += [
78 64
            '\[\!' => '[^',
79
            '\[' => '[',
80
            '\]' => ']',
81
            '\-' => '-',
82
        ];
83
84 64
        if ($this->withoutEscape) {
85 5
            unset($replacements['\\\\\\\\'], $replacements['\\\\\\*'], $replacements['\\\\\\?']);
86
        }
87
88 64
        $pattern = strtr(preg_quote($pattern, '#'), $replacements);
89 64
        $pattern = '#^' . $pattern . '$#us';
90
91 64
        if ($this->ignoreCase) {
92 1
            $pattern .= 'i';
93
        }
94
95 64
        return preg_match($pattern, $string) === 1;
96
    }
97
98
    /**
99
     * Disables using `\` to escape following special character. `\` becomes regular character.
100
     *
101
     * @param bool $flag
102
     *
103
     * @return self
104
     */
105 7
    public function withoutEscape(bool $flag = true): self
106
    {
107 7
        $new = clone $this;
108 7
        $new->withoutEscape = $flag;
109 7
        return $new;
110
    }
111
112
    /**
113
     * Make pattern case insensitive.
114
     *
115
     * @param bool $flag
116
     *
117
     * @return self
118
     */
119 3
    public function ignoreCase(bool $flag = true): self
120
    {
121 3
        $new = clone $this;
122 3
        $new->ignoreCase = $flag;
123 3
        return $new;
124
    }
125
126
    /**
127
     * Do not match `.` character at the beginning of string with wildcards.
128
     * Useful for matching file paths.
129
     *
130
     * @param bool $flag
131
     *
132
     * @return self
133
     */
134 5
    public function exactLeadingPeriod(bool $flag = true): self
135
    {
136 5
        $new = clone $this;
137 5
        $new->matchLeadingPeriodExactly = $flag;
138 5
        return $new;
139
    }
140
141
    /**
142
     * Returns whether the pattern contains a dynamic part i.e.
143
     * has unescaped "*",  "{", "?", or "[" character.
144
     *
145
     * @param string $pattern The pattern to check.
146
     *
147
     * @return bool Whether the pattern contains a dynamic part.
148
     */
149 5
    public static function isDynamic(string $pattern): bool
150
    {
151 5
        $pattern = preg_replace('/\\\\./', '', $pattern);
152 5
        return (bool)preg_match('/[*{?\[]/', $pattern);
153
    }
154
}
155