-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathPathResolver.php
More file actions
124 lines (105 loc) · 4.12 KB
/
Copy pathPathResolver.php
File metadata and controls
124 lines (105 loc) · 4.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?php
declare(strict_types=1);
namespace Codeception\Util;
use function array_fill;
use function array_filter;
use function array_merge;
use function array_shift;
use function count;
use function explode;
use function implode;
use function preg_match;
use function strlen;
use function substr;
class PathResolver
{
/**
* Returns path to a given directory relative to $projDir.
*/
public static function getRelativeDir(string $path, string $projDir, string $dirSep = DIRECTORY_SEPARATOR): string
{
$projDir = rtrim($projDir, $dirSep) . $dirSep;
$projLen = strlen($projDir);
if (self::fsCaseStrCmp(substr($path, 0, $projLen), $projDir, $dirSep) === 0) {
return substr($path, $projLen);
}
$pathPref = self::getPathAbsolutenessPrefix($path, $dirSep);
$projPref = self::getPathAbsolutenessPrefix($projDir, $dirSep);
if (self::fsCaseStrCmp($pathPref['wholePrefix'], $projPref['wholePrefix'], $dirSep) !== 0) {
if ($pathPref['devicePrefix'] !== '' && self::fsCaseStrCmp($pathPref['devicePrefix'], $projPref['devicePrefix'], $dirSep) === 0) {
return substr($path, strlen($pathPref['devicePrefix']));
}
return $path;
}
$baseLen = strlen($pathPref['wholePrefix']);
$partsPath = array_values(array_filter(explode($dirSep, substr($path, $baseLen))));
$partsProj = array_values(array_filter(explode($dirSep, substr($projDir, strlen($projPref['wholePrefix'])))));
while ($partsPath && $partsProj && self::fsCaseStrCmp($partsPath[0], $partsProj[0], $dirSep) === 0) {
array_shift($partsPath);
array_shift($partsProj);
}
if ($partsProj !== []) {
$partsPath = array_merge(array_fill(0, count($partsProj), '..'), $partsPath);
}
$trailingSep = (substr($path, -1) === $dirSep) ? $dirSep : '';
return implode($dirSep, $partsPath) . $trailingSep;
}
/**
* FileSystem Case String Comparison
* Compare two strings with the filesystem's case-sensitiveness
*
* @return int -1 / 0 / 1 for < / = / > respectively
*/
private static function fsCaseStrCmp(string $str1, string $str2, string $dirSep = DIRECTORY_SEPARATOR): int
{
$cmpFn = self::isWindowsFilesystem($dirSep) ? 'strcasecmp' : 'strcmp';
return $cmpFn($str1, $str2);
}
/**
* What part of this path (leftmost 0-3 characters) what
* it is absolute relative to:
*
* On Unix:
* This is simply '/' for an absolute path or
* '' for a relative path
*
* On Windows this is more complicated:
* If the first two characters are a letter followed
* by a ':', this indicates that the path is
* on a specific device.
* With or without a device specified, a path MAY
* start with a '\\' to indicate an absolute path
* on the device or '' to indicate a path relative
* to the device's CWD
*
* @return array<string, string>
*/
private static function getPathAbsolutenessPrefix(string $path, string $dirSep = DIRECTORY_SEPARATOR): array
{
$isWindows = self::isWindowsFilesystem($dirSep);
if ($isWindows && preg_match('/^[A-Za-z]:/', $path, $m)) {
$dev = $m[0];
$hasDirSep = (substr($path, strlen($dev), 1) === $dirSep) ? $dirSep : '';
return [
'wholePrefix' => $dev . $hasDirSep,
'devicePrefix' => $dev,
];
}
$wholePrefix = ($path !== '' && $path[0] === $dirSep) ? $dirSep : '';
return [
'wholePrefix' => $wholePrefix,
'devicePrefix' => '',
];
}
private static function isWindowsFilesystem(string $dirSep = DIRECTORY_SEPARATOR): bool
{
return $dirSep === '\\';
}
public static function isPathAbsolute(string $path): bool
{
if (DIRECTORY_SEPARATOR === '/') {
return $path !== '' && $path[0] === DIRECTORY_SEPARATOR;
}
return preg_match('#^[A-Z]:(?![^/\\\\])#i', $path) === 1;
}
}