-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
293 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
namespace App\Support; | ||
|
||
use EmptyIterator; | ||
use Iterator; | ||
|
||
/** | ||
* Currently there is no notion of "relative" or "absolute" | ||
* path as of {@see Path} object, because there is no functionality | ||
* of joining paths together. Method {@see Path::toString} | ||
* currently returns paths as absolute paths, but {@see Path} | ||
* itself can be thought of as both absolute and relative. | ||
* The distinction will have to be made when there are methods | ||
* in this class that allow joining paths - those methods | ||
* will have to decide whether to treat other {@see Path} as | ||
* relative or absolute. | ||
*/ | ||
class Path | ||
{ | ||
/** @var string[] */ | ||
private array $children; | ||
|
||
public function __construct(array $path) | ||
{ | ||
$this->children = $path; | ||
} | ||
|
||
public static function of(string $path): Path | ||
{ | ||
return new Path(\preg_split("#[\\\\/]#", $path)); | ||
} | ||
|
||
public function append(string $child): Path | ||
{ | ||
return new Path([...$this->children, $child]); | ||
} | ||
|
||
public function toString(): string | ||
{ | ||
return \join(\DIRECTORY_SEPARATOR, \iterator_to_array($this->children())); | ||
} | ||
|
||
private function children(): Iterator | ||
{ | ||
if (empty($this->children)) { | ||
return new EmptyIterator(); | ||
} | ||
return $this->normalizedChildren(); | ||
} | ||
|
||
private function normalizedChildren(): Iterator | ||
{ | ||
[$root, $children] = $this->rootAndChildren(); | ||
yield \rTrim($root, "/\\"); | ||
foreach ($children as $child) { | ||
if ($child === "") { | ||
continue; | ||
} | ||
yield \trim($child, "/\\"); | ||
} | ||
} | ||
|
||
private function rootAndChildren(): array | ||
{ | ||
$root = \reset($this->children); | ||
return [$root, \array_slice($this->children, 1)]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
namespace Tests\Psr4\Concerns; | ||
|
||
use Exception; | ||
|
||
/** | ||
* @method expectException(string $message) | ||
*/ | ||
trait PhpunitConcern | ||
{ | ||
/** | ||
* This test really is marked as unnecessary. If the condition is not met, | ||
* marking it as unnecessary is preferable to marking it as risky, incomplete | ||
* or skipped. | ||
* | ||
* This test simply doesn't make sense, if the condition is not met. | ||
*/ | ||
public function markTestUnnecessary(string $message): void | ||
{ | ||
$this->expectException(Exception::class); | ||
throw new Exception($message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
namespace Tests\Psr4; | ||
|
||
use PHPUnit\Framework\Assert; | ||
|
||
trait PhpUnitPolyfill | ||
{ | ||
public static function assertStringContainsString( | ||
string $needle, | ||
string $haystack, | ||
string $message = "" | ||
): void { | ||
$message = $message ?? "Failed to assert that starting contains substring"; | ||
Assert::assertTrue(\str_contains($haystack, $needle), $message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
<?php | ||
namespace Support; | ||
|
||
use App\Support\Path; | ||
use PHPUnit\Framework\TestCase; | ||
use Tests\Psr4\Concerns\PhpunitConcern; | ||
use Tests\Psr4\Concerns\SystemConcern; | ||
|
||
class PathTest extends TestCase | ||
{ | ||
use SystemConcern, PhpunitConcern; | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function shouldGetEmptyPath() | ||
{ | ||
// given | ||
$path = new Path([]); | ||
// when | ||
$asString = $path->toString(); | ||
// then | ||
$this->assertSame("", $asString); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function shouldIgnoreEmptyChildren() | ||
{ | ||
// given | ||
$path = new Path(["string", "", "", "string"]); | ||
// when | ||
$asString = $path->toString(); | ||
// then | ||
$this->assertSameWindows("string\string", $asString); | ||
$this->assertSameUnix("string/string", $asString); | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider fileNames | ||
*/ | ||
public function shouldGetSingleFile(string $filename) | ||
{ | ||
// given | ||
$path = new Path([$filename]); | ||
// when | ||
$asString = $path->toString(); | ||
// then | ||
$this->assertSame("file.txt", $asString); | ||
} | ||
|
||
public function fileNames(): array | ||
{ | ||
return [["file.txt"], ["file.txt/"], ["file.txt\\"]]; | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider pathPieces | ||
*/ | ||
public function shouldGetManyFiles(array $pathPieces) | ||
{ | ||
// given | ||
$path = new Path($pathPieces); | ||
// when | ||
$asString = $path->toString(); | ||
// then | ||
$this->assertSameWindows('first\second\third\file.txt', $asString); | ||
$this->assertSameUnix("first/second/third/file.txt", $asString); | ||
} | ||
|
||
public function pathPieces(): array | ||
{ | ||
return [ | ||
[["first", "second", "third", "file.txt"]], | ||
|
||
[["first", "/second", "/third", "/file.txt"]], | ||
[["first", "\second", '\third', '\file.txt']], | ||
|
||
[["first/", "second/", "third/", "file.txt"]], | ||
[["first\\", "second\\", "third\\", "file.txt"]], | ||
|
||
[["first/", "/second/", "/third/", "/file.txt"]], | ||
[["first\\", "\\second\\", '\\third\\', '\\file.txt']], | ||
]; | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider paths | ||
*/ | ||
public function shouldRepresentPath(string $stringPath) | ||
{ | ||
// given | ||
$path = Path::of($stringPath); | ||
// when | ||
$asString = $path->toString(); | ||
// then | ||
$this->assertSameWindows('one\two\three\file.txt', $asString); | ||
$this->assertSameUnix("one/two/three/file.txt", $asString); | ||
} | ||
|
||
public function paths(): array | ||
{ | ||
return [['one\two\three\file.txt'], ["one/two/three/file.txt"]]; | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider children | ||
*/ | ||
public function shouldAppendPath(Path $path, string $appendant) | ||
{ | ||
// when | ||
$childPath = $path->append($appendant); | ||
// then | ||
$this->assertSameWindows('uno\dos\tres', $childPath->toString()); | ||
$this->assertSameUnix("uno/dos/tres", $childPath->toString()); | ||
} | ||
|
||
public function children(): array | ||
{ | ||
return [ | ||
[Path::of("uno/dos"), "tres"], | ||
[Path::of("uno/dos"), '\tres'], | ||
[Path::of("uno/dos"), "/tres"], | ||
[Path::of("uno/dos"), "tres/"], | ||
[Path::of("uno/dos"), "tres\\"], | ||
|
||
[Path::of("uno/dos/"), "tres"], | ||
[Path::of("uno/dos/"), '\tres'], | ||
[Path::of("uno/dos/"), "/tres"], | ||
|
||
[Path::of("uno/dos\\"), "tres"], | ||
[Path::of("uno/dos\\"), '\tres'], | ||
[Path::of("uno/dos\\"), "/tres"], | ||
]; | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function shouldAcceptPathWithDriveOnWindows() | ||
{ | ||
if ($this->isUnix()) { | ||
$this->markTestUnnecessary("There are no drives on Unix"); | ||
} | ||
// given | ||
$path = Path::of("C:\directory"); | ||
// when | ||
$child = $path->append("file.txt"); | ||
// then | ||
$this->assertSame('C:\directory\file.txt', $child->toString()); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function shouldRemainAbsolutePathOnUnix() | ||
{ | ||
if (!$this->isUnix()) { | ||
$this->markTestUnnecessary("There are no leading separators on Windows"); | ||
} | ||
// given | ||
$path = Path::of("/usr/bin"); | ||
// when | ||
$child = $path->append("local"); | ||
// then | ||
$this->assertSame("/usr/bin/local", $child->toString()); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function shouldBeImmutable() | ||
{ | ||
// given | ||
$path = Path::of("one/two/three"); | ||
// when | ||
$this->assertSame($path->toString(), $path->toString()); | ||
} | ||
} |