Skip to content

Commit b6782ac

Browse files
committed
[FEATURE] Add rules doc generator
1 parent 7fce3fc commit b6782ac

31 files changed

+915
-2
lines changed

.github/workflows/lint_test_pull_requests.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
command: analyze:php
2020
- name: PHPUnit
2121
command: test:php
22-
directory: ['extension-installer', 'fractor', 'fractor-xml', 'typo3-fractor']
22+
directory: ['extension-installer', 'fractor', 'fractor-xml', 'typo3-fractor', 'fractor-doc-generator']
2323
exclude:
2424
- directory: extension-installer
2525
composer-command: {name: 'PHPUnit', command: 'test:php'}

fractor-doc-generator/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/vendor/
2+
/composer.lock
3+
.phpunit.cache
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
include __DIR__ . '/fractor-doc-generator.php';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
4+
use a9f\FractorDocGenerator\DependencyInjection\ContainerContainerBuilder;
5+
use a9f\FractorDocGenerator\FractorDocGeneratorApplication;
6+
7+
$autoloadFile = (static function (): ?string {
8+
$candidates = [
9+
getcwd() . '/vendor/autoload.php',
10+
__DIR__ . '/../../../autoload.php',
11+
__DIR__ . '/../vendor/autoload.php',
12+
];
13+
foreach ($candidates as $candidate) {
14+
if (file_exists($candidate)) {
15+
return $candidate;
16+
}
17+
}
18+
return null;
19+
})();
20+
if ($autoloadFile === null) {
21+
echo "Could not find autoload.php file";
22+
exit(1);
23+
}
24+
25+
include $autoloadFile;
26+
27+
$container = (new ContainerContainerBuilder())->createDependencyInjectionContainer();
28+
29+
/** @var FractorDocGeneratorApplication $application */
30+
$application = $container->get(FractorDocGeneratorApplication::class);
31+
$application->run();

