Skip to content

Commit 51df2ed

Browse files
committed
[FEATURE] Add fractor for yaml files
1 parent db64782 commit 51df2ed

19 files changed

+406
-1
lines changed

.github/workflows/lint_test_pull_requests.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
command: test:php
2222
- name: Rector
2323
command: 'rector --dry-run'
24-
directory: [ 'extension-installer', 'fractor', 'fractor-xml', 'typo3-fractor', 'fractor-doc-generator' ]
24+
directory: [ 'extension-installer', 'fractor', 'fractor-xml', 'typo3-fractor', 'fractor-doc-generator', 'fractor-yaml']
2525
exclude:
2626
- directory: extension-installer
2727
composer-command: {name: 'PHPUnit', command: 'test:php'}

fractor-yaml/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/vendor/
2+
/composer.lock
3+
.phpunit.cache

fractor-yaml/composer.json

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"name": "a9f/fractor-yaml",
3+
"description": "YAML extension for the File Read-Analyse-Change TOol. Allows modifying YAML files",
4+
"license": "MIT",
5+
"type": "fractor-extension",
6+
"authors": [
7+
{
8+
"name": "Andreas Wolf",
9+
"email": "[email protected]",
10+
"role": "Lead Developer"
11+
}
12+
],
13+
"require": {
14+
"php": "^8.2",
15+
"a9f/fractor": "@dev",
16+
"a9f/fractor-extension-installer": "@dev",
17+
"symfony/yaml": "^6.0",
18+
"webmozart/assert": "^1.11"
19+
},
20+
"require-dev": {
21+
"ergebnis/composer-normalize": "^2.42",
22+
"phpstan/phpstan": "^1.10",
23+
"phpunit/phpunit": "^10.5",
24+
"rector/rector": "^1.0",
25+
"symplify/easy-coding-standard": "^12.1"
26+
},
27+
"repositories": {
28+
"fractor": {
29+
"type": "path",
30+
"url": "../*"
31+
}
32+
},
33+
"autoload": {
34+
"psr-4": {
35+
"a9f\\FractorYaml\\": "src/"
36+
}
37+
},
38+
"autoload-dev": {
39+
"psr-4": {
40+
"a9f\\FractorYaml\\Tests\\": "tests/"
41+
}
42+
},
43+
"config": {
44+
"allow-plugins": {
45+
"a9f/fractor-extension-installer": true,
46+
"ergebnis/composer-normalize": true
47+
},
48+
"sort-packages": true
49+
},
50+
"scripts": {
51+
"analyze:php": "phpstan analyze",
52+
"rector": "rector",
53+
"style:php:check": "ecs",
54+
"style:php:fix": "ecs --fix",
55+
"test:php": "phpunit"
56+
}
57+
}

