Passed
Pull Request — master (#51)
by Alexander
18:25 queued 04:22
created

WildcardPattern   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 127
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 48
c 2
b 0
f 0
dl 0
loc 127
ccs 36
cts 36
cp 1
rs 10
wmc 15

7 Methods

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