2424
2525final class Path implements \JsonSerializable
2626{
27+ public readonly string $ value ;
28+ private readonly ?string $ driveLetter ;
29+ private readonly string $ pathWithoutDriveLetter ;
2730
28- private function __construct (public readonly string $ value )
31+ private function __construct (string $ value )
2932 {
3033 if (empty (trim ($ value ))) {
3134 throw new \Exception ('@TODO: Invalid path ' );
3235 }
36+
37+ $ this ->value = ($ value [0 ] === DIRECTORY_SEPARATOR ? DIRECTORY_SEPARATOR : '' ) . join (
38+ DIRECTORY_SEPARATOR ,
39+ array_filter (
40+ explode (
41+ DIRECTORY_SEPARATOR ,
42+ mb_ereg_replace ('\\\\|/ ' , DIRECTORY_SEPARATOR , $ value , 'msr ' )
43+ ),
44+ 'mb_strlen '
45+ )
46+ );
47+
48+ preg_match ('/^[a-zA-Z]:/ ' , $ this ->value , $ matches );
49+ $ this ->driveLetter = isset ($ matches [0 ]) ? $ matches [0 ] : null ;
50+ $ this ->pathWithoutDriveLetter = $ this ->driveLetter
51+ ? mb_substr ($ this ->value , mb_strlen ($ this ->driveLetter ))
52+ : $ this ->value ;
3353 }
3454
3555 public static function createMemory (): self
@@ -54,43 +74,42 @@ public function isRelative(): bool
5474
5575 public function isAbsolute (): bool
5676 {
57- return $ this ->value [0 ] === ' / ' ;
77+ return $ this ->pathWithoutDriveLetter [0 ] === DIRECTORY_SEPARATOR ;
5878 }
5979
60- public function getRelativePathTo (Path $ target ): self
80+ public function resolveRelationTo (Path $ other ): self
6181 {
62- if ($ this ->isMemory ()) {
63- throw new \Exception ('@TODO: Cannot create relative path for :memory: ' );
64- } elseif ($ this ->isRelative () && $ target ->isAbsolute ()) {
65- throw new \Exception ('@TODO: Cannot create relative path from realtive source to asbolute target. ' );
66- } elseif ($ this ->isAbsolute () && $ target ->isAbsolute ()) {
67- $ dirname = dirname ($ this ->value );
68- if (substr ($ target ->value , 0 , strlen ($ dirname )) === $ dirname ) {
69- return new self ('. ' . substr ($ target ->value , strlen ($ dirname )));
70- } else {
71- throw new \Exception ('@TODO: Cannot create relative path due to incompatible absolute paths. ' );
72- }
73- } else {
74- $ dirname = dirname ($ this ->value );
75- $ resultSegments = explode ('/ ' , $ dirname );
76- $ overflowSegments = [];
77- $ targetSegments = explode ('/ ' , $ target ->value );
78-
79- foreach ($ targetSegments as $ segment ) {
80- if ($ segment === '. ' || $ segment === '' ) {
81- // ignore
82- } elseif ($ segment === '.. ' ) {
83- if (count ($ resultSegments )) {
84- array_pop ($ resultSegments );
85- } else {
86- $ overflowSegments [] = $ segment ;
87- }
88- } else {
89- $ resultSegments [] = $ segment ;
82+ if ($ this ->isAbsolute () && $ other ->isRelative ()) {
83+ $ pathSegments = array_merge (
84+ explode (DIRECTORY_SEPARATOR , dirname ($ this ->pathWithoutDriveLetter )),
85+ explode (DIRECTORY_SEPARATOR , $ other ->value )
86+ );
87+
88+ $ absolutePathSegments = [];
89+ foreach ($ pathSegments as $ pathSegment ) {
90+ switch ($ pathSegment ) {
91+ case '. ' :
92+ continue 2 ;
93+ case '.. ' :
94+ if ($ absolutePathSegments ) {
95+ array_pop ($ absolutePathSegments );
96+ } else {
97+ throw new \Exception ('@TODO: Unable to resolve path ' . $ other ->value );
98+ }
99+ break ;
100+ default :
101+ $ absolutePathSegments [] = $ pathSegment ;
102+ break ;
90103 }
91104 }
92105
93- return new self (implode ('/ ' , [...$ overflowSegments , ...$ resultSegments ]));
106+ return new self (
107+ ($ this ->driveLetter ? $ this ->driveLetter : '' )
108+ . DIRECTORY_SEPARATOR
109+ . join (DIRECTORY_SEPARATOR , $ absolutePathSegments )
110+ );
111+ } else {
112+ return $ other ;
94113 }
95114 }
96115
0 commit comments