fractor-doc-generator/composer.json

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "a9f/fractor-doc-generator",
3+
"description": "Generate docs for Fractor",
4+
"license": "MIT",
5+
"type": "library",
6+
"authors": [
7+
{
8+
"name": "Andreas Wolf",
9+
"email": "[email protected]",
10+
"role": "Lead Developer"
11+
}
12+
],
13+
"require": {
14+
"php": "^8.2",
15+
"nette/utils": "^4.0",
16+
"sebastian/diff": "^5.0",
17+
"symfony/config": "^6.4",
18+
"symfony/console": "^6.4",
19+
"symfony/dependency-injection": "^6.4",
20+
"symplify/rule-doc-generator": "12.1.3",
21+
"symplify/rule-doc-generator-contracts": "^11.2"
22+
},
23+
"require-dev": {
24+
"ergebnis/composer-normalize": "^2.42",
25+
"phpstan/phpstan": "^1.10",
26+
"phpstan/phpstan-phpunit": "^1.3",
27+
"phpunit/phpunit": "^10.5",
28+
"symplify/easy-coding-standard": "^12.1"
29+
},
30+
"autoload": {
31+
"psr-4": {
32+
"a9f\\FractorDocGenerator\\": "src/"
33+
}
34+
},
35+
"autoload-dev": {
36+
"psr-4": {
37+
"a9f\\FractorDocGenerator\\Tests\\": "tests/"
38+
},
39+
"classmap": [
40+
"stubs"
41+
]
42+
},
43+
"bin": [
44+
"bin/fractor-doc-generator"
45+
],
46+
"config": {
47+
"allow-plugins": {
48+
"ergebnis/composer-normalize": true
49+
},
50+
"sort-packages": true
51+
},
52+
"scripts": {
53+
"analyze:php": "phpstan analyze",
54+
"style:php:check": "ecs",
55+
"style:php:fix": "ecs --fix",
56+
"test:php": "phpunit"
57+
}
58+
}
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
use a9f\FractorDocGenerator\Console\Factory\SymfonyStyleFactory;
4+
use a9f\FractorDocGenerator\Differ\DifferFactory;
5+
use a9f\FractorDocGenerator\FractorDocGeneratorApplication;
6+
use a9f\FractorDocGenerator\Printer\CodeSamplePrinter;
7+
use SebastianBergmann\Diff\Differ;
8+
use Symfony\Component\Console\Attribute\AsCommand;
9+
use Symfony\Component\Console\Style\SymfonyStyle;
10+
use Symfony\Component\DependencyInjection\ChildDefinition;
11+
use Symfony\Component\DependencyInjection\ContainerBuilder;
12+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
13+
use Symplify\RuleDocGenerator\Contract\RuleCodeSamplePrinterInterface;
14+
use Symplify\RuleDocGenerator\FileSystem\ClassByTypeFinder;
15+
use Symplify\RuleDocGenerator\RuleDefinitionsResolver;
16+
use Symplify\RuleDocGenerator\Text\KeywordHighlighter;
17+
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
18+
use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;
19+
20+
return static function (ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void {
21+
$services = $containerConfigurator->services();
22+
$services->defaults()
23+
->autowire()
24+
->public()
25+
->autoconfigure();
26+
27+
$services->load('a9f\\FractorDocGenerator\\', __DIR__ . '/../src/');
28+
$services->set(ClassByTypeFinder::class);
29+
$services->set(RuleDefinitionsResolver::class);
30+
$services->set(KeywordHighlighter::class);
31+
$services->set(Differ::class)->factory([service(DifferFactory::class), 'create']);
32+
33+
$services->set(FractorDocGeneratorApplication::class)
34+
->call('setCommandLoader', [service('console.command_loader')])
35+
->public();
36+
37+
$services->set(SymfonyStyle::class)->factory([service(SymfonyStyleFactory::class), 'create']);
38+
39+
$containerBuilder->registerAttributeForAutoconfiguration(
40+
AsCommand::class,
41+
static function (ChildDefinition $definition, AsCommand $attribute): void {
42+
$commands = explode('|', $attribute->name);
43+
$hidden = false;
44+
$name = array_shift($commands);
45+
46+
if ($name === '') {
47+
// Symfony AsCommand attribute encodes hidden flag as an empty command name
48+
$hidden = true;
49+
$name = array_shift($commands);
50+
}
51+
52+
if ($name === null) {
53+
// This happens in case no name and no aliases are given
54+
return;
55+
}
56+
57+
$definition->addTag(
58+
'console.command',
59+
[
60+
'command' => $name,
61+
'description' => $attribute->description,
62+
'hidden' => $hidden,
63+
]
64+
);
65+
66+
foreach ($commands as $name) {
67+
$definition->addTag(
68+
'console.command',
69+
[
70+
'command' => $name,
71+
'hidden' => $hidden,
72+
'alias' => true,
73+
]
74+
);
75+
}
76+
}
77+
);
78+
79+
$services->set(CodeSamplePrinter::class)->arg('$ruleCodeSamplePrinters', tagged_iterator('fractor_doc_generator.rule_code_sample_printer'));
80+
81+
$containerBuilder->registerForAutoconfiguration(RuleCodeSamplePrinterInterface::class)->addTag('fractor_doc_generator.rule_code_sample_printer');
82+
};

fractor-doc-generator/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-doc-generator/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-doc-generator/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-doc-generator">
5+
<directory>tests</directory>
6+
</testsuite>
7+
</testsuites>
8+
<source>
9+
<include>
10+
<directory>./src</directory>
11+
</include>
12+
</source>
13+
</phpunit>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorDocGenerator\Console\Command;
6+
7+
use a9f\FractorDocGenerator\Printer\DirectoryToMarkdownPrinter;
8+
use Nette\Utils\FileSystem;
9+
use Symfony\Component\Console\Attribute\AsCommand;
10+
use Symfony\Component\Console\Command\Command;
11+
use Symfony\Component\Console\Input\InputArgument;
12+
use Symfony\Component\Console\Input\InputInterface;
13+
use Symfony\Component\Console\Input\InputOption;
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
use Symfony\Component\Console\Style\SymfonyStyle;
16+
use Symplify\RuleDocGenerator\ValueObject\Option;
17+
18+
#[AsCommand(name: 'generate', description: 'Generated Markdown documentation based on documented rules found in directory')]
19+
final class GenerateCommand extends Command
20+
{
21+
public function __construct(
22+
private readonly DirectoryToMarkdownPrinter $directoryToMarkdownPrinter,
23+
private readonly SymfonyStyle $symfonyStyle,
24+
) {
25+
parent::__construct();
26+
}
27+
protected function configure(): void
28+
{
29+
$this->addArgument(
30+
Option::PATHS,
31+
InputArgument::REQUIRED | InputArgument::IS_ARRAY,
32+
'Path to directory of your project'
33+
);
34+
35+
$this->addOption(
36+
Option::OUTPUT_FILE,
37+
null,
38+
InputOption::VALUE_REQUIRED,
39+
'Path to output generated markdown file',
40+
getcwd() . '/docs/rules_overview.md'
41+
);
42+
}
43+
44+
protected function execute(InputInterface $input, OutputInterface $output): int
45+
{
46+
$paths = (array) $input->getArgument(Option::PATHS);
47+
48+
$outputFilePath = (string) $input->getOption(Option::OUTPUT_FILE);
49+
50+
$markdownFileDirectory = dirname($outputFilePath);
51+
52+
// ensure directory exists
53+
if (!file_exists($markdownFileDirectory)) {
54+
FileSystem::createDir($markdownFileDirectory);
55+
}
56+
57+
$markdownFileContent = $this->directoryToMarkdownPrinter->print($markdownFileDirectory, $paths);
58+
59+
FileSystem::write($outputFilePath, $markdownFileContent);
60+
61+
$this->symfonyStyle->success(sprintf('File "%s" was created', $outputFilePath));
62+
63+
return self::SUCCESS;
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorDocGenerator\Console\Factory;
6+
7+
use Symfony\Component\Console\Input\ArgvInput;
8+
use Symfony\Component\Console\Output\ConsoleOutput;
9+
use Symfony\Component\Console\Output\OutputInterface;
10+
use Symfony\Component\Console\Style\SymfonyStyle;
11+
12+
final class SymfonyStyleFactory
13+
{
14+
public static function create(): SymfonyStyle
15+
{
16+
// to prevent missing argv indexes
17+
if (!isset($_SERVER['argv'])) {
18+
$_SERVER['argv'] = [];
19+
}
20+
$argvInput = new ArgvInput();
21+
$consoleOutput = new ConsoleOutput();
22+
// --debug is called
23+
if ($argvInput->hasParameterOption('--debug')) {
24+
$consoleOutput->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
25+
}
26+
// disable output for tests
27+
if (self::isPHPUnitRun()) {
28+
$consoleOutput->setVerbosity(OutputInterface::VERBOSITY_QUIET);
29+
}
30+
31+
return new SymfonyStyle($argvInput, $consoleOutput);
32+
}
33+
34+
private static function isPHPUnitRun(): bool
35+
{
36+
return \defined('PHPUNIT_COMPOSER_INSTALL') || \defined('__PHPUNIT_PHAR__');
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace a9f\FractorDocGenerator\DependencyInjection;
4+
5+
use Symfony\Component\Config\FileLocator;
6+
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
7+
use Symfony\Component\DependencyInjection\ContainerBuilder;
8+
use Symfony\Component\DependencyInjection\ContainerInterface;
9+
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
10+
11+
class ContainerContainerBuilder
12+
{
13+
public function createDependencyInjectionContainer(): ContainerInterface
14+
{
15+
$containerBuilder = new ContainerBuilder();
16+
17+
$containerBuilder->addCompilerPass(new AddConsoleCommandPass());
18+
19+
$configFiles = [
20+
__DIR__ . '/../../config/config.php'
21+
];
22+
23+
foreach ($configFiles as $configFile) {
24+
if (!file_exists($configFile)) {
25+
continue;
26+
}
27+
28+
$fileLoader = new PhpFileLoader($containerBuilder, new FileLocator(dirname($configFile)));
29+
$fileLoader->load($configFile);
30+
}
31+
32+
$containerBuilder->compile();
33+
34+
return $containerBuilder;
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorDocGenerator\Differ;
6+
7+
use ReflectionProperty;
8+
use SebastianBergmann\Diff\Differ;
9+
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
10+
11+
final class DifferFactory
12+
{
13+
public static function create(): Differ
14+
{
15+
$unifiedDiffOutputBuilder = new UnifiedDiffOutputBuilder('');
16+
17+
// this is required to show full diffs from start to end
18+
$contextLinesReflectionProperty = new ReflectionProperty($unifiedDiffOutputBuilder, 'contextLines');
19+
$contextLinesReflectionProperty->setValue($unifiedDiffOutputBuilder, 10000);
20+
21+
return new Differ($unifiedDiffOutputBuilder);
22+
}
23+
}

0 commit comments

Comments
 (0)