Skip to content

Commit 6105e52

Browse files
authored
Merge pull request #73 from PHPCSStandards/feature/auto-deploy-ghpages-site
GH Actions: auto-deploy website on push to `stable`
2 parents 5d00339 + c2b5efb commit 6105e52

File tree

8 files changed

+429
-8
lines changed

8 files changed

+429
-8
lines changed

.github/build/Website.php

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
<?php
2+
/**
3+
* PHPCSDevTools, tools for PHP_CodeSniffer sniff developers.
4+
*
5+
* @package PHPCSDevTools\GHPages
6+
* @copyright 2019 PHPCSDevTools Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSDevTools
9+
*/
10+
11+
namespace PHPCSDevTools\Build;
12+
13+
use RuntimeException;
14+
15+
/**
16+
* Prepare the website pages for deploy to GH Pages.
17+
*
18+
* {@internal This functionality has a minimum PHP requirement of PHP 7.2.}
19+
*
20+
* @internal
21+
*
22+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewParamTypeDeclarations.stringFound
23+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.intFound
24+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.stringFound
25+
* @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.voidFound
26+
* @phpcs:disable PHPCompatibility.InitialValue.NewConstantArraysUsingConst.Found
27+
* @phpcs:disable PHPCompatibility.InitialValue.NewConstantScalarExpressions.constFound
28+
*/
29+
final class Website
30+
{
31+
32+
/**
33+
* Path to project root (without trailing slash).
34+
*
35+
* @var string
36+
*/
37+
const PROJECT_ROOT = __DIR__ . '/../..';
38+
39+
/**
40+
* Relative path to target directory off project root (without trailing slash).
41+
*
42+
* @var string
43+
*/
44+
const TARGET_DIR = '/deploy';
45+
46+
/**
47+
* Files to copy.
48+
*
49+
* Source should be the relative path from the project root.
50+
* Target should be the relative path in the target directory.
51+
* If target is left empty, the target will be the same as the source.
52+
*
53+
* @var array<string => string target>
54+
*/
55+
const FILES_TO_COPY = [
56+
'README.md' => 'index.md',
57+
];
58+
59+
/**
60+
* Frontmatter.
61+
*
62+
* @var string
63+
*/
64+
const FRONTMATTER = '---
65+
---
66+
';
67+
68+
/**
69+
* Resolved path to project root (with trailing slash).
70+
*
71+
* @var string
72+
*/
73+
private $realRoot;
74+
75+
/**
76+
* Resolved path to target directory (with trailing slash).
77+
*
78+
* @var string
79+
*/
80+
private $realTarget;
81+
82+
/**
83+
* Constructor
84+
*
85+
* @return void
86+
*/
87+
public function __construct()
88+
{
89+
// Check if the target directory exists and if not, create it.
90+
$targetDir = self::PROJECT_ROOT . self::TARGET_DIR;
91+
92+
if (@\is_dir($targetDir) === false) {
93+
if (@\mkdir($targetDir, 0777, true) === false) {
94+
throw new RuntimeException(\sprintf('Failed to create the %s directory.', $targetDir));
95+
}
96+
}
97+
98+
$realPath = \realpath($targetDir);
99+
if ($realPath === false) {
100+
throw new RuntimeException(\sprintf('Failed to find the %s directory.', $targetDir));
101+
}
102+
103+
$this->realRoot = \realpath(self::PROJECT_ROOT) . '/';
104+
$this->realTarget = $realPath . '/';
105+
}
106+
107+
/**
108+
* Run the transformation.
109+
*
110+
* @return int Exit code.
111+
*/
112+
public function run(): int
113+
{
114+
$exitcode = 0;
115+
116+
try {
117+
$this->copyFiles();
118+
$this->transformIndex();
119+
} catch (RuntimeException $e) {
120+
echo 'ERROR: ', $e->getMessage(), \PHP_EOL;
121+
$exitcode = 1;
122+
}
123+
124+
return $exitcode;
125+
}
126+
127+
/**
128+
* Copy files to the target directory.
129+
*
130+
* @return void
131+
*/
132+
private function copyFiles(): void
133+
{
134+
foreach (self::FILES_TO_COPY as $source => $target) {
135+
$source = $this->realRoot . $source;
136+
if (empty($target)) {
137+
$target = $this->realTarget . $source;
138+
} else {
139+
$target = $this->realTarget . $target;
140+
}
141+
142+
// Bit round-about way of copying the files, but we need to make sure the target dir exists.
143+
$contents = $this->getContents($source);
144+
$this->putContents($target, $contents);
145+
}
146+
}
147+
148+
/**
149+
* Transform the README to a usable homepage.
150+
*
151+
* - Remove the title and subtitle as those would become duplicate.
152+
* - Remove most of the badges, except for the first three.
153+
* - Transform those badges into HTML.
154+
* - Add frontmatter.
155+
*
156+
* @return void
157+
*
158+
* @throws \RuntimeException When any of the regexes do not yield any results.
159+
*/
160+
private function transformIndex(): void
161+
{
162+
// Read the file.
163+
$target = $this->realTarget . '/index.md';
164+
$contents = $this->getContents($target);
165+
166+
// Grab the start of the document.
167+
$matched = \preg_match('`^(.+)\* \[Installation\]`s', $contents, $matches);
168+
if ($matched !== 1) {
169+
throw new RuntimeException('Failed to match start of document. Adjust the regex');
170+
}
171+
172+
$startOfDoc = $matches[1];
173+
174+
// Grab the first few badges from the start of the document.
175+
$matched = \preg_match(
176+
'`((?:\[!\[[^\]]+\]\([^\)]+\)\]\([^\)]+\)[\n\r]+)+):construction:`',
177+
$startOfDoc,
178+
$matches
179+
);
180+
if ($matched !== 1) {
181+
throw new RuntimeException('Failed to match badges. Adjust the regex');
182+
}
183+
184+
$badges = \explode("\n", $matches[1]);
185+
$badges = \array_filter($badges);
186+
$badges = \array_map([$this, 'mdBadgeToHtml'], $badges);
187+
$badges = \implode("\n ", $badges);
188+
189+
$replacement = \sprintf(
190+
'%s
191+
192+
<div id="badges" aria-hidden="true">
193+
194+
%s
195+
196+
</div>
197+
198+
',
199+
self::FRONTMATTER,
200+
' ' . $badges
201+
);
202+
203+
$contents = \str_replace($startOfDoc, $replacement, $contents);
204+
205+
$this->putContents($target, $contents);
206+
}
207+
208+
/**
209+
* Transform markdown badges into HTML badges.
210+
*
211+
* Jekyll runs into trouble doing this when we also want to keep the wrapper div with aria-hidden="true".
212+
*
213+
* @param string $mdBadge Markdown badge code.
214+
*
215+
* @return string
216+
*/
217+
private function mdBadgeToHtml(string $mdBadge): string
218+
{
219+
$mdBadge = trim($mdBadge);
220+
221+
$matched = \preg_match(
222+
'`^\[!\[(?<alt>[^\]]+)\]\((?<imgurl>[^\)]+)\)\]\((?<href>[^\)]+)\)$`',
223+
$mdBadge,
224+
$matches
225+
);
226+
if ($matched !== 1) {
227+
throw new RuntimeException(\sprintf('Failed to parse the badge. Adjust the regex. Received: %s', $mdBadge));
228+
}
229+
230+
return \sprintf(
231+
'<a href="%s"><img src="%s" alt="%s" class="badge"></a>',
232+
$matches['href'],
233+
$matches['imgurl'],
234+
$matches['alt']
235+
);
236+
}
237+
238+
/**
239+
* Retrieve the contents of a file.
240+
*
241+
* @param string $source Path to the source file.
242+
*
243+
* @return string
244+
*
245+
* @throws \RuntimeException When the contents of the file could not be retrieved.
246+
*/
247+
private function getContents(string $source): string
248+
{
249+
$contents = \file_get_contents($source);
250+
if (!$contents) {
251+
throw new RuntimeException(\sprintf('Failed to read doc file: %s', $source));
252+
}
253+
254+
return $contents;
255+
}
256+
257+
/**
258+
* Write a string to a file.
259+
*
260+
* @param string $target Path to the target file.
261+
* @param string $contents File contents to write.
262+
*
263+
* @return void
264+
*
265+
* @throws \RuntimeException When the target directory could not be created.
266+
* @throws \RuntimeException When the file could not be written to the target directory.
267+
*/
268+
private function putContents(string $target, string $contents): void
269+
{
270+
// Check if the target directory exists and if not, create it.
271+
$targetDir = \dirname($target);
272+
273+
if (@\is_dir($targetDir) === false) {
274+
if (@\mkdir($targetDir, 0777, true) === false) {
275+
throw new RuntimeException(\sprintf('Failed to create the %s directory.', $targetDir));
276+
}
277+
}
278+
279+
// Make sure the file always ends on a new line.
280+
$contents = \rtrim($contents) . "\n";
281+
if (\file_put_contents($target, $contents) === false) {
282+
throw new RuntimeException(\sprintf('Failed to write to target location: %s', $target));
283+
}
284+
}
285+
}

