-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #421 from PHPCSStandards/develop
Release 1.0.0
- Loading branch information
Showing
105 changed files
with
646 additions
and
42,121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,306 @@ | ||
<?php | ||
/** | ||
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers. | ||
* | ||
* @package PHPCSUtils | ||
* @copyright 2019-2020 PHPCSUtils Contributors | ||
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3 | ||
* @link https://github.com/PHPCSStandards/PHPCSUtils | ||
*/ | ||
|
||
namespace PHPCSUtils\GHPages; | ||
|
||
use RuntimeException; | ||
|
||
/** | ||
* Prepare markdown documents for use in a GH Pages website before deploy. | ||
* | ||
* {@internal This functionality has a minimum PHP requirement of PHP 7.2.} | ||
* | ||
* @internal | ||
* | ||
* @phpcs:disable PHPCompatibility.Classes.NewConstVisibility.Found | ||
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewParamTypeDeclarations.intFound | ||
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewParamTypeDeclarations.stringFound | ||
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.intFound | ||
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.stringFound | ||
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.voidFound | ||
* @phpcs:disable PHPCompatibility.InitialValue.NewConstantScalarExpressions.constFound | ||
*/ | ||
final class UpdateWebsite | ||
{ | ||
|
||
/** | ||
* Path to project root (without trailing slash). | ||
* | ||
* @var string | ||
*/ | ||
private const PROJECT_ROOT = __DIR__ . '/../..'; | ||
|
||
/** | ||
* Relative path to target directory off project root (without trailing slash). | ||
* | ||
* @var string | ||
*/ | ||
private const TARGET_DIR = 'docs'; | ||
|
||
/** | ||
* Frontmatter for the website homepage. | ||
* | ||
* @var string | ||
*/ | ||
private const README_FRONTMATTER = '--- | ||
title: PHPCSUtils | ||
description: "PHPCSUtils: A suite of utility functions for use with PHP_CodeSniffer" | ||
anchor: home | ||
permalink: / | ||
seo: | ||
type: WebSite | ||
publisher: | ||
type: Organisation | ||
--- | ||
'; | ||
|
||
/** | ||
* Frontmatter for the changelog page. | ||
* | ||
* @var string | ||
*/ | ||
private const CHANGELOG_FRONTMATTER = '--- | ||
title: Changelog | ||
description: "Changelog for the PHPCSUtils suite of utility functions for use with PHP_CodeSniffer" | ||
anchor: changelog | ||
permalink: /changelog | ||
seo: | ||
type: WebSite | ||
publisher: | ||
type: Organisation | ||
--- | ||
'; | ||
|
||
/** | ||
* Resolved path to project root (with trailing slash). | ||
* | ||
* @var string | ||
*/ | ||
private $realRoot; | ||
|
||
/** | ||
* Resolved path to target directory (with trailing slash). | ||
* | ||
* @var string | ||
*/ | ||
private $realTarget; | ||
|
||
/** | ||
* Run the transformation. | ||
* | ||
* @return int Exit code. | ||
*/ | ||
public function run(): int | ||
{ | ||
$exitcode = 0; | ||
|
||
try { | ||
$this->setPaths(); | ||
$this->transformReadme(); | ||
$this->transformChangelog(); | ||
} catch (RuntimeException $e) { | ||
echo 'ERROR: ', $e->getMessage(), \PHP_EOL; | ||
$exitcode = 1; | ||
} | ||
|
||
return $exitcode; | ||
} | ||
|
||
/** | ||
* Validate the paths to use. | ||
* | ||
* @return void | ||
*/ | ||
private function setPaths(): void | ||
{ | ||
$realRoot = \realpath(self::PROJECT_ROOT) . '/'; | ||
if ($realRoot === false) { | ||
throw new RuntimeException(\sprintf('Failed to find the %s directory.', $realRoot)); | ||
} | ||
|
||
$this->realRoot = $realRoot; | ||
|
||
// Check if the target directory exists and if not, create it. | ||
$targetDir = $this->realRoot . self::TARGET_DIR; | ||
|
||
if (@\is_dir($targetDir) === false) { | ||
if (@\mkdir($targetDir, 0777, true) === false) { | ||
throw new RuntimeException(\sprintf('Failed to create the %s directory.', $targetDir)); | ||
} | ||
} | ||
|
||
$realPath = \realpath($targetDir); | ||
if ($realPath === false) { | ||
throw new RuntimeException(\sprintf('Failed to find the %s directory.', $targetDir)); | ||
} | ||
|
||
$this->realTarget = $realPath . '/'; | ||
} | ||
|
||
/** | ||
* Apply various transformations to the index page. | ||
* | ||
* - Remove title, badges and index. | ||
* - Replace code samples with properly highlighted versions. | ||
* - Add frontmatter. | ||
* | ||
* @return void | ||
* | ||
* @throws \RuntimeException When any of the expected replacements could not be made. | ||
*/ | ||
private function transformReadme(): void | ||
{ | ||
$contents = $this->getContents($this->realRoot . 'README.md'); | ||
|
||
// Remove title, badges and index. | ||
$contents = $this->replace('`^.*## Features`s', '## Features', $contents, 1); | ||
|
||
// Remove the section about Non-Composer based integration. | ||
$contents = $this->replace( | ||
'`### Non-Composer based integration[\n\r]+(?:.+[\n\r]+)+?## Frequently Asked Questions`', | ||
'## Frequently Asked Questions', | ||
$contents, | ||
1 | ||
); | ||
|
||
// Replace installation instructions with properly highlighted version. | ||
$search = '~`{3}bash[\n\r]+composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer' | ||
. ' true[\n\r]+' | ||
. 'composer require phpcsstandards/phpcsutils:"([^\n\r]+)"[\n\r]+`{3}~'; | ||
$replace = '<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>' | ||
. 'composer config <span class="s">allow-plugins.dealerdirect/phpcodesniffer-composer-installer</span>' | ||
. ' <span class="mf">true</span>' | ||
. "\n" | ||
. 'composer require <span class="s">{{ site.phpcsutils.packagist }}</span>:"<span class="mf">$1</span>"' | ||
. "\n" | ||
. '</code></pre></div></div>'; | ||
$contents = $this->replace($search, $replace, $contents, 1); | ||
|
||
// Replace suggested end-user installation instructions with properly highlighted versions. | ||
$search = '~`{3}bash[\r\n]+> composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer' | ||
. ' true[\r\n]+> `{3}~'; | ||
$replace = '<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>' | ||
. 'composer config <span class="s">allow-plugins.dealerdirect/phpcodesniffer-composer-installer</span>' | ||
. ' <span class="mf">true</span>' | ||
. "\n" | ||
. '> </code></pre></div></div>'; | ||
$contents = $this->replace($search, $replace, $contents, 1); | ||
|
||
// Replace suggested end-user upgrade instructions with properly highlighted versions. | ||
$search = '~`{3}bash[\r\n]+> composer update your/cs-package --with-\[all-\]dependencies[\r\n]+> `{3}~'; | ||
$replace = '<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>' | ||
. 'composer update <span class="s">your/cs-package</span> <span class="mf">--with-[all-]dependencies</span>' | ||
. "\n" | ||
. '> </code></pre></div></div>'; | ||
$contents = $this->replace($search, $replace, $contents, 1); | ||
|
||
// Add frontmatter. | ||
$contents = self::README_FRONTMATTER . "\n" . $contents; | ||
|
||
$this->putContents($this->realTarget . 'index.md', $contents); | ||
} | ||
|
||
/** | ||
* Add frontmatter to the changelog page and remove "Unreleased". | ||
* | ||
* @return void | ||
*/ | ||
private function transformChangelog(): void | ||
{ | ||
$contents = $this->getContents($this->realRoot . 'CHANGELOG.md'); | ||
|
||
// Remove the section about Non-Composer based integration. | ||
$contents = $this->replace( | ||
'`## \[Unreleased\][\n\r]+(?:.+[\n\r]+)+?##`', | ||
'##', | ||
$contents, | ||
1 | ||
); | ||
|
||
// Add frontmatter. | ||
$contents = self::CHANGELOG_FRONTMATTER . "\n" . $contents; | ||
|
||
$this->putContents($this->realTarget . 'changelog.md', $contents); | ||
} | ||
|
||
/** | ||
* Execute a regex search and replace and verify the replacement was actually made. | ||
* | ||
* @param string $search The pattern to search for. | ||
* @param string $replace The replacement. | ||
* @param string $subject The string to execute the search & replace on. | ||
* @param int $limit Maximum number of replacements to make. | ||
* | ||
* @return string | ||
* | ||
* @throws \RuntimeException When the replacement was not made or not made the required number of times. | ||
*/ | ||
private function replace(string $search, string $replace, string $subject, int $limit = 1): string | ||
{ | ||
$subject = \preg_replace($search, $replace, $subject, $limit, $count); | ||
if ($count !== $limit) { | ||
throw new RuntimeException( | ||
'Failed to make required replacement.' . \PHP_EOL | ||
. "Search regex: $search" . \PHP_EOL | ||
. "Replacements made: $count" | ||
); | ||
} | ||
|
||
return $subject; | ||
} | ||
|
||
/** | ||
* Retrieve the contents of a file. | ||
* | ||
* @param string $source Path to the source file. | ||
* | ||
* @return string | ||
* | ||
* @throws \RuntimeException When the contents of the file could not be retrieved. | ||
*/ | ||
private function getContents(string $source): string | ||
{ | ||
$contents = \file_get_contents($source); | ||
if (!$contents) { | ||
throw new RuntimeException(\sprintf('Failed to read doc file: %s', $source)); | ||
} | ||
|
||
return $contents; | ||
} | ||
|
||
/** | ||
* Write a string to a file. | ||
* | ||
* @param string $target Path to the target file. | ||
* @param string $contents File contents to write. | ||
* | ||
* @return void | ||
* | ||
* @throws \RuntimeException When the target directory could not be created. | ||
* @throws \RuntimeException When the file could not be written to the target directory. | ||
*/ | ||
private function putContents(string $target, string $contents): void | ||
{ | ||
// Check if the target directory exists and if not, create it. | ||
$targetDir = \dirname($target); | ||
|
||
if (@\is_dir($targetDir) === false) { | ||
if (@\mkdir($targetDir, 0777, true) === false) { | ||
throw new RuntimeException(\sprintf('Failed to create the %s directory.', $targetDir)); | ||
} | ||
} | ||
|
||
// Make sure the file always ends on a new line. | ||
$contents = \rtrim($contents) . "\n"; | ||
if (\file_put_contents($target, $contents) === false) { | ||
throw new RuntimeException(\sprintf('Failed to write to target location: %s', $target)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#!/usr/bin/env php | ||
<?php | ||
/** | ||
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers. | ||
* | ||
* Update the phpDocumentor configuration file. | ||
* | ||
* {@internal This functionality has a minimum PHP requirement of PHP 7.2.} | ||
* | ||
* @internal | ||
* | ||
* @package PHPCSUtils | ||
* @copyright 2019-2020 PHPCSUtils Contributors | ||
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3 | ||
* @link https://github.com/PHPCSStandards/PHPCSUtils | ||
* | ||
* @phpcs:disable PHPCompatibility.FunctionUse.NewFunctionParameters.getenv_local_onlyFound | ||
* @phpcs:disable PHPCompatibility.FunctionUse.NewFunctionParameters.dirname_levelsFound | ||
*/ | ||
|
||
namespace PHPCSUtils\GHPages; | ||
|
||
$phpcsutilsPhpdocVersionUpdater = static function () { | ||
$tagname = \getenv('TAG', true); | ||
if ($tagname === false) { | ||
echo 'ERROR: No TAG environment variable found.', \PHP_EOL; | ||
exit(1); | ||
} | ||
|
||
$tagname = \trim($tagname); | ||
if ($tagname === '' || \preg_match('`^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:alpha|beta|rc)[0-9]*)?$`', $tagname) !== 1) { | ||
echo "ERROR: \"$tagname\" is not a valid tag.", \PHP_EOL; | ||
exit(1); | ||
} | ||
|
||
$projectRoot = \dirname(__DIR__, 2); | ||
$source = '.phpdoc.xml.dist'; | ||
$destination = 'phpdoc.xml'; | ||
$count = 0; | ||
|
||
if (\file_exists($projectRoot . '/' . $destination)) { | ||
echo "WARNING: Detected pre-existing \"$destination\" file.", \PHP_EOL; | ||
echo "Please make sure that this overload file is in sync with the \"$source\" file.", \PHP_EOL; | ||
echo 'This is your own responsibility!' . \PHP_EOL, \PHP_EOL; | ||
|
||
$config = \file_get_contents($projectRoot . '/' . $destination); | ||
if (!$config) { | ||
echo "ERROR: Failed to read phpDocumentor $destination configuration file.", \PHP_EOL; | ||
exit(1); | ||
} | ||
|
||
// Replace the previous version nr in the API doc title with the latest version number. | ||
$config = \preg_replace( | ||
'`<title>PHPCSUtils ([\#0-9\.]+)</title>`', | ||
"<title>PHPCSUtils {$tagname}</title>", | ||
$config, | ||
-1, | ||
$count | ||
); | ||
} else { | ||
$config = \file_get_contents($projectRoot . '/' . $source); | ||
if (!$config) { | ||
echo "ERROR: Failed to read phpDocumentor $source configuration template file.", \PHP_EOL; | ||
exit(1); | ||
} | ||
|
||
// Replace the "#.#.#" placeholder in the API doc title with the latest version number. | ||
$config = \str_replace( | ||
'<title>PHPCSUtils</title>', | ||
"<title>PHPCSUtils {$tagname}</title>", | ||
$config, | ||
$count | ||
); | ||
} | ||
|
||
if ($count !== 1) { | ||
echo "ERROR: Version number text replacement failed. Made $count replacements.", \PHP_EOL; | ||
exit(1); | ||
} | ||
|
||
if (\file_put_contents($projectRoot . '/' . $destination, $config) === false) { | ||
echo "ERROR: Failed to write phpDocumentor $destination configuration file.", \PHP_EOL; | ||
exit(1); | ||
} else { | ||
echo "SUCCESFULLY updated/created the $destination file!", \PHP_EOL; | ||
} | ||
|
||
exit(0); | ||
}; | ||
|
||
$phpcsutilsPhpdocVersionUpdater(); |
Oops, something went wrong.