Skip to content

Commit df0ef52

Browse files
authored
Merge pull request #421 from PHPCSStandards/develop
Release 1.0.0
2 parents a98f01e + 8d3b6cb commit df0ef52

File tree

105 files changed

+646
-42121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+646
-42121
lines changed

.github/GHPages/UpdateWebsite.php

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
<?php
2+
/**
3+
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4+
*
5+
* @package PHPCSUtils
6+
* @copyright 2019-2020 PHPCSUtils Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSUtils
9+
*/
10+
11+
namespace PHPCSUtils\GHPages;
12+
13+
use RuntimeException;
14+
15+
/**
16+
* Prepare markdown documents for use in a GH Pages website before deploy.
17+
*
18+
* {@internal This functionality has a minimum PHP requirement of PHP 7.2.}
19+
*
20+
* @internal
21+
*
22+
* @phpcs:disable PHPCompatibility.Classes.NewConstVisibility.Found
23+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewParamTypeDeclarations.intFound
24+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewParamTypeDeclarations.stringFound
25+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.intFound
26+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.stringFound
27+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.voidFound
28+
* @phpcs:disable PHPCompatibility.InitialValue.NewConstantScalarExpressions.constFound
29+
*/
30+
final class UpdateWebsite
31+
{
32+
33+
/**
34+
* Path to project root (without trailing slash).
35+
*
36+
* @var string
37+
*/
38+
private const PROJECT_ROOT = __DIR__ . '/../..';
39+
40+
/**
41+
* Relative path to target directory off project root (without trailing slash).
42+
*
43+
* @var string
44+
*/
45+
private const TARGET_DIR = 'docs';
46+
47+
/**
48+
* Frontmatter for the website homepage.
49+
*
50+
* @var string
51+
*/
52+
private const README_FRONTMATTER = '---
53+
title: PHPCSUtils
54+
description: "PHPCSUtils: A suite of utility functions for use with PHP_CodeSniffer"
55+
anchor: home
56+
permalink: /
57+
seo:
58+
type: WebSite
59+
publisher:
60+
type: Organisation
61+
---
62+
';
63+
64+
/**
65+
* Frontmatter for the changelog page.
66+
*
67+
* @var string
68+
*/
69+
private const CHANGELOG_FRONTMATTER = '---
70+
title: Changelog
71+
description: "Changelog for the PHPCSUtils suite of utility functions for use with PHP_CodeSniffer"
72+
anchor: changelog
73+
permalink: /changelog
74+
seo:
75+
type: WebSite
76+
publisher:
77+
type: Organisation
78+
---
79+
';
80+
81+
/**
82+
* Resolved path to project root (with trailing slash).
83+
*
84+
* @var string
85+
*/
86+
private $realRoot;
87+
88+
/**
89+
* Resolved path to target directory (with trailing slash).
90+
*
91+
* @var string
92+
*/
93+
private $realTarget;
94+
95+
/**
96+
* Run the transformation.
97+
*
98+
* @return int Exit code.
99+
*/
100+
public function run(): int
101+
{
102+
$exitcode = 0;
103+
104+
try {
105+
$this->setPaths();
106+
$this->transformReadme();
107+
$this->transformChangelog();
108+
} catch (RuntimeException $e) {
109+
echo 'ERROR: ', $e->getMessage(), \PHP_EOL;
110+
$exitcode = 1;
111+
}
112+
113+
return $exitcode;
114+
}
115+
116+
/**
117+
* Validate the paths to use.
118+
*
119+
* @return void
120+
*/
121+
private function setPaths(): void
122+
{
123+
$realRoot = \realpath(self::PROJECT_ROOT) . '/';
124+
if ($realRoot === false) {
125+
throw new RuntimeException(\sprintf('Failed to find the %s directory.', $realRoot));
126+
}
127+
128+
$this->realRoot = $realRoot;
129+
130+
// Check if the target directory exists and if not, create it.
131+
$targetDir = $this->realRoot . self::TARGET_DIR;
132+
133+
if (@\is_dir($targetDir) === false) {
134+
if (@\mkdir($targetDir, 0777, true) === false) {
135+
throw new RuntimeException(\sprintf('Failed to create the %s directory.', $targetDir));
136+
}
137+
}
138+
139+
$realPath = \realpath($targetDir);
140+
if ($realPath === false) {
141+
throw new RuntimeException(\sprintf('Failed to find the %s directory.', $targetDir));
142+
}
143+
144+
$this->realTarget = $realPath . '/';
145+
}
146+
147+
/**
148+
* Apply various transformations to the index page.
149+
*
150+
* - Remove title, badges and index.
151+
* - Replace code samples with properly highlighted versions.
152+
* - Add frontmatter.
153+
*
154+
* @return void
155+
*
156+
* @throws \RuntimeException When any of the expected replacements could not be made.
157+
*/
158+
private function transformReadme(): void
159+
{
160+
$contents = $this->getContents($this->realRoot . 'README.md');
161+
162+
// Remove title, badges and index.
163+
$contents = $this->replace('`^.*## Features`s', '## Features', $contents, 1);
164+
165+
// Remove the section about Non-Composer based integration.
166+
$contents = $this->replace(
167+
'`### Non-Composer based integration[\n\r]+(?:.+[\n\r]+)+?## Frequently Asked Questions`',
168+
'## Frequently Asked Questions',
169+
$contents,
170+
1
171+
);
172+
173+
// Replace installation instructions with properly highlighted version.
174+
$search = '~`{3}bash[\n\r]+composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer'
175+
. ' true[\n\r]+'
176+
. 'composer require phpcsstandards/phpcsutils:"([^\n\r]+)"[\n\r]+`{3}~';
177+
$replace = '<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'
178+
. 'composer config <span class="s">allow-plugins.dealerdirect/phpcodesniffer-composer-installer</span>'
179+
. ' <span class="mf">true</span>'
180+
. "\n"
181+
. 'composer require <span class="s">{{ site.phpcsutils.packagist }}</span>:"<span class="mf">$1</span>"'
182+
. "\n"
183+
. '</code></pre></div></div>';
184+
$contents = $this->replace($search, $replace, $contents, 1);
185+
186+
// Replace suggested end-user installation instructions with properly highlighted versions.
187+
$search = '~`{3}bash[\r\n]+> composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer'
188+
. ' true[\r\n]+> `{3}~';
189+
$replace = '<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'
190+
. 'composer config <span class="s">allow-plugins.dealerdirect/phpcodesniffer-composer-installer</span>'
191+
. ' <span class="mf">true</span>'
192+
. "\n"
193+
. '> </code></pre></div></div>';
194+
$contents = $this->replace($search, $replace, $contents, 1);
195+
196+
// Replace suggested end-user upgrade instructions with properly highlighted versions.
197+
$search = '~`{3}bash[\r\n]+> composer update your/cs-package --with-\[all-\]dependencies[\r\n]+> `{3}~';
198+
$replace = '<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'
199+
. 'composer update <span class="s">your/cs-package</span> <span class="mf">--with-[all-]dependencies</span>'
200+
. "\n"
201+
. '> </code></pre></div></div>';
202+
$contents = $this->replace($search, $replace, $contents, 1);
203+
204+
// Add frontmatter.
205+
$contents = self::README_FRONTMATTER . "\n" . $contents;
206+
207+
$this->putContents($this->realTarget . 'index.md', $contents);
208+
}
209+
210+
/**
211+
* Add frontmatter to the changelog page and remove "Unreleased".
212+
*
213+
* @return void
214+
*/
215+
private function transformChangelog(): void
216+
{
217+
$contents = $this->getContents($this->realRoot . 'CHANGELOG.md');
218+
219+
// Remove the section about Non-Composer based integration.
220+
$contents = $this->replace(
221+
'`## \[Unreleased\][\n\r]+(?:.+[\n\r]+)+?##`',
222+
'##',
223+
$contents,
224+
1
225+
);
226+
227+
// Add frontmatter.
228+
$contents = self::CHANGELOG_FRONTMATTER . "\n" . $contents;
229+
230+
$this->putContents($this->realTarget . 'changelog.md', $contents);
231+
}
232+
233+
/**
234+
* Execute a regex search and replace and verify the replacement was actually made.
235+
*
236+
* @param string $search The pattern to search for.
237+
* @param string $replace The replacement.
238+
* @param string $subject The string to execute the search & replace on.
239+
* @param int $limit Maximum number of replacements to make.
240+
*
241+
* @return string
242+
*
243+
* @throws \RuntimeException When the replacement was not made or not made the required number of times.
244+
*/
245+
private function replace(string $search, string $replace, string $subject, int $limit = 1): string
246+
{
247+
$subject = \preg_replace($search, $replace, $subject, $limit, $count);
248+
if ($count !== $limit) {
249+
throw new RuntimeException(
250+
'Failed to make required replacement.' . \PHP_EOL
251+
. "Search regex: $search" . \PHP_EOL
252+
. "Replacements made: $count"
253+
);
254+
}
255+
256+
return $subject;
257+
}
258+
259+
/**
260+
* Retrieve the contents of a file.
261+
*
262+
* @param string $source Path to the source file.
263+
*
264+
* @return string
265+
*
266+
* @throws \RuntimeException When the contents of the file could not be retrieved.
267+
*/
268+
private function getContents(string $source): string
269+
{
270+
$contents = \file_get_contents($source);
271+
if (!$contents) {
272+
throw new RuntimeException(\sprintf('Failed to read doc file: %s', $source));
273+
}
274+
275+
return $contents;
276+
}
277+
278+
/**
279+
* Write a string to a file.
280+
*
281+
* @param string $target Path to the target file.
282+
* @param string $contents File contents to write.
283+
*
284+
* @return void
285+
*
286+
* @throws \RuntimeException When the target directory could not be created.
287+
* @throws \RuntimeException When the file could not be written to the target directory.
288+
*/
289+
private function putContents(string $target, string $contents): void
290+
{
291+
// Check if the target directory exists and if not, create it.
292+
$targetDir = \dirname($target);
293+
294+
if (@\is_dir($targetDir) === false) {
295+
if (@\mkdir($targetDir, 0777, true) === false) {
296+
throw new RuntimeException(\sprintf('Failed to create the %s directory.', $targetDir));
297+
}
298+
}
299+
300+
// Make sure the file always ends on a new line.
301+
$contents = \rtrim($contents) . "\n";
302+
if (\file_put_contents($target, $contents) === false) {
303+
throw new RuntimeException(\sprintf('Failed to write to target location: %s', $target));
304+
}
305+
}
306+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env php
2+
<?php
3+
/**
4+
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
5+
*
6+
* Update the phpDocumentor configuration file.
7+
*
8+
* {@internal This functionality has a minimum PHP requirement of PHP 7.2.}
9+
*
10+
* @internal
11+
*
12+
* @package PHPCSUtils
13+
* @copyright 2019-2020 PHPCSUtils Contributors
14+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
15+
* @link https://github.com/PHPCSStandards/PHPCSUtils
16+
*
17+
* @phpcs:disable PHPCompatibility.FunctionUse.NewFunctionParameters.getenv_local_onlyFound
18+
* @phpcs:disable PHPCompatibility.FunctionUse.NewFunctionParameters.dirname_levelsFound
19+
*/
20+
21+
namespace PHPCSUtils\GHPages;
22+
23+
$phpcsutilsPhpdocVersionUpdater = static function () {
24+
$tagname = \getenv('TAG', true);
25+
if ($tagname === false) {
26+
echo 'ERROR: No TAG environment variable found.', \PHP_EOL;
27+
exit(1);
28+
}
29+
30+
$tagname = \trim($tagname);
31+
if ($tagname === '' || \preg_match('`^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:alpha|beta|rc)[0-9]*)?$`', $tagname) !== 1) {
32+
echo "ERROR: \"$tagname\" is not a valid tag.", \PHP_EOL;
33+
exit(1);
34+
}
35+
36+
$projectRoot = \dirname(__DIR__, 2);
37+
$source = '.phpdoc.xml.dist';
38+
$destination = 'phpdoc.xml';
39+
$count = 0;
40+
41+
if (\file_exists($projectRoot . '/' . $destination)) {
42+
echo "WARNING: Detected pre-existing \"$destination\" file.", \PHP_EOL;
43+
echo "Please make sure that this overload file is in sync with the \"$source\" file.", \PHP_EOL;
44+
echo 'This is your own responsibility!' . \PHP_EOL, \PHP_EOL;
45+
46+
$config = \file_get_contents($projectRoot . '/' . $destination);
47+
if (!$config) {
48+
echo "ERROR: Failed to read phpDocumentor $destination configuration file.", \PHP_EOL;
49+
exit(1);
50+
}
51+
52+
// Replace the previous version nr in the API doc title with the latest version number.
53+
$config = \preg_replace(
54+
'`<title>PHPCSUtils ([\#0-9\.]+)</title>`',
55+
"<title>PHPCSUtils {$tagname}</title>",
56+
$config,
57+
-1,
58+
$count
59+
);
60+
} else {
61+
$config = \file_get_contents($projectRoot . '/' . $source);
62+
if (!$config) {
63+
echo "ERROR: Failed to read phpDocumentor $source configuration template file.", \PHP_EOL;
64+
exit(1);
65+
}
66+
67+
// Replace the "#.#.#" placeholder in the API doc title with the latest version number.
68+
$config = \str_replace(
69+
'<title>PHPCSUtils</title>',
70+
"<title>PHPCSUtils {$tagname}</title>",
71+
$config,
72+
$count
73+
);
74+
}
75+
76+
if ($count !== 1) {
77+
echo "ERROR: Version number text replacement failed. Made $count replacements.", \PHP_EOL;
78+
exit(1);
79+
}
80+
81+
if (\file_put_contents($projectRoot . '/' . $destination, $config) === false) {
82+
echo "ERROR: Failed to write phpDocumentor $destination configuration file.", \PHP_EOL;
83+
exit(1);
84+
} else {
85+
echo "SUCCESFULLY updated/created the $destination file!", \PHP_EOL;
86+
}
87+
88+
exit(0);
89+
};
90+
91+
$phpcsutilsPhpdocVersionUpdater();

0 commit comments

Comments
 (0)