Unix path
parent
93df980c8a
commit
cfec611306
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Arokettu\Path;
|
||||
|
||||
interface AbsolutePathInterface extends PathInterface
|
||||
{
|
||||
/**
|
||||
* @param static|string $path
|
||||
* @return static
|
||||
*/
|
||||
public function makeRelative($path, bool $strict = false): self;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Arokettu\Path;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractAbsolutePath extends AbstractPath implements AbsolutePathInterface
|
||||
{
|
||||
/**
|
||||
* @param static|string $path
|
||||
* @param bool $strict
|
||||
* @return static
|
||||
*/
|
||||
public function makeRelative($path, bool $strict = false): self
|
||||
{
|
||||
throw new \BadMethodCallException('not implemented');
|
||||
}
|
||||
}
|
|
@ -14,11 +14,11 @@ abstract class AbstractPath implements PathInterface
|
|||
protected string $prefix;
|
||||
protected \SplDoublyLinkedList $components;
|
||||
|
||||
abstract protected function parsePath(string $path): void;
|
||||
abstract protected function parsePath(string $path, bool $strict): void;
|
||||
|
||||
public function __construct(string $path)
|
||||
public function __construct(string $path, bool $strict = false)
|
||||
{
|
||||
$this->parsePath($path);
|
||||
$this->parsePath($path, $strict);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,12 +101,16 @@ abstract class AbstractPath implements PathInterface
|
|||
continue;
|
||||
}
|
||||
|
||||
if ($component === '..' && $prevComponent !== '..' && $prevComponent !== null) {
|
||||
if (
|
||||
$component === '..' &&
|
||||
$prevComponent !== '..' && $prevComponent !== null && // leading ..'s
|
||||
$componentsList->count() > 0 // beginning of the list
|
||||
) {
|
||||
$componentsList->pop();
|
||||
} else {
|
||||
$componentsList->push($component);
|
||||
continue;
|
||||
}
|
||||
|
||||
$componentsList->push($component);
|
||||
$prevComponent = $component;
|
||||
}
|
||||
|
||||
|
@ -146,6 +150,11 @@ abstract class AbstractPath implements PathInterface
|
|||
return $this->toString();
|
||||
}
|
||||
|
||||
public function getPrefix(): string
|
||||
{
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
public function getComponents(): array
|
||||
{
|
||||
return iterator_to_array($this->components, false);
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Arokettu\Path;
|
||||
|
||||
abstract class FilesystemPath extends AbstractAbsolutePath
|
||||
{
|
||||
public function __construct(string $path, bool $strict = false)
|
||||
{
|
||||
if ($this instanceof WindowsPath || $this instanceof UnixPath) {
|
||||
parent::__construct($path, $strict);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \LogicException('The class is not meant to be extended externally');
|
||||
}
|
||||
|
||||
public static function parse(string $path, bool $strict = false): self
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
return new WindowsPath($path, $strict);
|
||||
}
|
||||
|
||||
if (DIRECTORY_SEPARATOR === '/') {
|
||||
return new UnixPath($path, $strict);
|
||||
}
|
||||
|
||||
throw new \LogicException('Unknown directory separator: ' . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ namespace Arokettu\Path;
|
|||
|
||||
interface PathInterface extends \Stringable
|
||||
{
|
||||
public function getPrefix(): string;
|
||||
public function getComponents(): array;
|
||||
public function toString(): string;
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ final class RelativePath extends AbstractPath implements RelativePathInterface
|
|||
return new self($path, true);
|
||||
}
|
||||
|
||||
protected function parsePath(string $path): void
|
||||
protected function parsePath(string $path, bool $strict): void
|
||||
{
|
||||
$components = explode('/', $path);
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Arokettu\Path;
|
||||
|
||||
final class UnixPath extends FilesystemPath
|
||||
{
|
||||
public static function parse(string $path, bool $strict = false): self
|
||||
{
|
||||
return new self($path, $strict);
|
||||
}
|
||||
|
||||
protected function parsePath(string $path, bool $strict): void
|
||||
{
|
||||
if ($path[0] !== '/') {
|
||||
throw new \InvalidArgumentException('Valid unix path must begin with a slash');
|
||||
}
|
||||
|
||||
$components = explode('/', $path);
|
||||
|
||||
$parsedComponents = $this->normalize($components);
|
||||
|
||||
if ($parsedComponents[0] === '..') {
|
||||
if ($strict) {
|
||||
throw new \InvalidArgumentException('Path went beyond root');
|
||||
}
|
||||
|
||||
do {
|
||||
$parsedComponents->shift();
|
||||
} while ($parsedComponents[0] === '..');
|
||||
}
|
||||
|
||||
$this->prefix = '/';
|
||||
$this->components = $parsedComponents;
|
||||
}
|
||||
}
|
|
@ -14,16 +14,16 @@ class RelativePathTest extends TestCase
|
|||
public function testCreate(): void
|
||||
{
|
||||
// 'absolute' relative path
|
||||
$path = RelativePath::unix('/i/am/./skipme/../test/./path');
|
||||
self::assertEquals('/i/am/test/path', $path->toString());
|
||||
$path = RelativePath::unix('/i/am/./skipme/../test/./relative/path');
|
||||
self::assertEquals('/i/am/test/relative/path', $path->toString());
|
||||
|
||||
// relative path from the current directory
|
||||
$path = RelativePath::unix('./i/am/./skipme/../test/./path');
|
||||
self::assertEquals('i/am/test/path', $path->toString());
|
||||
$path = RelativePath::unix('./i/am/./skipme/../test/./relative/path');
|
||||
self::assertEquals('i/am/test/relative/path', $path->toString());
|
||||
|
||||
// relative path from the parent directory
|
||||
$path = RelativePath::unix('.././../i/am/./skipme/../test/./path');
|
||||
self::assertEquals('../../i/am/test/path', $path->toString());
|
||||
$path = RelativePath::unix('.././../i/am/./skipme/../test/./relative/path');
|
||||
self::assertEquals('../../i/am/test/relative/path', $path->toString());
|
||||
}
|
||||
|
||||
public function testCreateWindows(): void
|
||||
|
@ -44,37 +44,37 @@ class RelativePathTest extends TestCase
|
|||
public function testResolveRelative(): void
|
||||
{
|
||||
$paths = [
|
||||
new RelativePath('/i/am/test/path'),
|
||||
new RelativePath('i/am/test/path'),
|
||||
new RelativePath('../../i/am/test/path'),
|
||||
new RelativePath('../../../../../../../../i/am/test/path'),
|
||||
new RelativePath('/i/am/test/relative/path'),
|
||||
new RelativePath('i/am/test/relative/path'),
|
||||
new RelativePath('../../i/am/test/relative/path'),
|
||||
new RelativePath('../../../../../../../../i/am/test/relative/path'),
|
||||
];
|
||||
$relativePaths = $paths;
|
||||
|
||||
$matrix = [
|
||||
[
|
||||
'/i/am/test/path',
|
||||
'/i/am/test/path/i/am/test/path',
|
||||
'/i/am/i/am/test/path',
|
||||
'/i/am/test/path',
|
||||
'/i/am/test/relative/path',
|
||||
'/i/am/test/relative/path/i/am/test/relative/path',
|
||||
'/i/am/test/i/am/test/relative/path',
|
||||
'/i/am/test/relative/path',
|
||||
],
|
||||
[
|
||||
'/i/am/test/path',
|
||||
'i/am/test/path/i/am/test/path',
|
||||
'i/am/i/am/test/path',
|
||||
'../../../../i/am/test/path',
|
||||
'/i/am/test/relative/path',
|
||||
'i/am/test/relative/path/i/am/test/relative/path',
|
||||
'i/am/test/i/am/test/relative/path',
|
||||
'../../../i/am/test/relative/path',
|
||||
],
|
||||
[
|
||||
'/i/am/test/path',
|
||||
'../../i/am/test/path/i/am/test/path',
|
||||
'../../i/am/i/am/test/path',
|
||||
'../../../../../../i/am/test/path',
|
||||
'/i/am/test/relative/path',
|
||||
'../../i/am/test/relative/path/i/am/test/relative/path',
|
||||
'../../i/am/test/i/am/test/relative/path',
|
||||
'../../../../../i/am/test/relative/path',
|
||||
],
|
||||
[
|
||||
'/i/am/test/path',
|
||||
'../../../../../../../../i/am/test/path/i/am/test/path',
|
||||
'../../../../../../../../i/am/i/am/test/path',
|
||||
'../../../../../../../../../../../../i/am/test/path',
|
||||
'/i/am/test/relative/path',
|
||||
'../../../../../../../../i/am/test/relative/path/i/am/test/relative/path',
|
||||
'../../../../../../../../i/am/test/i/am/test/relative/path',
|
||||
'../../../../../../../../../../../i/am/test/relative/path',
|
||||
],
|
||||
];
|
||||
|
||||
|
@ -90,37 +90,37 @@ class RelativePathTest extends TestCase
|
|||
public function testResolveRelativeStrict(): void
|
||||
{
|
||||
$paths = [
|
||||
new RelativePath('/i/am/test/path'),
|
||||
new RelativePath('i/am/test/path'),
|
||||
new RelativePath('../../i/am/test/path'),
|
||||
new RelativePath('../../../../../../../../i/am/test/path'),
|
||||
new RelativePath('/i/am/test/relative/path'),
|
||||
new RelativePath('i/am/test/relative/path'),
|
||||
new RelativePath('../../i/am/test/relative/path'),
|
||||
new RelativePath('../../../../../../../../i/am/test/relative/path'),
|
||||
];
|
||||
$relativePaths = $paths;
|
||||
|
||||
$matrix = [
|
||||
[
|
||||
'/i/am/test/path',
|
||||
'/i/am/test/path/i/am/test/path',
|
||||
'/i/am/i/am/test/path',
|
||||
'/i/am/test/relative/path',
|
||||
'/i/am/test/relative/path/i/am/test/relative/path',
|
||||
'/i/am/test/i/am/test/relative/path',
|
||||
null,
|
||||
],
|
||||
[
|
||||
'/i/am/test/path',
|
||||
'i/am/test/path/i/am/test/path',
|
||||
'i/am/i/am/test/path',
|
||||
'../../../../i/am/test/path',
|
||||
'/i/am/test/relative/path',
|
||||
'i/am/test/relative/path/i/am/test/relative/path',
|
||||
'i/am/test/i/am/test/relative/path',
|
||||
'../../../i/am/test/relative/path',
|
||||
],
|
||||
[
|
||||
'/i/am/test/path',
|
||||
'../../i/am/test/path/i/am/test/path',
|
||||
'../../i/am/i/am/test/path',
|
||||
'../../../../../../i/am/test/path',
|
||||
'/i/am/test/relative/path',
|
||||
'../../i/am/test/relative/path/i/am/test/relative/path',
|
||||
'../../i/am/test/i/am/test/relative/path',
|
||||
'../../../../../i/am/test/relative/path',
|
||||
],
|
||||
[
|
||||
'/i/am/test/path',
|
||||
'../../../../../../../../i/am/test/path/i/am/test/path',
|
||||
'../../../../../../../../i/am/i/am/test/path',
|
||||
'../../../../../../../../../../../../i/am/test/path',
|
||||
'/i/am/test/relative/path',
|
||||
'../../../../../../../../i/am/test/relative/path/i/am/test/relative/path',
|
||||
'../../../../../../../../i/am/test/i/am/test/relative/path',
|
||||
'../../../../../../../../../../../i/am/test/relative/path',
|
||||
],
|
||||
];
|
||||
|
||||
|
@ -142,24 +142,29 @@ class RelativePathTest extends TestCase
|
|||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Relative path went beyond root');
|
||||
|
||||
$p = new RelativePath('/i/am/test/path');
|
||||
$rp = new RelativePath('../../../../../../../../i/am/test/path');
|
||||
$p = new RelativePath('/i/am/test/relative/path');
|
||||
$rp = new RelativePath('../../../../../../../../i/am/test/relative/path');
|
||||
|
||||
$p->resolveRelative($rp, true);
|
||||
}
|
||||
|
||||
public function testExternalRelativeImplementations(): void
|
||||
{
|
||||
$p = new RelativePath('i/am/test/path');
|
||||
$p = new RelativePath('i/am/test/relative/path');
|
||||
|
||||
$rp1 = new class implements RelativePathInterface {
|
||||
public function __toString(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getPrefix(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getComponents(): array
|
||||
{
|
||||
return explode('/', '../../i/am/test/path');
|
||||
return explode('/', '../../i/am/test/relative/path');
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
|
@ -179,13 +184,19 @@ class RelativePathTest extends TestCase
|
|||
};
|
||||
|
||||
$rp2 = new class implements RelativePathInterface {
|
||||
public function __toString(): string {
|
||||
public function __toString(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getPrefix(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getComponents(): array
|
||||
{
|
||||
return explode('/', 'i/am/test/path');
|
||||
return explode('/', 'i/am/test/relative/path');
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
|
@ -204,7 +215,7 @@ class RelativePathTest extends TestCase
|
|||
}
|
||||
};
|
||||
|
||||
self::assertEquals('i/am/i/am/test/path', $p->resolveRelative($rp1)->toString());
|
||||
self::assertEquals('/i/am/test/path', $p->resolveRelative($rp2)->toString());
|
||||
self::assertEquals('i/am/test/i/am/test/relative/path', $p->resolveRelative($rp1)->toString());
|
||||
self::assertEquals('/i/am/test/relative/path', $p->resolveRelative($rp2)->toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Arokettu\Path\Tests;
|
||||
|
||||
use Arokettu\Path\RelativePath;
|
||||
use Arokettu\Path\UnixPath;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UnixPathTest extends TestCase
|
||||
{
|
||||
public function testCreate(): void
|
||||
{
|
||||
$path1 = UnixPath::parse('/i/./am/./skipme/./.././test/./unix/path');
|
||||
self::assertEquals('/i/am/test/unix/path', $path1->toString());
|
||||
|
||||
$path2 = UnixPath::parse('/invalid/level/of/nesting/../../../../../../../../../../i/am/test/unix/path');
|
||||
self::assertEquals('/i/am/test/unix/path', $path2->toString());
|
||||
|
||||
$path3 = UnixPath::parse('/i/./am/./skipme/./.././test/./unix/path', true);
|
||||
self::assertEquals('/i/am/test/unix/path', $path3->toString());
|
||||
}
|
||||
|
||||
public function testCreateStrict(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Path went beyond root');
|
||||
|
||||
UnixPath::parse('/invalid/level/of/nesting/../../../../../../../../../../i/am/test/unix/path', true);
|
||||
}
|
||||
|
||||
public function testCreateInvalid(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Valid unix path must begin with a slash');
|
||||
|
||||
UnixPath::parse('not/starting/with/slash', true);
|
||||
}
|
||||
|
||||
public function testResolveRelative(): void
|
||||
{
|
||||
$path = UnixPath::parse('/i/am/test/unix/path');
|
||||
|
||||
$rp1 = new RelativePath('/i/am/test/relative/path');
|
||||
self::assertEquals(
|
||||
'/i/am/test/relative/path',
|
||||
$path->resolveRelative($rp1)->toString()
|
||||
);
|
||||
|
||||
$rp2 = new RelativePath('i/am/test/relative/path');
|
||||
self::assertEquals(
|
||||
'/i/am/test/unix/path/i/am/test/relative/path',
|
||||
$path->resolveRelative($rp2)->toString()
|
||||
);
|
||||
|
||||
$rp3 = new RelativePath('../../i/am/test/relative/path');
|
||||
self::assertEquals(
|
||||
'/i/am/test/i/am/test/relative/path',
|
||||
$path->resolveRelative($rp3)->toString()
|
||||
);
|
||||
|
||||
$rp4 = new RelativePath('../../../../../../../../i/am/test/relative/path');
|
||||
self::assertEquals(
|
||||
'/i/am/test/relative/path',
|
||||
$path->resolveRelative($rp4)->toString()
|
||||
);
|
||||
}
|
||||
|
||||
public function testResolveRelativeStrict(): void
|
||||
{
|
||||
$path = UnixPath::parse('/i/am/test/unix/path');
|
||||
|
||||
$rp1 = new RelativePath('/i/am/test/relative/path');
|
||||
self::assertEquals(
|
||||
'/i/am/test/relative/path',
|
||||
$path->resolveRelative($rp1, true)->toString()
|
||||
);
|
||||
|
||||
$rp2 = new RelativePath('i/am/test/relative/path');
|
||||
self::assertEquals(
|
||||
'/i/am/test/unix/path/i/am/test/relative/path',
|
||||
$path->resolveRelative($rp2, true)->toString()
|
||||
);
|
||||
|
||||
$rp3 = new RelativePath('../../i/am/test/relative/path');
|
||||
self::assertEquals(
|
||||
'/i/am/test/i/am/test/relative/path',
|
||||
$path->resolveRelative($rp3, true)->toString()
|
||||
);
|
||||
}
|
||||
|
||||
public function testResolveRelativeStrictInvalid(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Relative path went beyond root');
|
||||
|
||||
$path = UnixPath::parse('/i/am/test/unix/path');
|
||||
$rp4 = new RelativePath('../../../../../../../../i/am/test/relative/path');
|
||||
$path->resolveRelative($rp4, true);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue