Skip to content

Provide way to add options to your mapping #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/vendor/
composer.lock

.phpunit.result.cache
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
60 changes: 54 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<?php

use \Youwe\FileMapping\UnixFileMapping;
use \Youwe\FileMapping\UnixFileMappingReader;

/**
* Create a mapping.
*/
$mapping = new UnixFileMapping(
__DIR__ . '/../folder/files',
getcwd(),
['./dir/one','./dir/two']

);
$mapping = new UnixFileMapping(
sourceDirectory: __DIR__ . '/../folder/files',
destinationDirectory: getcwd(),
mapping: '{templates/dot,.}gitignore'
'option1',
'option2',
);

/**
* Or read mappings from a file
*/
$reader = new UnixFileMappingReader(
sourceDirectory: __DIR__ . '/../folder/files',
targetDirectory: getcwd(),
'path/to/mapping-file-1',
'path/to/mapping-file-2',
);
foreach ($reader as $mapping) {
// Use the mapping
}

/**
* Get the relative path to the source file.
Expand All @@ -36,5 +80,9 @@ $mapping->getRelativeDestination();
*/
$mapping->getDestination();

/**
* Get the options from this mapping
*/
$mapping->getOptions();
```

7 changes: 3 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
5 changes: 5 additions & 0 deletions src/FileMappingInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public function getRelativeDestination(): string;
* @return string
*/
public function getDestination(): string;

/**
* @return string[]
*/
public function getOptions(): array;
}
37 changes: 21 additions & 16 deletions src/UnixFileMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,34 @@

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.
*
* @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;
}

/**
Expand Down Expand Up @@ -87,4 +84,12 @@ public function getDestination(): string
. DIRECTORY_SEPARATOR
. $this->destination;
}

/**
* @return string[]
*/
public function getOptions(): array
{
return $this->options;
}
}
49 changes: 26 additions & 23 deletions src/UnixFileMappingReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<FileMappingInterface>
*/
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<FileMappingInterface>
*/
private function getMappings(): Iterator
private function getMappings(): ArrayIterator
{
if ($this->mappings === null) {
if (!isset($this->mappings)) {
$filePaths = [];

foreach ($this->mappingFilePaths as $mappingFilePath) {
Expand All @@ -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))
)
);
}
Expand Down
60 changes: 32 additions & 28 deletions tests/UnixFileMappingReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Loading