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

WildcardPattern::match()   B

Complexity

Conditions 11
Paths 34

Size

Total Lines 46
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 11.0176

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 29
c 1
b 0
f 0
nc 34
nop 1
dl 0
loc 46
ccs 18
cts 19
cp 0.9474
crap 11.0176
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Strings;
6
7
/**
8
 * A shell 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.
12
 *   Does not match slashes if {@see WildcardPattern::withExactSlashes()} is used.
13
 * - `**` always matches any string, including the empty string and slashes.
14
 * - `?` matches any single character.
15
 * - `[seq]` matches any character in seq.
16
 * - `[a-z]` matches any character from a to z.
17
 * - `[!seq]` matches any character not in seq.
18
 * - `[[:alnum:]]` matches POSIX style character classes,
19
 *   see {@see https://www.php.net/manual/en/regexp.reference.character-classes.php}.
20
 *
21
 * @see https://www.man7.org/linux/man-pages/man7/glob.7.html
22
 *
23
 * The class emulates {@see fnmatch()} using PCRE since it is not uniform across operating systems
24
 * and may not be available.
25
 */
26
final class WildcardPattern
27
{
28
    private bool $withoutEscape = false;
29
    private bool $matchSlashesExactly = false;
30
    private bool $matchLeadingPeriodExactly = false;
31
    private bool $ignoreCase = false;
32
    private bool $matchEnding = false;
33
    private string $pattern;
34
35
    /**
36
     * @param string $pattern The shell wildcard pattern to match against.
37
     */
38 68
    public function __construct(string $pattern)
39
    {
40 68
        $this->pattern = $pattern;
41 68
    }
42
43
    /**
44
     * Checks if the passed string would match the given shell wildcard pattern.
45
     *
46
     * @param string $string The tested string.
47
     *
48
     * @return bool Whether the string matches pattern or not.
49
     */
50 67
    public function match(string $string): bool
51
    {
52 67
        if ($this->pattern === '**' && !$this->matchLeadingPeriodExactly) {
53
            return true;
54
        }
55
56 67
        if ($this->pattern === '*' && !$this->matchSlashesExactly && !$this->matchLeadingPeriodExactly) {
57 2
            return true;
58
        }
59
60 65
        $pattern = $this->pattern;
61
62 65
        if ($this->matchLeadingPeriodExactly) {
63 3
            $pattern = preg_replace('/^[*?]/', '[!.]', $pattern);
64
        }
65
66
        $replacements = [
67 65
            '\*\*' => '[^\\\\]*',
68
            '\\\\\\\\' => '\\\\',
69
            '\\\\\\*' => '[*]',
70
            '\\\\\\?' => '[?]',
71
            '\*' => '.*',
72
            '\?' => '.',
73
            '\[\!' => '[^',
74
            '\[' => '[',
75
            '\]' => ']',
76
            '\-' => '-',
77
        ];
78
79 65
        if ($this->withoutEscape) {
80 5
            unset($replacements['\\\\\\\\'], $replacements['\\\\\\*'], $replacements['\\\\\\?']);
81
        }
82
83 65
        if ($this->matchSlashesExactly) {
84 14
            $replacements['\*'] = '[^/\\\\]*';
85 14
            $replacements['\?'] = '[^/\\\\]';
86
        }
87
88 65
        $pattern = strtr(preg_quote($pattern, '#'), $replacements);
89 65
        $pattern = '#' . ($this->matchEnding ? '' : '^') . $pattern . '$#us';
90
91 65
        if ($this->ignoreCase) {
92 1
            $pattern .= 'i';
93
        }
94
95 65
        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
     * Do not match `/` character with wildcards. The only way to match `/` is with an explicit `/` in pattern.
114
     * Useful for matching file paths. Use with {@see withExactLeadingPeriod()}.
115
     *
116
     * @param bool $flag
117
     *
118
     * @return self
119
     */
120 16
    public function withExactSlashes(bool $flag = true): self
121
    {
122 16
        $new = clone $this;
123 16
        $new->matchSlashesExactly = $flag;
124 16
        return $new;
125
    }
126
127
    /**
128
     * Make pattern case insensitive.
129
     *
130
     * @param bool $flag
131
     *
132
     * @return self
133
     */
134 3
    public function ignoreCase(bool $flag = true): self
135
    {
136 3
        $new = clone $this;
137 3
        $new->ignoreCase = $flag;
138 3
        return $new;
139
    }
140
141
    /**
142
     * Do not match `.` character at the beginning of string with wildcards.
143
     * Useful for matching file paths. Use with {@see withExactSlashes()}.
144
     *
145
     * @param bool $flag
146
     *
147
     * @return self
148
     */
149 5
    public function withExactLeadingPeriod(bool $flag = true): self
150
    {
151 5
        $new = clone $this;
152 5
        $new->matchLeadingPeriodExactly = $flag;
153 5
        return $new;
154
    }
155
156
    /**
157
     * Match ending only.
158
     * By default wildcard pattern matches string exactly. By using this mode, beginning of the string could be anything.
159
     *
160
     * @param bool $flag
161
     *
162
     * @return self
163
     */
164 8
    public function withEnding(bool $flag = true): self
165
    {
166 8
        $new = clone $this;
167 8
        $new->matchEnding = $flag;
168 8
        return $new;
169
    }
170
}
171