diff --git a/.gitignore b/.gitignore index 89329b1395c142d881cfb5c9359fed7012870491..2e46ea9f1fb4562fa5d2c455a134b5a9cfbbb19c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /vendor /composer.lock /.php_cs.*cache +/.phpunit.result.cache diff --git a/composer.json b/composer.json index b9f1cadc18b9008618202ab3e6d7bf3ea3b3b5c1..0bd4ea66ac2950f66d0e80b87c350336fe23c294 100644 --- a/composer.json +++ b/composer.json @@ -1,26 +1,41 @@ { - "name": "irstea/php-cs-fixer-config", - "description": "Jeux de règles pour php-cs-fixer.", - "type": "library", - "license": "MIT", - "authors": [ - { - "name": "Irstea - DSI - pôle IS", - "email": "dsi.poleis@irstea.fr" - } - ], - "require": { - "php": "^5.6 || ^7.0", - "ext-json": "*", - "friendsofphp/php-cs-fixer": "^2.13" - }, - "autoload": { - "psr-4": { "Irstea\\CS\\": "src/" } - }, - "config": { - "sort-packages": true - }, - "archive": { - "exclude": [".?*"] + "name": "irstea/php-cs-fixer-config", + "description": "Jeux de règles pour php-cs-fixer.", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Irstea - DSI - pôle IS", + "email": "dsi.poleis@irstea.fr" } + ], + "require": { + "php": "^5.6 || ^7.0", + "ext-json": "*", + "beberlei/assert": "^3.2", + "friendsofphp/php-cs-fixer": "^2.13", + "symfony/cache": "^5.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^8.5" + }, + "autoload": { + "psr-4": { + "Irstea\\CS\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Irstea\\CS\\Tests\\": "tests/" + } + }, + "config": { + "sort-packages": true + }, + "archive": { + "exclude": [ + ".?*" + ] + } } diff --git a/headers/GPL-3.0.txt b/headers/GPL-3.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..6f0c39ea0162ec1be03fe66697d521a7ec69a1fd --- /dev/null +++ b/headers/GPL-3.0.txt @@ -0,0 +1,15 @@ +%package% - %description% +Copyright (C) %yearRange% IRSTEA + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/headers/LGPL-3.0.txt b/headers/LGPL-3.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..f8ecdd7c3d5364a3c6580441aef635fd5e370fd9 --- /dev/null +++ b/headers/LGPL-3.0.txt @@ -0,0 +1,15 @@ +%package% - %description% +Copyright (C) %yearRange% IRSTEA + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/.docheader b/headers/default.txt similarity index 60% rename from .docheader rename to headers/default.txt index 72e66b79b4b2259686c42107ed6aa954df6c239e..9e1f2c5d4deb7197a0a3e3361bd1a2cd9bd5cf03 100644 --- a/.docheader +++ b/headers/default.txt @@ -1,5 +1,5 @@ -This file is part of "%package%". -(c) %yearRange% Irstea <dsi.poleis@irstea.fr> +%package% - %description% +Copyright (C) %yearRange% IRSTEA For the full copyright and license information, please view the LICENSE file that was distributed with this source code. diff --git a/headers/proprietary.txt b/headers/proprietary.txt new file mode 100644 index 0000000000000000000000000000000000000000..7c4bab25feeae5aa10f87243584637f7a9ba4d9e --- /dev/null +++ b/headers/proprietary.txt @@ -0,0 +1,4 @@ +%package% - %description% +Copyright (C) %yearRange% IRSTEA + +All rights reserved. diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..882f36c66ad4d3d434a10216577d6ebe4705c282 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd" + bootstrap="vendor/autoload.php" + executionOrder="depends,defects" + forceCoversAnnotation="true" + beStrictAboutCoversAnnotation="true" + beStrictAboutOutputDuringTests="true" + beStrictAboutTodoAnnotatedTests="true" + verbose="true"> + + <testsuites> + <testsuite name="default"> + <directory suffix="Test.php">tests</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist processUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">src</directory> + </whitelist> + </filter> +</phpunit> diff --git a/src/Composer/ComposerPackage.php b/src/Composer/ComposerPackage.php new file mode 100644 index 0000000000000000000000000000000000000000..ef10f1a7a945f1abe1be4d13b3ce8baee544a74c --- /dev/null +++ b/src/Composer/ComposerPackage.php @@ -0,0 +1,119 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\Composer; + +use Assert\Assertion; +use Irstea\CS\FileLocator\FileLocatorInterface; + +/** + * Class ComposerPackage. + */ +final class ComposerPackage implements ComposerPackageInterface +{ + /** + * @var array|null + */ + private $composerJson; + + /** + * @var FileLocatorInterface + */ + private $fileLocator; + + /** + * ComposerPackage constructor. + */ + public function __construct(FileLocatorInterface $fileLocator) + { + $this->fileLocator = $fileLocator; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->getKey('name'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() + { + return $this->getKey('description'); + } + + /** + * {@inheritdoc} + */ + public function getRequiredPHPVersion() + { + $require = $this->getKey('require', []); + if ( + isset($require['php']) + && preg_match('/(?:>=?|\^|~)\s*([578]\.\d)/', $require['php'], $groups) + ) { + return (float) $groups[1]; + } + + return 5.6; + } + + /** + * {@inheritdoc} + */ + public function getLicenses() + { + return (array) $this->getKey('license', 'proprietary'); + } + + /** + * @param string $key + * @param mixed $default + * + * @throws \Assert\AssertionFailedException + * + * @return mixed|null + */ + private function getKey($key, $default = null) + { + Assertion::string($key); + + $data = $this->getComposerJson(); + + return \array_key_exists($key, $data) ? $data[$key] : $default; + } + + /** + * @return array + */ + private function getComposerJson() + { + return $this->composerJson !== null ? $this->composerJson : $this->readComposerJson(); + } + + /** + * @throws \Assert\AssertionFailedException + * + * @return array + */ + private function readComposerJson() + { + $composerPath = $this->fileLocator->locate('composer.json'); + Assertion::notNull($composerPath, 'could not find composer.json'); + + $content = file_get_contents($composerPath); + Assertion::string($content, "could not read `$composerPath`"); + + return $this->composerJson = json_decode($content, true, 512, \JSON_THROW_ON_ERROR); + } +} diff --git a/src/Composer/ComposerPackageInterface.php b/src/Composer/ComposerPackageInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a6abdd90b0206a06ddec1c4cd4789eb513d070f4 --- /dev/null +++ b/src/Composer/ComposerPackageInterface.php @@ -0,0 +1,37 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\Composer; + +/** + * Interface ComposerPackageInterface. + */ +interface ComposerPackageInterface +{ + /** + * @return string + */ + public function getName(); + + /** + * @return string + */ + public function getDescription(); + + /** + * @return float + */ + public function getRequiredPHPVersion(); + + /** + * @return iterable + */ + public function getLicenses(); +} diff --git a/src/Config.php b/src/Config.php index f911ec8f02852af2ac7b5b1b9320664413937c88..1b69e7a5d59466f6aeb410a90323ac10d48d5ecb 100644 --- a/src/Config.php +++ b/src/Config.php @@ -1,15 +1,28 @@ <?php /* - * This file is part of "irstea/php-cs-fixer-config". - * (c) 2018-2019 Irstea <dsi.poleis@irstea.fr> + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * */ namespace Irstea\CS; +use Irstea\CS\Composer\ComposerPackage; +use Irstea\CS\Composer\ComposerPackageInterface; +use Irstea\CS\FileLocator\FileLocator; +use Irstea\CS\Git\CachedGitRepository; +use Irstea\CS\Git\GitRepository; +use Irstea\CS\HeaderComment\ChainTemplateProvider; +use Irstea\CS\HeaderComment\FormattedHeaderProvider; +use Irstea\CS\HeaderComment\HeaderProviderInterface; +use Irstea\CS\HeaderComment\LicenseTemplateProvider; +use Irstea\CS\HeaderComment\TemplateFormatter; +use Irstea\CS\HeaderComment\UserDefinedTemplateProvider; use PhpCsFixer\Config as PhpCsFixerConfig; +use Symfony\Component\Cache\Adapter\ArrayAdapter; /** * Class Config. @@ -17,41 +30,34 @@ use PhpCsFixer\Config as PhpCsFixerConfig; final class Config extends PhpCsFixerConfig { /** - * @var string|null - */ - private $commit; - - /** - * @var mixed[] - */ - private $cache; - - /** - * @var array|null + * @var ComposerPackageInterface */ - private $composerConfig; + private $composerPackage; /** - * @var string + * @var HeaderProviderInterface */ - private $cacheFile = '.php_cs.commit-cache'; + private $headerProvider; /** - * @var string + * @var array<string, mixed> */ - private $docHeaderfile = '.docheader'; - - /** @var array<string, mixed> */ private $ruleOverrides = []; /** - * Set cacheFile. + * Config constructor. * - * @param string $cacheFile + * @param string $name */ - public function setCacheFile($cacheFile) - { - $this->cacheFile = $cacheFile; + public function __construct( + ComposerPackageInterface $composerPackage, + HeaderProviderInterface $headerProvider, + $name = 'default' + ) { + parent::__construct($name); + + $this->composerPackage = $composerPackage; + $this->headerProvider = $headerProvider; } /** @@ -69,7 +75,7 @@ final class Config extends PhpCsFixerConfig */ public function getRules() { - $phpVersion = $this->findRequiredPHPVersion(); + $phpVersion = $this->composerPackage->getRequiredPHPVersion(); $risky = $this->getRiskyAllowed(); return array_replace( @@ -97,6 +103,7 @@ final class Config extends PhpCsFixerConfig '@PHP70Migration:risky' => $risky && $phpVersion >= 7.0, '@PHP71Migration' => $phpVersion >= 7.1, '@PHP71Migration:risky' => $risky && $phpVersion >= 7.1, + '@PHP73Migration' => $phpVersion >= 7.3, ]; } @@ -109,33 +116,37 @@ final class Config extends PhpCsFixerConfig public function baseRules($phpVersion, $risky) { $rules = [ - // Configuration && overrides - 'binary_operator_spaces' => ['align_double_arrow' => true], - 'blank_line_after_opening_tag' => false, - 'concat_space' => ['spacing' => 'one'], - 'method_argument_space' => ['ensure_fully_multiline' => true], - - // Safe - 'align_multiline_comment' => true, - 'array_syntax' => ['syntax' => 'short'], - 'general_phpdoc_annotation_remove' => ['annotations' => ['author', 'package']], - 'no_multiline_whitespace_before_semicolons' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'ordered_imports' => true, - 'phpdoc_add_missing_param_annotation' => true, - 'phpdoc_annotation_without_dot' => true, - 'phpdoc_order' => true, - 'semicolon_after_instruction' => true, - 'yoda_style' => false, + // Configuration && overrides + 'binary_operator_spaces' => ['align_double_arrow' => true], + 'blank_line_after_opening_tag' => false, + 'concat_space' => ['spacing' => 'one'], + 'method_argument_space' => ['ensure_fully_multiline' => true], + 'declare_strict_types' => $phpVersion >= 7.0, + + // Safe + 'align_multiline_comment' => true, + 'array_syntax' => ['syntax' => 'short'], + 'general_phpdoc_annotation_remove' => ['annotations' => ['author', 'package']], + 'no_multiline_whitespace_before_semicolons' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_imports' => true, + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_order' => true, + 'semicolon_after_instruction' => true, + 'yoda_style' => false, + ]; - 'header_comment' => [ - 'commentType' => 'comment', - 'location' => 'after_declare_strict', - 'separate' => 'bottom', - 'header' => $this->headerComment(), - ], + $header = $this->headerProvider->getHeader(); + if ($header) { + $rules['header_comment'] = [ + 'commentType' => 'comment', + 'location' => 'after_declare_strict', + 'separate' => 'bottom', + 'header' => $header, ]; + } if ($risky) { $rules['is_null'] = ['use_yoda_style' => false]; @@ -166,146 +177,29 @@ final class Config extends PhpCsFixerConfig } /** - * @return string - */ - private function headerComment() - { - $header = "Create and customize a file named {$this->docHeaderfile} at the root of the project to change this message."; - - if (file_exists($this->docHeaderfile)) { - $header = trim(file_get_contents($this->docHeaderfile)); - } - - return str_replace(['%yearRange%', '%package%'], [$this->getYearRange(), $this->findPackageName()], $header); - } - - /** - * @return string|null + * {@inheritdoc} */ - private function findPackageName() + public static function create() { - return $this->memoize( - 'package-name', - function () { - $config = $this->getComposerConfig(); + $stackTrace = debug_backtrace(1); + $callerPath = \dirname($stackTrace[0]['file']); - return isset($config['name']) ? $config['name'] : null; - } - ); - } + $fileLocator = new FileLocator($callerPath); + $composerPackage = new ComposerPackage($fileLocator); - /** - * @return string - */ - private function getYearRange() - { - return $this->memoize( - 'years', - function () { - $last = date('Y'); - $first = exec('git log --format=%cd --date=format:%Y --date-order | tail -n1'); - if (!$first) { - $first = '???'; - } + $cache = new ArrayAdapter(); - return ($last !== null && $last !== $first) ? "$first-$last" : $first; - } - ); - } + $backendGitRepository = new GitRepository($callerPath); + $gitRepository = new CachedGitRepository($backendGitRepository, $cache); - /** - * @return float - */ - private function findRequiredPHPVersion() - { - return $this->memoize( - 'php-req', - function () { - $config = $this->getComposerConfig(); - if (isset($config['require']['php'])) { - if (preg_match('/(?:>=?|\^|~)\s*([57]\.\d)/', $config['require']['php'], $groups)) { - return (float) $groups[1]; - } - } - - return 5.6; - } + $headerProvider = new FormattedHeaderProvider( + new ChainTemplateProvider([ + new UserDefinedTemplateProvider($fileLocator), + new LicenseTemplateProvider($composerPackage), + ]), + new TemplateFormatter($gitRepository, $composerPackage) ); - } - /** - * @return array - */ - private function getComposerConfig() - { - if ($this->composerConfig !== null) { - return $this->composerConfig; - } - $this->composerConfig = []; - - if (file_exists('composer.json')) { - $data = json_decode(file_get_contents('composer.json'), true); - if (\is_array($data)) { - $this->composerConfig = $data; - } - } - - return $this->composerConfig; - } - - /** - * @param string $key - * @param callable $generator - * - * @return mixed - */ - private function memoize($key, $generator) - { - $commit = $this->getHeadCommit(); - $cache = $this->loadCache(); - if (!isset($cache[$commit][$key])) { - if (!isset($cache[$commit])) { // TODO: load a template from a local file. - $cache[$commit] = []; - } - $cache[$commit][$key] = $generator(); - $this->saveCache($cache); - } - - return $cache[$commit][$key]; - } - - /** - * @return string - */ - private function getHeadCommit() - { - if ($this->commit === null) { - $this->commit = trim(shell_exec('git rev-parse HEAD')); - } - - return $this->commit; - } - - /** - * @return mixed[] - */ - private function loadCache() - { - if ($this->cache === null) { - $this->cache = []; - if ($this->getUsingCache() && file_exists($this->cacheFile)) { - $this->cache = json_decode(file_get_contents($this->cacheFile), true); - } - } - - return $this->cache; - } - - /** - * @param array $cache - */ - private function saveCache(array $cache) - { - file_put_contents($this->cacheFile, json_encode($cache)); + return new self($composerPackage, $headerProvider); } } diff --git a/src/FileLocator/ChainFileLocator.php b/src/FileLocator/ChainFileLocator.php new file mode 100644 index 0000000000000000000000000000000000000000..74357b86b015eab003657c8f3f8808eeed82fb46 --- /dev/null +++ b/src/FileLocator/ChainFileLocator.php @@ -0,0 +1,52 @@ +<?php +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\FileLocator; + +use Assert\Assertion; + +/** + * Class ChainFileLocator. + */ +final class ChainFileLocator implements FileLocatorInterface +{ + /** + * @var FileLocatorInterface[] + */ + private $fileLocators; + + /** + * ChainFileLocator constructor. + * + * @param FileLocatorInterface[] $fileLocators + */ + public function __construct($fileLocators) + { + Assertion::isArray($fileLocators); + Assertion::allImplementsInterface($fileLocators, FileLocatorInterface::class); + + $this->fileLocators = $fileLocators; + } + + /** + * {@inheritdoc} + */ + public function locate($filename) + { + foreach ($this->fileLocators as $fileLocator) { + $result = $fileLocator->locate($filename); + if ($result !== null) { + return $result; + } + } + + return null; + } +} diff --git a/src/FileLocator/FileLocator.php b/src/FileLocator/FileLocator.php new file mode 100644 index 0000000000000000000000000000000000000000..88e32827cf207d14064dbc47caf7a0317b2ca3d5 --- /dev/null +++ b/src/FileLocator/FileLocator.php @@ -0,0 +1,51 @@ +<?php +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\FileLocator; + +use Assert\Assertion; + +/** + * Class FileLocator. + */ +final class FileLocator implements FileLocatorInterface +{ + /** + * @var string + */ + private $baseDir; + + /** + * FileLocator constructor. + * + * @param string $baseDir + */ + public function __construct($baseDir) + { + Assertion::string($baseDir); + + $this->baseDir = rtrim($baseDir, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR; + } + + /** + * {@inheritdoc} + */ + public function locate($filename) + { + Assertion::string($filename); + + $path = $this->baseDir . ltrim($filename, \DIRECTORY_SEPARATOR); + if (!file_exists($path)) { + return null; + } + + return $path; + } +} diff --git a/src/FileLocator/FileLocatorInterface.php b/src/FileLocator/FileLocatorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5a1fb82e38c8a9a0f33e7136309b4bb449c761a8 --- /dev/null +++ b/src/FileLocator/FileLocatorInterface.php @@ -0,0 +1,24 @@ +<?php +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\FileLocator; + +/** + * Interface FileLocatorInterface. + */ +interface FileLocatorInterface +{ + /** + * @param string $filename + * + * @return string|null + */ + public function locate($filename); +} diff --git a/src/Git/CachedGitRepository.php b/src/Git/CachedGitRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..a6431177ff366f7965e03dbe67d2421bfbf485d5 --- /dev/null +++ b/src/Git/CachedGitRepository.php @@ -0,0 +1,55 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\Git; + +use Symfony\Contracts\Cache\CacheInterface; + +/** + * Class CachedGitRepository. + */ +final class CachedGitRepository implements GitRepositoryInterface +{ + /** + * @var GitRepositoryInterface + */ + private $inner; + + /** + * @var CacheInterface + */ + private $cache; + + /** + * CachedGitRepository constructor. + */ + public function __construct(GitRepositoryInterface $inner, CacheInterface $cache) + { + $this->inner = $inner; + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function getHeadCommit() + { + // Jamais mis en cache ! + return $this->inner->getHeadCommit(); + } + + /** + * {@inheritdoc} + */ + public function getYearRange() + { + return $this->cache->get('git.year-range', [$this->inner, 'getYearRange']); + } +} diff --git a/src/Git/GitRepository.php b/src/Git/GitRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..db6357eb1dd6634bccd64bd08466740f8bc21c3d --- /dev/null +++ b/src/Git/GitRepository.php @@ -0,0 +1,59 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\Git; + +use Assert\Assertion; + +/** + * Class GitRepository. + */ +final class GitRepository implements GitRepositoryInterface +{ + /** + * @var string + */ + private $repositoryPath; + + /** + * GitRepository constructor. + * + * @param string $repositoryPath + */ + public function __construct($repositoryPath) + { + Assertion::string($repositoryPath); + Assertion::directory($repositoryPath . '/.git'); + + $this->repositoryPath = $repositoryPath; + } + + /** + * {@inheritdoc} + */ + public function getHeadCommit() + { + return trim(shell_exec('git -C ' . escapeshellarg($this->repositoryPath) . ' rev-parse HEAD')); + } + + /** + * {@inheritdoc} + */ + public function getYearRange() + { + $last = date('Y'); + $first = exec('git -C ' . escapeshellarg($this->repositoryPath) . ' log --format=%cd --date=format:%Y --date-order | tail -n1'); + if (!$first) { + $first = '???'; + } + + return ($last !== null && $last !== $first) ? "$first-$last" : $first; + } +} diff --git a/src/Git/GitRepositoryInterface.php b/src/Git/GitRepositoryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..4ee3cf13514d66839d2b493aafe4bf411941abec --- /dev/null +++ b/src/Git/GitRepositoryInterface.php @@ -0,0 +1,27 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\Git; + +/** + * Class GitRepositoryInterface. + */ +interface GitRepositoryInterface +{ + /** + * @return string + */ + public function getHeadCommit(); + + /** + * @return string + */ + public function getYearRange(); +} diff --git a/src/HeaderComment/ChainTemplateProvider.php b/src/HeaderComment/ChainTemplateProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..52939e95f5ea177d5ee1710d85e7a583296ddda2 --- /dev/null +++ b/src/HeaderComment/ChainTemplateProvider.php @@ -0,0 +1,52 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\HeaderComment; + +use Assert\Assertion; + +/** + * Class ChainTemplateProvider. + */ +final class ChainTemplateProvider implements TemplateProviderInterface +{ + /** + * @var TemplateProviderInterface[] + */ + private $templateProviders; + + /** + * ChainTemplateProvider constructor. + * + * @param TemplateProviderInterface[] $templateProviders + */ + public function __construct($templateProviders) + { + Assertion::isArray($templateProviders); + Assertion::allImplementsInterface($templateProviders, TemplateProviderInterface::class); + + $this->templateProviders = $templateProviders; + } + + /** + * {@inheritdoc} + */ + public function getTemplate() + { + foreach ($this->templateProviders as $templateProvider) { + $template = $templateProvider->getTemplate(); + if ($template) { + return $template; + } + } + + return null; + } +} diff --git a/src/HeaderComment/FormattedHeaderProvider.php b/src/HeaderComment/FormattedHeaderProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..017e642b3eac6b666a905cb89702ba3e0d884759 --- /dev/null +++ b/src/HeaderComment/FormattedHeaderProvider.php @@ -0,0 +1,47 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\HeaderComment; + +/** + * Class FormattedHeaderProvider. + */ +final class FormattedHeaderProvider implements HeaderProviderInterface +{ + /** + * @var TemplateFormatterInterface + */ + private $templateFormatter; + + /** + * @var TemplateProviderInterface + */ + private $templateProvider; + + /** + * UserDefinedTemplateProvider constructor. + */ + public function __construct(TemplateProviderInterface $templateProvider, TemplateFormatterInterface $templateFormatter) + { + $this->templateProvider = $templateProvider; + $this->templateFormatter = $templateFormatter; + } + + /** + * {@inheritdoc} + */ + public function getHeader() + { + $template = $this->templateProvider->getTemplate(); + var_dump($template); + + return $template ? $this->templateFormatter->format($template) : null; + } +} diff --git a/src/HeaderComment/HeaderProviderInterface.php b/src/HeaderComment/HeaderProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..51acfe7278767110eb2835e9ca55ad3ae97c6a4f --- /dev/null +++ b/src/HeaderComment/HeaderProviderInterface.php @@ -0,0 +1,22 @@ +<?php +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\HeaderComment; + +/** + * Class HeaderProviderInterface. + */ +interface HeaderProviderInterface +{ + /** + * @return string|null + */ + public function getHeader(); +} diff --git a/src/HeaderComment/LicenseTemplateProvider.php b/src/HeaderComment/LicenseTemplateProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..2419045c0c9633ad20984af7647ee3d5862bed14 --- /dev/null +++ b/src/HeaderComment/LicenseTemplateProvider.php @@ -0,0 +1,72 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\HeaderComment; + +use Irstea\CS\Composer\ComposerPackageInterface; +use Irstea\CS\FileLocator\FileLocator; +use Irstea\CS\FileLocator\FileLocatorInterface; + +/** + * Class LicenseTemplateProvider. + */ +final class LicenseTemplateProvider implements TemplateProviderInterface +{ + /** + * @var ComposerPackageInterface + */ + private $composerPackage; + + /** + * @var FileLocator + */ + private $fileLocator; + + /** + * LicenseTemplateProvider constructor. + */ + public function __construct( + ComposerPackageInterface $composerPackage, + FileLocatorInterface $fileLocator = null + ) { + $this->composerPackage = $composerPackage; + $this->fileLocator = $fileLocator ?: new FileLocator(\dirname(__DIR__, 2) . '/headers'); + } + + /** + * {@inheritdoc} + */ + public function getTemplate() + { + $path = $this->getLicensePath(); + + return $path ? file_get_contents($path) : null; + } + + /** + * @return string|null + */ + private function getLicensePath() + { + $licenses = $this->composerPackage->getLicenses(); + if (!$licenses) { + return $this->fileLocator->locate('proprietary.txt'); + } + + foreach ($licenses as $license) { + $templatePath = $this->fileLocator->locate("$license.txt"); + if ($templatePath) { + return $templatePath; + } + } + + return $this->fileLocator->locate('default.txt'); + } +} diff --git a/src/HeaderComment/TemplateFormatter.php b/src/HeaderComment/TemplateFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..b2be4e92d719cc472efb59342057273a545b6ff9 --- /dev/null +++ b/src/HeaderComment/TemplateFormatter.php @@ -0,0 +1,60 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\HeaderComment; + +use Assert\Assertion; +use Irstea\CS\Composer\ComposerPackageInterface; +use Irstea\CS\Git\GitRepositoryInterface; + +/** + * Class TemplateFormatter. + */ +final class TemplateFormatter implements TemplateFormatterInterface +{ + /** + * @var GitRepositoryInterface + */ + private $gitRepository; + + /** + * @var ComposerPackageInterface + */ + private $composerPackage; + + /** + * TemplateFormatter constructor. + */ + public function __construct(GitRepositoryInterface $gitRepository, ComposerPackageInterface $composerPackage) + { + $this->gitRepository = $gitRepository; + $this->composerPackage = $composerPackage; + } + + /** + * {@inheritdoc} + */ + public function format($template) + { + Assertion::string($template); + + $variables = [ + '%package%' => $this->composerPackage->getName(), + '%description%' => $this->composerPackage->getDescription(), + '%yearRange%' => $this->gitRepository->getYearRange(), + ]; + + return str_replace( + array_keys($variables), + array_values($variables), + $template + ); + } +} diff --git a/src/HeaderComment/TemplateFormatterInterface.php b/src/HeaderComment/TemplateFormatterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..330f2d237df6bfe6029189d37df2e59a6da2816f --- /dev/null +++ b/src/HeaderComment/TemplateFormatterInterface.php @@ -0,0 +1,24 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\HeaderComment; + +/** + * Class TemplateFormatterInterface. + */ +interface TemplateFormatterInterface +{ + /** + * @param string $template + * + * @return string + */ + public function format($template); +} diff --git a/src/HeaderComment/TemplateProviderInterface.php b/src/HeaderComment/TemplateProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1883fcb5e2f33c02b73bf61b2ca23456b3ce2c69 --- /dev/null +++ b/src/HeaderComment/TemplateProviderInterface.php @@ -0,0 +1,22 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\HeaderComment; + +/** + * Class TemplateProviderInterface. + */ +interface TemplateProviderInterface +{ + /** + * @return string|null + */ + public function getTemplate(); +} diff --git a/src/HeaderComment/UserDefinedTemplateProvider.php b/src/HeaderComment/UserDefinedTemplateProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..54c00cf47d52b12b48d9cf0ec813e04071b9c63f --- /dev/null +++ b/src/HeaderComment/UserDefinedTemplateProvider.php @@ -0,0 +1,40 @@ +<?php +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\HeaderComment; + +use Irstea\CS\FileLocator\FileLocatorInterface; + +/** + * Class UserDefinedTemplateProvider. + */ +final class UserDefinedTemplateProvider implements TemplateProviderInterface +{ + /** + * @var FileLocatorInterface + */ + private $fileLocator; + + /** + * UserDefinedTemplateProvider constructor. + */ + public function __construct(FileLocatorInterface $fileLocator) + { + $this->fileLocator = $fileLocator; + } + + /** + * {@inheritdoc} + */ + public function getTemplate() + { + return $this->fileLocator->locate('.docheader'); + } +} diff --git a/tests/FileLocator/FileLocatorTest.php b/tests/FileLocator/FileLocatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7dc2e3574976bebdac2502f6c2ec7fbe3d94a0a2 --- /dev/null +++ b/tests/FileLocator/FileLocatorTest.php @@ -0,0 +1,43 @@ +<?php declare(strict_types=1); +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\Tests\FileLocator; + +use Irstea\CS\FileLocator\FileLocator; +use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\TestCase; + +/** + * Class FileLocatorTest. + */ +class FileLocatorTest extends TestCase +{ + public function testShouldLocateExistingFile() + { + $fs = vfsStream::setup('root', 0755, [ + 'file' => 'content', + ]); + + $locator = new FileLocator($fs->url()); + + self::assertEquals( + $fs->getChild('file')->url(), + $locator->locate('file') + ); + } + + public function testShouldReturnNullOnMissingFile() + { + $fs = vfsStream::setup(); + $locator = new FileLocator($fs->url()); + + self::assertNull($locator->locate('file')); + } +} diff --git a/tests/HeaderComment/LicenseTemplateProviderTest.php b/tests/HeaderComment/LicenseTemplateProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a84096b92ab9c804bbf964e8047c19f158cc7af0 --- /dev/null +++ b/tests/HeaderComment/LicenseTemplateProviderTest.php @@ -0,0 +1,98 @@ +<?php +/* + * irstea/php-cs-fixer-config - Jeux de règles pour php-cs-fixer. + * Copyright (C) 2018-2019 IRSTEA + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Irstea\CS\Tests\HeaderComment; + +use Irstea\CS\Composer\ComposerPackageInterface; +use Irstea\CS\FileLocator\FileLocator; +use Irstea\CS\FileLocator\FileLocatorInterface; +use Irstea\CS\HeaderComment\LicenseTemplateProvider; +use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; +use PHPUnit\Framework\TestCase; +use Prophecy\Prophecy\ObjectProphecy; + +/** + * Class LicenseTemplateProviderTest. + */ +final class LicenseTemplateProviderTest extends TestCase +{ + /** + * @var ComposerPackageInterface|ObjectProphecy + */ + private $composerPackage; + + /** + * @var vfsStreamDirectory + */ + private $filesystem; + + /** + * @var FileLocatorInterface + */ + private $fileLocator; + + /** + * @var LicenseTemplateProvider + */ + private $templateProvider; + + protected function setUp(): void + { + parent::setUp(); + + /* @var ComposerPackageInterface|ObjectProphecy $composerPackage */ + $this->composerPackage = $this->prophesize(ComposerPackageInterface::class); + + $this->filesystem = vfsStream::setup( + 'root', + 0755, + [ + 'proprietary.txt' => 'proprietary-template', + 'GPL.txt' => 'GPL-template', + 'default.txt' => 'default-template', + ] + ); + + $this->fileLocator = new FileLocator($this->filesystem->url()); + + $this->templateProvider = new LicenseTemplateProvider( + $this->composerPackage->reveal(), + $this->fileLocator + ); + } + + public function testShouldProvideKnownLicenseHeader() + { + $this->composerPackage->getLicenses() + ->shouldBeCalled() + ->willReturn(['MIT', 'GPL']); + + self::assertEquals('GPL-template', $this->templateProvider->getTemplate()); + } + + public function testShouldProvideDefaultLicenseHeader() + { + $this->composerPackage->getLicenses() + ->shouldBeCalled() + ->willReturn(['MIT']); + + self::assertEquals('default-template', $this->templateProvider->getTemplate()); + } + + public function testShouldProvideProprietaryLicenseHeaderWithNoLicense() + { + $this->composerPackage->getLicenses() + ->shouldBeCalled() + ->willReturn([]); + + self::assertEquals('proprietary-template', $this->templateProvider->getTemplate()); + } +}