|
10 | 10 | * Examples: |
11 | 11 | * - normalize('src//Foo/./Bar/..') => 'src/Foo' |
12 | 12 | * - isUnder('src/Acme/Lib', 'src') => true |
13 | | - * - match('src/* /Lib', 'src/Acme/Lib') => true (single-star within one segment) |
14 | | - * - match('src/** /Lib', 'src/a/b/c/Lib') => true (double-star across directories) |
| 13 | + * - match('src/*\/Lib', 'src/Acme/Lib') => true (single-star within one segment) |
| 14 | + * - match('src/**\/Lib', 'src/a/b/c/Lib') => true (double-star across directories) |
15 | 15 | * - leaf('src/Acme/Lib') => 'Lib' |
16 | 16 | */ |
17 | 17 | final class PathUtils implements PathUtilsInterface |
18 | 18 | { |
19 | 19 | public function normalize(string $path): string |
20 | 20 | { |
21 | | - $p = str_replace('\\', '/', $path); |
| 21 | + $normalizedPath = str_replace('\\', '/', $path); |
22 | 22 | // remove multiple slashes |
23 | | - $p = preg_replace('#/+#', '/', $p) ?? $p; |
| 23 | + $normalizedPath = preg_replace('#/+#', '/', $normalizedPath) ?? $normalizedPath; |
24 | 24 | // remove trailing slash (except root '/') |
25 | | - if ($p !== '/' && str_ends_with($p, '/')) { |
26 | | - $p = rtrim($p, '/'); |
| 25 | + if ($normalizedPath !== '/' && str_ends_with($normalizedPath, '/')) { |
| 26 | + $normalizedPath = rtrim($normalizedPath, '/'); |
27 | 27 | } |
28 | 28 |
|
29 | 29 | // resolve "." and ".." cheaply (string-level, not FS) |
30 | | - $parts = []; |
31 | | - foreach (explode('/', $p) as $seg) { |
32 | | - if ($seg === '') { |
| 30 | + $resolvedSegments = []; |
| 31 | + foreach (explode('/', $normalizedPath) as $segment) { |
| 32 | + if ($segment === '') { |
33 | 33 | continue; |
34 | 34 | } |
35 | | - if ($seg === '.') { |
| 35 | + |
| 36 | + if ($segment === '.') { |
36 | 37 | continue; |
37 | 38 | } |
38 | 39 |
|
39 | | - if ($seg === '..') { |
40 | | - array_pop($parts); |
| 40 | + if ($segment === '..') { |
| 41 | + array_pop($resolvedSegments); |
41 | 42 | continue; |
42 | 43 | } |
43 | 44 |
|
44 | | - $parts[] = $seg; |
| 45 | + $resolvedSegments[] = $segment; |
45 | 46 | } |
46 | 47 |
|
47 | | - $out = implode('/', $parts); |
| 48 | + $out = implode('/', $resolvedSegments); |
48 | 49 | return $out === '' ? '.' : $out; |
49 | 50 | } |
50 | 51 |
|
51 | 52 | public function isUnder(string $path, string $root): bool |
52 | 53 | { |
53 | | - $p = $this->normalize($path); |
54 | | - $r = $this->normalize($root); |
55 | | - return $p === $r || str_starts_with($p, $r . '/'); |
| 54 | + $normalizedPath = $this->normalize($path); |
| 55 | + $normalizedRoot = $this->normalize($root); |
| 56 | + return $normalizedPath === $normalizedRoot || str_starts_with($normalizedPath, $normalizedRoot . '/'); |
56 | 57 | } |
57 | 58 |
|
58 | 59 | public function match(string $pattern, string $path): bool |
59 | 60 | { |
60 | | - $pat = $this->normalize($pattern); |
61 | | - $pth = $this->normalize($path); |
| 61 | + $normalizedPattern = $this->normalize($pattern); |
| 62 | + $normalizedPath = $this->normalize($path); |
62 | 63 |
|
63 | 64 | // Split into tokens while keeping the delimiters (** , * , ?) |
64 | | - $parts = preg_split('/(\*\*|\*|\?)/', $pat, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); |
65 | | - if ($parts === false) { |
| 65 | + $tokens = preg_split('/(\*\*|\*|\?)/', $normalizedPattern, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); |
| 66 | + if ($tokens === false) { |
66 | 67 | return false; |
67 | 68 | } |
68 | 69 |
|
69 | 70 | $regex = ''; |
70 | | - foreach ($parts as $part) { |
71 | | - if ($part === '**') { |
| 71 | + foreach ($tokens as $token) { |
| 72 | + if ($token === '**') { |
72 | 73 | $regex .= '.*'; // can cross slashes, zero or more |
73 | | - } elseif ($part === '*') { |
| 74 | + } elseif ($token === '*') { |
74 | 75 | $regex .= '[^/]*'; // single segment |
75 | | - } elseif ($part === '?') { |
| 76 | + } elseif ($token === '?') { |
76 | 77 | $regex .= '[^/]'; // one char in a segment |
77 | 78 | } else { |
78 | | - $regex .= preg_quote($part, '#'); // literal |
| 79 | + $regex .= preg_quote($token, '#'); // literal |
79 | 80 | } |
80 | 81 | } |
81 | 82 |
|
82 | 83 | // full-string, case-sensitive; add 'i' if you want case-insensitive |
83 | | - return (bool) preg_match('#^' . $regex . '$#u', $pth); |
| 84 | + return (bool) preg_match('#^' . $regex . '$#u', $normalizedPath); |
84 | 85 | } |
85 | 86 |
|
86 | 87 | public function leaf(string $path): string |
87 | 88 | { |
88 | | - $p = $this->normalize($path); |
89 | | - $pos = strrpos($p, '/'); |
90 | | - return $pos === false ? $p : substr($p, $pos + 1); |
| 89 | + $normalizedPath = $this->normalize($path); |
| 90 | + $pos = strrpos($normalizedPath, '/'); |
| 91 | + return $pos === false ? $normalizedPath : substr($normalizedPath, $pos + 1); |
91 | 92 | } |
92 | 93 | } |
0 commit comments