fractor-yaml/config/application.php

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
use a9f\FractorYaml\Contract\YamlDumper;
4+
use a9f\FractorYaml\Contract\YamlFractorRule;
5+
use a9f\FractorYaml\Contract\YamlParser;
6+
use a9f\FractorYaml\SymfonyYamlDumper;
7+
use a9f\FractorYaml\SymfonyYamlParser;
8+
use a9f\FractorYaml\YamlFileProcessor;
9+
use Symfony\Component\DependencyInjection\ContainerBuilder;
10+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
11+
use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;
12+
13+
return static function (ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void {
14+
$services = $containerConfigurator->services();
15+
$services->defaults()
16+
->autowire()
17+
->autoconfigure();
18+
19+
$services->load('a9f\\FractorYaml\\', __DIR__ . '/../src/');
20+
21+
$services->alias(YamlParser::class, SymfonyYamlParser::class);
22+
$services->alias(YamlDumper::class, SymfonyYamlDumper::class);
23+
24+
$services->set(YamlFileProcessor::class)->arg('$rules', tagged_iterator('fractor.yaml_rule'));
25+
26+
$containerBuilder->registerForAutoconfiguration(YamlFractorRule::class)->addTag('fractor.yaml_rule');
27+
};

fractor-yaml/ecs.php

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return (include __DIR__ . '/../.build/ecs.php')
6+
->withPaths([
7+
__DIR__ . '/config',
8+
__DIR__ . '/src',
9+
__DIR__ . '/tests',
10+
])
11+
;

fractor-yaml/phpstan.neon

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
parameters:
2+
level: 8
3+
4+
paths:
5+
- src/
6+
- tests/

fractor-yaml/phpunit.xml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" cacheDirectory=".phpunit.cache">
3+
<testsuites>
4+
<testsuite name="fractor-yaml">
5+
<directory>tests</directory>
6+
</testsuite>
7+
</testsuites>
8+
<source>
9+
<include>
10+
<directory>./src</directory>
11+
</include>
12+
</source>
13+
</phpunit>

fractor-yaml/rector.php

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return (include __DIR__ . '/../.build/rector.php')
6+
->withPaths([
7+
__DIR__ . '/config',
8+
__DIR__ . '/src',
9+
__DIR__ . '/tests',
10+
]);
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace a9f\FractorYaml\Contract;
5+
6+
use a9f\Fractor\ValueObject\Indent;
7+
8+
interface YamlDumper
9+
{
10+
public function dump(array $input, Indent $indent): string;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace a9f\FractorYaml\Contract;
5+
6+
use a9f\Fractor\Application\Contract\FractorRule;
7+
8+
interface YamlFractorRule extends FractorRule
9+
{
10+
/**
11+
* @param mixed[] $yaml
12+
* @return mixed[]
13+
*/
14+
public function refactor(array $yaml): array;
15+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace a9f\FractorYaml\Contract;
5+
6+
use a9f\Fractor\Application\ValueObject\File;
7+
use a9f\FractorYaml\Exception\ParseException;
8+
9+
interface YamlParser
10+
{
11+
/**
12+
* @throws ParseException
13+
*/
14+
public function parse(File $file): array;
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace a9f\FractorYaml\Exception;
5+
6+
final class ParseException extends \RuntimeException
7+
{
8+
9+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace a9f\FractorYaml;
5+
6+
use a9f\Fractor\ValueObject\Indent;
7+
use a9f\FractorYaml\Contract\YamlDumper;
8+
use Symfony\Component\Yaml\Yaml;
9+
10+
final class SymfonyYamlDumper implements YamlDumper
11+
{
12+
13+
public function dump(array $input, Indent $indent): string
14+
{
15+
return Yaml::dump($input, 99, $indent->length());
16+
}
17+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace a9f\FractorYaml;
5+
6+
use a9f\Fractor\Application\ValueObject\File;
7+
use a9f\FractorYaml\Contract\YamlParser;
8+
use a9f\FractorYaml\Exception\ParseException;
9+
use Symfony\Component\Yaml\Exception\ParseException as YamlParseException;
10+
use Symfony\Component\Yaml\Yaml;
11+
12+
final class SymfonyYamlParser implements YamlParser
13+
{
14+
15+
public function parse(File $file): array
16+
{
17+
try {
18+
return Yaml::parse($file->getContent(), Yaml::PARSE_CUSTOM_TAGS) ?? [];
19+
} catch (YamlParseException $parseException) {
20+
$parseException->setParsedFile($file->getFilePath());
21+
22+
throw new ParseException($parseException->getMessage());
23+
}
24+
}
25+
}
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace a9f\FractorYaml;
5+
6+
use a9f\Fractor\Application\Contract\FileProcessor;
7+
use a9f\Fractor\Application\ValueObject\File;
8+
use a9f\Fractor\ValueObject\Indent;
9+
use a9f\FractorYaml\Contract\YamlDumper;
10+
use a9f\FractorYaml\Contract\YamlFractorRule;
11+
use a9f\FractorYaml\Contract\YamlParser;
12+
use Webmozart\Assert\Assert;
13+
14+
final readonly class YamlFileProcessor implements FileProcessor
15+
{
16+
/**
17+
* @param iterable<YamlFractorRule> $rules
18+
*/
19+
public function __construct(private iterable $rules, private YamlParser $yamlParser, private YamlDumper $yamlDumper)
20+
{
21+
Assert::allIsInstanceOf($this->rules, YamlFractorRule::class);
22+
}
23+
24+
public function canHandle(File $file): bool
25+
{
26+
return in_array($file->getFileExtension(), $this->allowedFileExtensions(), true);
27+
}
28+
29+
public function handle(File $file): void
30+
{
31+
$yaml = $this->yamlParser->parse($file);
32+
$indent = Indent::fromFile($file);
33+
34+
$newYaml = $yaml;
35+
36+
foreach ($this->rules as $rule) {
37+
$newYaml = $rule->refactor($newYaml);
38+
}
39+
40+
// Nothing has changed.
41+
if ($newYaml === $yaml) {
42+
return;
43+
}
44+
45+
$file->changeFileContent($this->yamlDumper->dump($newYaml, $indent));
46+
}
47+
48+
public function allowedFileExtensions(): array
49+
{
50+
return ['yaml', 'yml'];
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace a9f\FractorYaml\Tests\Fixtures;
5+
6+
use a9f\FractorYaml\Contract\YamlFractorRule;
7+
use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
8+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
9+
10+
final class DummyYamlFractorRule implements YamlFractorRule
11+
{
12+
/**
13+
* @var string
14+
*/
15+
private const TRANSLATION_FILE_KEY = 'translationFile';
16+
17+
public function getRuleDefinition(): RuleDefinition
18+
{
19+
throw new BadMethodCallException('Not implemented yet');
20+
}
21+
22+
public function refactor(array $yaml): array
23+
{
24+
return $this->refactorTranslationFile($yaml);
25+
}
26+
27+
/**
28+
* @param mixed[] $yaml
29+
* @return mixed[]
30+
* @see https://github.com/TYPO3/typo3/blob/10.4/typo3/sysext/form/Classes/Controller/FormEditorController.php#L653-L689
31+
*/
32+
private function refactorTranslationFile(array &$yaml): array
33+
{
34+
foreach ($yaml as &$section) {
35+
if (! is_array($section)) {
36+
continue;
37+
}
38+
39+
if (array_key_exists(self::TRANSLATION_FILE_KEY, $section)
40+
&& is_array($section[self::TRANSLATION_FILE_KEY])
41+
) {
42+
$section['translationFiles'] = $this->buildNewTranslations($section[self::TRANSLATION_FILE_KEY]);
43+
unset($section[self::TRANSLATION_FILE_KEY]);
44+
}
45+
46+
$this->refactorTranslationFile($section);
47+
}
48+
49+
unset($section);
50+
51+
return $yaml;
52+
}
53+
54+
/**
55+
* @param array<int, string> $oldTranslations
56+
*
57+
* @return array<int, string>
58+
*/
59+
private function buildNewTranslations(array $oldTranslations): array
60+
{
61+
return array_filter(
62+
$oldTranslations,
63+
static fn ($oldTranslationFile) => ! \str_starts_with($oldTranslationFile, 'EXT:form')
64+
);
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
TYPO3:
2+
CMS:
3+
Form:
4+
prototypes:
5+
standard:
6+
formElementsDefinition:
7+
Form:
8+
renderingOptions:
9+
translation:
10+
translationFile:
11+
10: 'EXT:form/Resources/Private/Language/locallang.xlf'
12+
20: 'EXT:myextension/Resources/Private/Language/locallang.xlf'
13+
-----
14+
TYPO3:
15+
CMS:
16+
Form:
17+
prototypes:
18+
standard:
19+
formElementsDefinition:
20+
Form:
21+
renderingOptions:
22+
translation:
23+
translationFiles:
24+
20: 'EXT:myextension/Resources/Private/Language/locallang.xlf'

0 commit comments

Comments
 (0)