.github/build/update-website.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env php
2+
<?php
3+
/**
4+
* PHPCSDevTools, tools for PHP_CodeSniffer sniff developers.
5+
*
6+
* Website deploy preparation script.
7+
*
8+
* Grabs files which will be used in the website, adjusts if needed and places them in a target directory.
9+
*
10+
* {@internal This functionality has a minimum PHP requirement of PHP 7.2.}
11+
*
12+
* @internal
13+
*
14+
* @package PHPCSDevTools\GHPages
15+
* @copyright 2019 PHPCSDevTools Contributors
16+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
17+
* @link https://github.com/PHPCSStandards/PHPCSDevTools
18+
*/
19+
20+
namespace PHPCSDevTools\Build;
21+
22+
require_once __DIR__ . '/Website.php';
23+
24+
$websiteUpdater = new Website();
25+
$websiteUpdateSuccess = $websiteUpdater->run();
26+
27+
exit($websiteUpdateSuccess);

.github/workflows/quicktest.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,12 @@ jobs:
2525
matrix:
2626
php: ['5.4', 'latest']
2727
phpcs_version: ['dev-master']
28-
lint: [true]
2928

3029
include:
3130
- php: '7.2'
3231
phpcs_version: '3.1.0'
33-
lint: false
3432
- php: '5.4'
3533
phpcs_version: '3.1.0'
36-
lint: false
3734

