diff --git a/.gitignore b/.gitignore index 8180267..0794651 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /vendor/ composer.lock - +.phpunit.result.cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcf852..b0fcc19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 2.0.0 +### Added +- Way to provide options to the mapping. These options can then be used within your library. + +### Changed +- Interface `\Youwe\FileMapping\FileMappingInterface` has a new method `getOptions(): array`. + +### Removed +- Support for PHP < 8.1 + ## 1.1.2 ### Fixed - Issue where in PHP 8, Deprecation Notices were thrown about missing return types. diff --git a/README.md b/README.md index 8831558..9c79629 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,63 @@ A PHP package for mapping files from one location to another. It is used by the [youwe/composer-file-installer](https://github.com/YouweGit/composer-file-installer) package to move installed files according to the location mapping. +## Mapping notation + +The mapping can contain `{source,destination}` placeholders, which will be replaced with the source or destination part +relatively. A mapping string can contain multiple `{source,destination}` placeholders. + +Examples mappings: + +| Mapping string | Translates to source path | Translates to destination path | +|----------------------------------|----------------------------|--------------------------------| +| `file.php` | `file.php` | `file.php` | +| `{dot,.}gitignore` | `dotgitignore` | `.gitignore` | +| `{default/,}config.yaml{.dist,}` | `default/config.yaml.dist` | `config.yaml` | + +## Mapping file + +All mappings can be stored in and read from a file in your project/library. In that case, adding options to the mapping +should be done with a colon-separated string. The actual meaning of the options depends on the implementation actually +using the mappings, but an example of an option could be to indicate that contents should be force overwritten. + +Example mapping file: +```text +file1.php +file2.php:option1:option2 +{dot,.}gitignore:merge:force +``` + ## Usage examples ```php getRelativeDestination(); */ $mapping->getDestination(); +/** + * Get the options from this mapping + */ +$mapping->getOptions(); ``` diff --git a/composer.json b/composer.json index ca746ae..f4b5987 100644 --- a/composer.json +++ b/composer.json @@ -16,12 +16,11 @@ } ], "require": { - "php": "^7.2 || ^8.0" + "php": "^8.1" }, "require-dev": { - "kint-php/kint": "@stable", - "mikey179/vfsstream": "@stable", - "phpunit/phpunit": "@stable" + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^12.3" }, "autoload": { "psr-4": { diff --git a/src/FileMappingInterface.php b/src/FileMappingInterface.php index 3c472b7..b5524af 100644 --- a/src/FileMappingInterface.php +++ b/src/FileMappingInterface.php @@ -38,4 +38,9 @@ public function getRelativeDestination(): string; * @return string */ public function getDestination(): string; + + /** + * @return string[] + */ + public function getOptions(): array; } diff --git a/src/UnixFileMapping.php b/src/UnixFileMapping.php index 3188e42..56b3fc7 100644 --- a/src/UnixFileMapping.php +++ b/src/UnixFileMapping.php @@ -11,17 +11,13 @@ class UnixFileMapping implements FileMappingInterface { - /** @var string */ - private $sourceDirectory; + private readonly string $source; + private readonly string $destination; - /** @var string */ - private $destinationDirectory; - - /** @var string */ - private $source; - - /** @var string */ - private $destination; + /** + * @var string[] + */ + private readonly array $options; /** * Constructor. @@ -29,19 +25,20 @@ class UnixFileMapping implements FileMappingInterface * @param string $sourceDirectory * @param string $destinationDirectory * @param string $mapping + * @param string ...$options */ public function __construct( - string $sourceDirectory, - string $destinationDirectory, - string $mapping + private readonly string $sourceDirectory, + private readonly string $destinationDirectory, + string $mapping, + string ...$options, ) { - $this->sourceDirectory = $sourceDirectory; - $this->destinationDirectory = $destinationDirectory; - // Expand the source and destination. static $pattern = '/({(.*?),(.*?)})/'; $this->source = preg_replace($pattern, '$2', $mapping); $this->destination = preg_replace($pattern, '$3', $mapping); + + $this->options = $options; } /** @@ -87,4 +84,12 @@ public function getDestination(): string . DIRECTORY_SEPARATOR . $this->destination; } + + /** + * @return string[] + */ + public function getOptions(): array + { + return $this->options; + } } diff --git a/src/UnixFileMappingReader.php b/src/UnixFileMappingReader.php index 83582cc..3e9c890 100644 --- a/src/UnixFileMappingReader.php +++ b/src/UnixFileMappingReader.php @@ -10,48 +10,43 @@ namespace Youwe\FileMapping; use ArrayIterator; -use Iterator; use SplFileObject; class UnixFileMappingReader implements FileMappingReaderInterface { - /** @var array */ - private $mappingFilePaths; - - /** @var string */ - private $sourceDirectory; - - /** @var string */ - private $targetDirectory; + /** + * @var string[] + */ + private readonly array $mappingFilePaths; - /** @var Iterator|FileMappingInterface[] */ - private $mappings; + /** + * @var ArrayIterator + */ + private ArrayIterator $mappings; /** * Constructor. * * @param string $sourceDirectory * @param string $targetDirectory - * @param string[] ...$mappingFilePaths + * @param string ...$mappingFilePaths */ public function __construct( - string $sourceDirectory, - string $targetDirectory, + private readonly string $sourceDirectory, + private readonly string $targetDirectory, string ...$mappingFilePaths ) { - $this->sourceDirectory = $sourceDirectory; - $this->targetDirectory = $targetDirectory; $this->mappingFilePaths = $mappingFilePaths; } /** * Get the mappings. * - * @return Iterator + * @return ArrayIterator */ - private function getMappings(): Iterator + private function getMappings(): ArrayIterator { - if ($this->mappings === null) { + if (!isset($this->mappings)) { $filePaths = []; foreach ($this->mappingFilePaths as $mappingFilePath) { @@ -62,16 +57,24 @@ private function getMappings(): Iterator $this->mappings = new ArrayIterator( array_map( function (string $mapping): FileMappingInterface { + // Trim line as filenames normally don't contain spaces but the mapping file can (accidentally) contain trailing whitespace + $mapping = trim($mapping); + if (!str_contains($mapping, ':')) { + $options = []; + } else { + [$mapping, $options] = explode(':', $mapping, 2); + $options = explode(':', $options); + } + return new UnixFileMapping( $this->sourceDirectory, $this->targetDirectory, - trim($mapping) + $mapping, + ...$options, ); }, // Filter out empty lines. - array_filter( - $filePaths - ) + array_values(array_filter($filePaths)) ) ); } diff --git a/tests/UnixFileMappingReaderTest.php b/tests/UnixFileMappingReaderTest.php index 3a2c196..38b58f5 100644 --- a/tests/UnixFileMappingReaderTest.php +++ b/tests/UnixFileMappingReaderTest.php @@ -9,52 +9,56 @@ namespace Youwe\FileMapping\Tests; +use PHPUnit\Framework\Attributes\CoversClass; use Youwe\FileMapping\FileMappingInterface; use org\bovigo\vfs\vfsStream; use PHPUnit\Framework\TestCase; use Youwe\FileMapping\UnixFileMappingReader; -/** - * @coversDefaultClass \Youwe\FileMapping\UnixFileMappingReader - */ +#[CoversClass(UnixFileMappingReader::class)] class UnixFileMappingReaderTest extends TestCase { - /** - * @return void - * - * @covers ::__construct - * @covers ::getMappings - * @covers ::next - * @covers ::key - * @covers ::valid - * @covers ::rewind - * @covers ::current - */ - public function testIteration() + public function testIteration(): void { $fileSystem = vfsStream::setup( sha1(__METHOD__), null, [ - 'files' => '{foo,bar}.php', - 'source' => [ - 'foo.php' => 'Foo' - ], + 'files' => + "{foo,bar}.php\n{templates/dot,.}gitignore:merge:force\n", + 'source' => [], 'destination' => [] ] ); - $mappings = new UnixFileMappingReader( - $fileSystem->getChild('source')->url(), - $fileSystem->getChild('destination')->url(), - $fileSystem->getChild('files')->url(), + $sourceDirectory = $fileSystem->getChild('source')->url(); + $destinationDirectory = $fileSystem->getChild('destination')->url(); + $mappingsReader = new UnixFileMappingReader( + $sourceDirectory, + $destinationDirectory, $fileSystem->getChild('files')->url() ); - foreach ($mappings as $offset => $mapping) { - $this->assertInstanceOf(FileMappingInterface::class, $mapping); - $this->assertIsInt($offset); - $this->assertFileExists($mapping->getSource()); - } + /** @var FileMappingInterface[] $mappings */ + $mappings = iterator_to_array($mappingsReader); + + $this->assertIsList($mappings); + $this->assertCount(2, $mappings); + + // Verify mapping '{foo,bar}.php' + $this->assertInstanceOf(FileMappingInterface::class, $mappings[0]); + $this->assertSame('foo.php', $mappings[0]->getRelativeSource()); + $this->assertSame($sourceDirectory . DIRECTORY_SEPARATOR . 'foo.php', $mappings[0]->getSource()); + $this->assertSame('bar.php', $mappings[0]->getRelativeDestination()); + $this->assertSame($destinationDirectory . DIRECTORY_SEPARATOR . 'bar.php', $mappings[0]->getDestination()); + $this->assertSame([], $mappings[0]->getOptions()); + + // Verify mapping '{templates/dot,.}gitignore:merge:force' + $this->assertInstanceOf(FileMappingInterface::class, $mappings[1]); + $this->assertSame('templates/dotgitignore', $mappings[1]->getRelativeSource()); + $this->assertSame($sourceDirectory . DIRECTORY_SEPARATOR . 'templates/dotgitignore', $mappings[1]->getSource()); + $this->assertSame('.gitignore', $mappings[1]->getRelativeDestination()); + $this->assertSame($destinationDirectory . DIRECTORY_SEPARATOR . '.gitignore', $mappings[1]->getDestination()); + $this->assertSame(['merge', 'force'], $mappings[1]->getOptions()); } } diff --git a/tests/UnixFileMappingTest.php b/tests/UnixFileMappingTest.php index db1fcff..02f17c8 100644 --- a/tests/UnixFileMappingTest.php +++ b/tests/UnixFileMappingTest.php @@ -10,18 +10,18 @@ namespace Youwe\FileMapping\Tests; use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Youwe\FileMapping\UnixFileMapping; -/** - * @coversDefaultClass \Youwe\FileMapping\UnixFileMapping - */ +#[CoversClass(UnixFileMapping::class)] class UnixFileMappingTest extends TestCase { /** * @return string[][] */ - public function mappingProvider(): array + public static function mappingProvider(): array { return [ [ @@ -42,38 +42,19 @@ public function mappingProvider(): array ]; } - /** - * @dataProvider mappingProvider - * - * @param string $mapping - * @param string $expectedSource - * @param string $expectedDestination - * - * @return UnixFileMapping - * - * @covers ::__construct - * @covers ::getRelativeSource - * @covers ::getRelativeDestination - */ + #[DataProvider('mappingProvider')] public function testMapping( string $mapping, string $expectedSource, string $expectedDestination - ): UnixFileMapping { + ): void { $mapping = new UnixFileMapping('.', '.', $mapping); $this->assertEquals($expectedSource, $mapping->getRelativeSource()); $this->assertEquals($expectedDestination, $mapping->getRelativeDestination()); - - return $mapping; } - /** - * @return void - * @covers ::getSource - * @covers ::getDestination - */ - public function testDirectoryResolving() + public function testDirectoryResolving(): void { $fs = vfsStream::setup( sha1(__METHOD__), @@ -93,6 +74,6 @@ public function testDirectoryResolving() ); $this->assertFileExists($mapping->getSource()); - $this->assertFileNotExists($mapping->getDestination()); + $this->assertFileDoesNotExist($mapping->getDestination()); } }