Skip to content

Commit 2d9d8eb

Browse files
authored
Merge pull request #75 from PHPCSStandards/develop
Release version 1.1.1
2 parents 3192581 + 3f671a6 commit 2d9d8eb

File tree

14 files changed

+569
-89
lines changed

14 files changed

+569
-89
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/cs.yml

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ on:
44
# Run on all pushes and on all pull requests.
55
# Prevent the build from running when there are only irrelevant changes.
66
push:
7-
paths-ignore:
8-
- '**.md'
97
pull_request:
10-
paths-ignore:
11-
- '**.md'
8+
# Allow manually triggering the workflow.
9+
workflow_dispatch:
10+
11+
# Cancels all previous workflow runs for the same branch that have not yet completed.
12+
concurrency:
13+
# The concurrency group contains the workflow name and the branch name.
14+
group: ${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
1216

1317
jobs:
1418
checkcs:
@@ -20,28 +24,36 @@ jobs:
2024

2125
steps:
2226
- name: Checkout code
23-
uses: actions/checkout@v2
27+
uses: actions/checkout@v3
2428

2529
- name: Install PHP
2630
uses: shivammathur/setup-php@v2
2731
with:
2832
php-version: '7.4'
2933
coverage: none
34+
tools: cs2pr
35+
36+
# Validate the composer.json file.
37+
# @link https://getcomposer.org/doc/03-cli.md#validate
38+
- name: Validate Composer installation
39+
run: composer validate --no-check-all --strict
3040

3141
- name: 'Composer: adjust dependencies'
3242
run: |
3343
# The sniff stage doesn't run the unit tests, so no need for PHPUnit.
34-
composer remove --no-update --dev phpunit/phpunit --no-scripts
44+
composer remove --no-update --dev phpunit/phpunit --no-scripts --no-interaction
3545
# Using PHPCS `master` as an early detection system for bugs upstream.
36-
composer require --no-update squizlabs/php_codesniffer:"dev-master"
46+
composer require --no-update squizlabs/php_codesniffer:"dev-master" --no-interaction
3747
3848
# Install dependencies and handle caching in one go.
3949
# @link https://github.com/marketplace/actions/install-composer-dependencies
4050
- name: Install Composer dependencies
41-
uses: "ramsey/composer-install@v1"
51+
uses: "ramsey/composer-install@v2"
4252

4353
- name: Install xmllint
44-
run: sudo apt-get install --no-install-recommends -y libxml2-utils
54+
run: |
55+
sudo apt-get update
56+
sudo apt-get install --no-install-recommends -y libxml2-utils
4557
4658
# Show XML violations inline in the file diff.
4759
# @link https://github.com/marketplace/actions/xmllint-problem-matcher
@@ -58,9 +70,8 @@ jobs:
5870

5971
# Check the code-style consistency of the PHP files.
6072
- name: Check PHP code style
61-
run: composer check-cs
73+
continue-on-error: true
74+
run: composer checkcs -- --report-full --report-checkstyle=./phpcs-report.xml
6275

63-
# Validate the composer.json file.
64-
# @link https://getcomposer.org/doc/03-cli.md#validate
65-
- name: Validate Composer installation
66-
run: composer validate --no-check-all --strict
76+
- name: Show PHPCS results in PR
77+
run: cs2pr ./phpcs-report.xml

0 commit comments

Comments
 (0)