3835
name: "QTest${{ matrix.lint && ' + Lint' || '' }}: PHP ${{ matrix.php }} - PHPCS ${{ matrix.phpcs_version }}"
3936

@@ -71,10 +68,14 @@ jobs:
7168
- name: Install Composer dependencies
7269
uses: "ramsey/composer-install@v2"
7370

74-
- name: Lint against parse errors
75-
if: ${{ matrix.lint }}
71+
- name: Lint against parse errors (PHP 7.2+)
72+
if: ${{ matrix.phpcs_version == 'dev-master' && matrix.php >= '7.2' }}
7673
run: composer lint
7774

75+
- name: Lint against parse errors (PHP < 7.2)
76+
if: ${{ matrix.phpcs_version == 'dev-master' && matrix.php < '7.2' }}
77+
run: composer lintlt72
78+
7879
# Check that any sniffs available are feature complete.
7980
# This also acts as an integration test for the feature completeness script,
8081
# which is why it is run against various PHP versions and not in the "Sniff" stage.

.github/workflows/test.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,14 @@ jobs:
120120
with:
121121
composer-options: --ignore-platform-reqs
122122

123-
- name: Lint against parse errors
124-
if: matrix.phpcs_version == 'dev-master'
123+
- name: Lint against parse errors (PHP 7.2+)
124+
if: ${{ matrix.phpcs_version == 'dev-master' && matrix.php >= '7.2' }}
125125
run: composer lint -- --checkstyle | cs2pr
126126

127+
- name: Lint against parse errors (PHP < 7.2)
128+
if: ${{ matrix.phpcs_version == 'dev-master' && matrix.php < '7.2' }}
129+
run: composer lintlt72 -- --checkstyle | cs2pr
130+
127131
# Check that any sniffs available are feature complete.
128132
# This also acts as an integration test for the feature completeness script,
129133
# which is why it is run against various PHP versions and not in the "Sniff" stage.

0 commit comments

Comments
 (0)