diff --git a/lib/DataSources/ProjectVersions.php b/lib/DataSources/ProjectVersions.php new file mode 100644 index 0000000..47cf220 --- /dev/null +++ b/lib/DataSources/ProjectVersions.php @@ -0,0 +1,237 @@ +} $projectData + * + * @return mixed[] + */ + public function buildProjectVersions(string $repositoryName, array $projectData): array + { + $projectVersions = $this->readProjectVersionsFromGit($repositoryName); + $projectVersions = $this->removeUnwantedVersions($projectVersions, $projectData); + $projectVersions = $this->applyConfiguredProjectVersions($projectVersions, $projectData); + + $projectVersions = $this->sortProjectVersions($projectVersions); + + $this->prepareProjectVersions( + $repositoryName, + $projectVersions, + $projectData, + ); + + return $projectVersions; + } + + /** + * @param list}> $projectVersions + * @param array{versions: list} $projectData + * + * @return list}> + */ + private function removeUnwantedVersions(array $projectVersions, array $projectData): array + { + $allowedVersions = array_map(static fn (array $versions) => $versions['name'], $projectData['versions']); + $projectVersions = array_filter($projectVersions, static fn (array $version): bool => in_array($version['name'], $allowedVersions, true)); + + return array_values($projectVersions); + } + + /** @return list}> */ + private function readProjectVersionsFromGit(string $repositoryName): array + { + $repositoryPath = $this->projectsDir . '/' . $repositoryName; + + $projectVersions = $this->projectVersionsReader->readProjectVersions($repositoryPath); + + // fix this, we shouldn't have null branch names at this point. Fix it further upstream + + /** @phpstan-ignore return.type */ + return array_filter($projectVersions, static function (array $projectVersion): bool { + return count($projectVersion['tags']) > 0; + }); + } + + /** + * @param list}> $projectVersions + * @param array{versions: list} $projectData + * + * @return list, + * maintained?: false + * }> + */ + private function applyConfiguredProjectVersions( + array $projectVersions, + array $projectData, + ): array { + foreach ($projectVersions as $key => $projectVersion) { + $configured = false; + + foreach ($projectData['versions'] as $k => $version) { + if ($this->containsSameProjectVersion($projectVersion, $version)) { + $version['tags'] = $projectVersion['tags']; + + $version['branchName'] = $projectVersion['branchName']; + + $projectVersions[$key] = $version; + + unset($projectData['versions'][$k]); + + $configured = true; + + break; + } + } + + if ($configured !== false) { + continue; + } + + $projectVersions[$key]['maintained'] = false; + } + + foreach ($projectData['versions'] as $projectVersion) { + $projectVersions[] = $projectVersion; + } + + return $projectVersions; + } + + /** + * @param array{name: string, slug: string, branchName: string|null, tags: list} $a + * @param array{name: string, branchName?: string|null} $b + */ + private function containsSameProjectVersion(array $a, array $b): bool + { + if ($a['name'] === $b['name']) { + return true; + } + + if (! isset($b['branchName'])) { + return false; + } + + return $a['branchName'] === $b['branchName']; + } + + /** + * @param list, maintained?: false}> $projectVersions + * @param mixed[] $projectData + * + * @return mixed[] + */ + private function prepareProjectVersions( + string $repositoryName, + array &$projectVersions, + array $projectData, + ): array { + $docsRepositoryName = $projectData['docsRepositoryName'] ?? $projectData['repositoryName']; + + $docsDir = $this->projectsDir . '/' . $docsRepositoryName . $projectData['docsPath']; + + foreach ($projectVersions as $key => $projectVersion) { + if (! isset($projectVersion['branchName'])) { + if (! isset($projectVersion['tags'])) { + throw new RuntimeException(sprintf( + <<<'EXCEPTION' + Project version "%s" of project "%s" has no branch name and does not have any tags, cannot checkout! + EXCEPTION, + $projectVersion['name'], + $repositoryName, + )); + } + + $this->projectGitSyncer->checkoutTag( + $docsRepositoryName, + end($projectVersion['tags'])->getName(), + ); + } else { + $this->projectGitSyncer->checkoutBranch( + $docsRepositoryName, + $projectVersion['branchName'], + ); + } + + $docsLanguages = array_map(static function (RSTLanguage $language): array { + return [ + 'code' => $language->getCode(), + 'path' => $language->getPath(), + ]; + }, $this->rstLanguagesDetector->detectLanguages($docsDir)); + + $projectVersions[$key]['hasDocs'] = count($docsLanguages) > 0; + $projectVersions[$key]['docsLanguages'] = $docsLanguages; + + if (! isset($projectVersion['tags'])) { + continue; + } + + $projectVersions[$key]['tags'] = array_map(static function (Tag $tag): array { + return [ + 'name' => $tag->getName(), + 'date' => $tag->getDate()->format('Y-m-d H:i:s'), + ]; + }, $projectVersion['tags']); + } + + // switch back to master + $this->projectGitSyncer->checkoutDefaultBranch($repositoryName); + + return $projectVersions; + } + + /** + * @param T $projectVersions + * + * @return T + * + * @template T of mixed[] + */ + private function sortProjectVersions(array $projectVersions): array + { + // sort by name so newest versions are first + usort($projectVersions, static function (array $a, array $b): int { + return strnatcmp($b['name'], $a['name']); + }); + + /** @phpstan-ignore return.type */ + return $projectVersions; + } +} diff --git a/lib/DataSources/Projects.php b/lib/DataSources/Projects.php index f56a09d..98c5f58 100644 --- a/lib/DataSources/Projects.php +++ b/lib/DataSources/Projects.php @@ -4,26 +4,14 @@ declare(strict_types=1); namespace Doctrine\Website\DataSources; -use Doctrine\Website\Docs\RST\RSTLanguage; -use Doctrine\Website\Docs\RST\RSTLanguagesDetector; -use Doctrine\Website\Git\Tag; use Doctrine\Website\Projects\GetProjectPackagistData; use Doctrine\Website\Projects\ProjectDataReader; use Doctrine\Website\Projects\ProjectDataRepository; use Doctrine\Website\Projects\ProjectGitSyncer; -use Doctrine\Website\Projects\ProjectVersionsReader; -use RuntimeException; use function array_filter; use function array_map; use function array_replace; -use function array_values; -use function count; -use function end; -use function in_array; -use function sprintf; -use function strnatcmp; -use function usort; final readonly class Projects implements DataSource { @@ -37,10 +25,8 @@ final readonly class Projects implements DataSource private ProjectDataRepository $projectDataRepository, private ProjectGitSyncer $projectGitSyncer, private ProjectDataReader $projectDataReader, - private ProjectVersionsReader $projectVersionsReader, - private RSTLanguagesDetector $rstLanguagesDetector, private GetProjectPackagistData $getProjectPackagistData, - private string $projectsDir, + private ProjectVersions $projectVersions, ) { } @@ -65,7 +51,7 @@ final readonly class Projects implements DataSource $this->projectDataReader->read($repositoryName), ); - $projectData['versions'] = $this->buildProjectVersions( + $projectData['versions'] = $this->projectVersions->buildProjectVersions( $repositoryName, $projectData, ); @@ -76,208 +62,4 @@ final readonly class Projects implements DataSource return $projectData; } - - /** - * @param array{versions: list} $projectData - * - * @return mixed[] - */ - private function buildProjectVersions(string $repositoryName, array $projectData): array - { - $projectVersions = $this->readProjectVersionsFromGit($repositoryName); - $projectVersions = $this->removeUnwantedVersions($projectVersions, $projectData); - $projectVersions = $this->applyConfiguredProjectVersions($projectVersions, $projectData); - - $projectVersions = $this->sortProjectVersions($projectVersions); - - $this->prepareProjectVersions( - $repositoryName, - $projectVersions, - $projectData, - ); - - return $projectVersions; - } - - /** - * @param list}> $projectVersions - * @param array{versions: list} $projectData - * - * @return list}> - */ - private function removeUnwantedVersions(array $projectVersions, array $projectData): array - { - $allowedVersions = array_map(static fn (array $versions) => $versions['name'], $projectData['versions']); - $projectVersions = array_filter($projectVersions, static fn (array $version): bool => in_array($version['name'], $allowedVersions, true)); - - return array_values($projectVersions); - } - - /** @return list}> */ - private function readProjectVersionsFromGit(string $repositoryName): array - { - $repositoryPath = $this->projectsDir . '/' . $repositoryName; - - $projectVersions = $this->projectVersionsReader->readProjectVersions($repositoryPath); - - // fix this, we shouldn't have null branch names at this point. Fix it further upstream - - /** @phpstan-ignore return.type */ - return array_filter($projectVersions, static function (array $projectVersion): bool { - return count($projectVersion['tags']) > 0; - }); - } - - /** - * @param list}> $projectVersions - * @param array{versions: list} $projectData - * - * @return list, - * maintained?: false - * }> - */ - private function applyConfiguredProjectVersions( - array $projectVersions, - array $projectData, - ): array { - foreach ($projectVersions as $key => $projectVersion) { - $configured = false; - - foreach ($projectData['versions'] as $k => $version) { - if ($this->containsSameProjectVersion($projectVersion, $version)) { - $version['tags'] = $projectVersion['tags']; - - $version['branchName'] = $projectVersion['branchName']; - - $projectVersions[$key] = $version; - - unset($projectData['versions'][$k]); - - $configured = true; - - break; - } - } - - if ($configured !== false) { - continue; - } - - $projectVersions[$key]['maintained'] = false; - } - - foreach ($projectData['versions'] as $projectVersion) { - $projectVersions[] = $projectVersion; - } - - return $projectVersions; - } - - /** - * @param array{name: string, slug: string, branchName: string|null, tags: list} $a - * @param array{name: string, branchName?: string|null} $b - */ - private function containsSameProjectVersion(array $a, array $b): bool - { - if ($a['name'] === $b['name']) { - return true; - } - - if (! isset($b['branchName'])) { - return false; - } - - return $a['branchName'] === $b['branchName']; - } - - /** - * @param list, maintained?: false}> $projectVersions - * @param mixed[] $projectData - * - * @return mixed[] - */ - private function prepareProjectVersions( - string $repositoryName, - array &$projectVersions, - array $projectData, - ): array { - $docsRepositoryName = $projectData['docsRepositoryName'] ?? $projectData['repositoryName']; - - $docsDir = $this->projectsDir . '/' . $docsRepositoryName . $projectData['docsPath']; - - foreach ($projectVersions as $key => $projectVersion) { - if (! isset($projectVersion['branchName'])) { - if (! isset($projectVersion['tags'])) { - throw new RuntimeException(sprintf( - <<<'EXCEPTION' - Project version "%s" of project "%s" has no branch name and does not have any tags, cannot checkout! - EXCEPTION, - $projectVersion['name'], - $repositoryName, - )); - } - - $this->projectGitSyncer->checkoutTag( - $docsRepositoryName, - end($projectVersion['tags'])->getName(), - ); - } else { - $this->projectGitSyncer->checkoutBranch( - $docsRepositoryName, - $projectVersion['branchName'], - ); - } - - $docsLanguages = array_map(static function (RSTLanguage $language): array { - return [ - 'code' => $language->getCode(), - 'path' => $language->getPath(), - ]; - }, $this->rstLanguagesDetector->detectLanguages($docsDir)); - - $projectVersions[$key]['hasDocs'] = count($docsLanguages) > 0; - $projectVersions[$key]['docsLanguages'] = $docsLanguages; - - if (! isset($projectVersion['tags'])) { - continue; - } - - $projectVersions[$key]['tags'] = array_map(static function (Tag $tag): array { - return [ - 'name' => $tag->getName(), - 'date' => $tag->getDate()->format('Y-m-d H:i:s'), - ]; - }, $projectVersion['tags']); - } - - // switch back to master - $this->projectGitSyncer->checkoutDefaultBranch($repositoryName); - - return $projectVersions; - } - - /** - * @param T $projectVersions - * - * @return T - * - * @template T of mixed[] - */ - private function sortProjectVersions(array $projectVersions): array - { - // sort by name so newest versions are first - usort($projectVersions, static function (array $a, array $b): int { - return strnatcmp($b['name'], $a['name']); - }); - - /** @phpstan-ignore return.type */ - return $projectVersions; - } } diff --git a/tests/DataSources/ProjectsTest.php b/tests/DataSources/ProjectsTest.php index e345d3d..283491b 100644 --- a/tests/DataSources/ProjectsTest.php +++ b/tests/DataSources/ProjectsTest.php @@ -6,6 +6,7 @@ namespace Doctrine\Website\Tests\DataSources; use DateTimeImmutable; use Doctrine\Website\DataSources\Projects; +use Doctrine\Website\DataSources\ProjectVersions; use Doctrine\Website\Docs\RST\RSTLanguage; use Doctrine\Website\Docs\RST\RSTLanguagesDetector; use Doctrine\Website\Git\Tag; @@ -259,14 +260,19 @@ class ProjectsTest extends TestCase $this->getProjectPackagistData = $this->createMock(GetProjectPackagistData::class); $this->projectsDir = '/path/to/projects'; + $projectVersions = new ProjectVersions( + $this->projectGitSyncer, + $this->projectVersionsReader, + $this->rstLanguagesDetector, + $this->projectsDir, + ); + $this->dataSource = new Projects( $this->projectDataRepository, $this->projectGitSyncer, $this->projectDataReader, - $this->projectVersionsReader, - $this->rstLanguagesDetector, $this->getProjectPackagistData, - $this->projectsDir, + $projectVersions, ); } }