Skip to content

Commit

Permalink
Merge pull request #421 from PHPCSStandards/develop
Browse files Browse the repository at this point in the history
Release 1.0.0
  • Loading branch information
jrfnl authored Jan 3, 2023
2 parents a98f01e + 8d3b6cb commit df0ef52
Show file tree
Hide file tree
Showing 105 changed files with 646 additions and 42,121 deletions.
306 changes: 306 additions & 0 deletions .github/GHPages/UpdateWebsite.php
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));
}
}
}
91 changes: 91 additions & 0 deletions .github/GHPages/update-docgen-config.php
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();
Loading

0 comments on commit df0ef52

Please sign in to comment.