Skip to content

Commit 6605a8a

Browse files
author
Paul M. Jones
authored
Merge pull request #21 from afilina/compliance-validator
Compliance validator
2 parents 84b5113 + 611e299 commit 6605a8a

File tree

3 files changed

+367
-0
lines changed

3 files changed

+367
-0
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ A package MUST use these names for these root-level files:
3838
A package SHOULD include a root-level file indicating the licensing and
3939
copyright terms of the package contents.
4040

41+
## Validator
42+
43+
Quickly validate your project's compliance by following these steps:
44+
45+
- Install package in your project: `composer require pds/skeleton @dev`
46+
- Run the validator: `./vendor/pds/skeleton/bin/validate`
47+
4148
## Root-Level Directories
4249

4350
### bin/

bin/validate

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
#!/usr/bin/env php
2+
3+
<?php
4+
5+
if (!defined('ENV') || ENV != 'test') {
6+
$lines = scandir(__DIR__ . "/../../../../");
7+
foreach ($lines as $i => $line) {
8+
if (is_dir($line)) {
9+
$lines[$i] .= "/";
10+
}
11+
}
12+
$validator = new ComplianceValidator();
13+
$results = $validator->validate($lines);
14+
$validator->outputResults($results);
15+
}
16+
17+
class ComplianceValidator
18+
{
19+
const STATE_OPTIONAL_NOT_PRESENT = 1;
20+
const STATE_CORRECT_PRESENT = 2;
21+
const STATE_REQUIRED_NOT_PRESENT = 3;
22+
const STATE_INCORRECT_PRESENT = 4;
23+
24+
public function validate($lines)
25+
{
26+
$complianceTests = [
27+
"Command-line executables" => $this->checkBin($lines),
28+
"Configuration files" => $this->checkConfig($lines),
29+
"Documentation files" => $this->checkDocs($lines),
30+
"Web server files" => $this->checkPublic($lines),
31+
"Other resource files" => $this->checkResources($lines),
32+
"PHP source code" => $this->checkSrc($lines),
33+
"Test code" => $this->checkTests($lines),
34+
"Package managers" => $this->checkVendor($lines),
35+
"Log of changes between releases" => $this->checkChangelog($lines),
36+
"Guidelines for contributors" => $this->checkContributing($lines),
37+
"Licensing information" => $this->checkLicense($lines),
38+
"Information about the package itself" => $this->checkReadme($lines),
39+
];
40+
41+
$results = [];
42+
foreach ($complianceTests as $label => $complianceResult) {
43+
$state = $complianceResult[0];
44+
$expected = $complianceResult[1];
45+
$actual = $complianceResult[2];
46+
$results[$expected] = [
47+
'label' => $label,
48+
'state' => $state,
49+
'expected' => $expected,
50+
'actual' => $actual,
51+
];
52+
}
53+
return $results;
54+
}
55+
56+
public function outputResults($results)
57+
{
58+
foreach ($results as $result) {
59+
$this->outputResultLine($result['label'], $result['state'], $result['expected'], $result['actual']);
60+
}
61+
}
62+
63+
protected function outputResultLine($label, $complianceState, $expected, $actual)
64+
{
65+
$messages = [
66+
self::STATE_OPTIONAL_NOT_PRESENT => "Optional {$expected} not present",
67+
self::STATE_CORRECT_PRESENT => "Correct {$actual} present",
68+
self::STATE_INCORRECT_PRESENT => "Incorrect {$actual} present",
69+
self::STATE_REQUIRED_NOT_PRESENT => "Required {$expected} not present",
70+
];
71+
echo $this->colorConsoleText("- " . $label . ": " . $messages[$complianceState], $complianceState) . PHP_EOL;
72+
}
73+
74+
protected function colorConsoleText($text, $complianceState)
75+
{
76+
$colors = [
77+
self::STATE_OPTIONAL_NOT_PRESENT => "\033[43;30m",
78+
self::STATE_CORRECT_PRESENT => "\033[42;30m",
79+
self::STATE_INCORRECT_PRESENT => "\033[41m",
80+
self::STATE_REQUIRED_NOT_PRESENT => "\033[41m",
81+
];
82+
if (!array_key_exists($complianceState, $colors)) {
83+
return $text;
84+
}
85+
return $colors[$complianceState] . " " . $text . " \033[0m";
86+
}
87+
88+
protected function checkDir($lines, $pass, array $fail)
89+
{
90+
foreach ($lines as $line) {
91+
$line = trim($line);
92+
if ($line == $pass) {
93+
return [self::STATE_CORRECT_PRESENT, $pass, $line];
94+
}
95+
if (in_array($line, $fail)) {
96+
return [self::STATE_INCORRECT_PRESENT, $pass, $line];
97+
}
98+
}
99+
return [self::STATE_OPTIONAL_NOT_PRESENT, $pass, null];
100+
}
101+
102+
protected function checkFile($lines, $pass, array $fail)
103+
{
104+
foreach ($lines as $line) {
105+
$line = trim($line);
106+
if (preg_match("/^{$pass}(\.[a-z]+)?$/", $line)) {
107+
return [self::STATE_CORRECT_PRESENT, $pass, $line];
108+
}
109+
foreach ($fail as $regex) {
110+
if (preg_match($regex, $line)) {
111+
return [self::STATE_INCORRECT_PRESENT, $pass, $line];
112+
}
113+
}
114+
}
115+
return [self::STATE_OPTIONAL_NOT_PRESENT, $pass, null];
116+
}
117+
118+
protected function checkVendor($lines, $pass = 'vendor/')
119+
{
120+
foreach ($lines as $line) {
121+
$line = trim($line);
122+
if ($line == $pass) {
123+
return [self::STATE_CORRECT_PRESENT, $pass, $line];
124+
}
125+
}
126+
return [self::STATE_REQUIRED_NOT_PRESENT, $pass, null];
127+
}
128+
129+
protected function checkChangelog($lines)
130+
{
131+
return $this->checkFile($lines, 'CHANGELOG', [
132+
'/^.*CHANGLOG.*$/i',
133+
'/^.*CAHNGELOG.*$/i',
134+
'/^WHATSNEW(\.[a-z]+)?$/i',
135+
'/^RELEASE((_|-)?NOTES)?(\.[a-z]+)?$/i',
136+
'/^RELEASES(\.[a-z]+)?$/i',
137+
'/^CHANGES(\.[a-z]+)?$/i',
138+
'/^CHANGE(\.[a-z]+)?$/i',
139+
'/^HISTORY(\.[a-z]+)?$/i',
140+
]);
141+
}
142+
143+
protected function checkContributing($lines)
144+
{
145+
return $this->checkFile($lines, 'CONTRIBUTING', [
146+
'/^DEVELOPMENT(\.[a-z]+)?$/i',
147+
'/^README\.CONTRIBUTING(\.[a-z]+)?$/i',
148+
'/^DEVELOPMENT_README(\.[a-z]+)?$/i',
149+
'/^CONTRIBUTE(\.[a-z]+)?$/i',
150+
'/^HACKING(\.[a-z]+)?$/i',
151+
]);
152+
}
153+
154+
protected function checkLicense($lines)
155+
{
156+
return $this->checkFile($lines, 'LICENSE', [
157+
'/^.*EULA.*$/i',
158+
'/^.*(GPL|BSD).*$/i',
159+
'/^([A-Z-]+)?LI(N)?(S|C)(E|A)N(S|C)(E|A)(_[A-Z_]+)?(\.[a-z]+)?$/i',
160+
'/^COPY(I)?NG(\.[a-z]+)?$/i',
161+
'/^COPYRIGHT(\.[a-z]+)?$/i',
162+
]);
163+
}
164+
165+
protected function checkReadme($lines)
166+
{
167+
return $this->checkFile($lines, 'README', [
168+
'/^USAGE(\.[a-z]+)?$/i',
169+
'/^SUMMARY(\.[a-z]+)?$/i',
170+
'/^DESCRIPTION(\.[a-z]+)?$/i',
171+
'/^IMPORTANT(\.[a-z]+)?$/i',
172+
'/^NOTICE(\.[a-z]+)?$/i',
173+
'/^GETTING(_|-)STARTED(\.[a-z]+)?$/i',
174+
]);
175+
}
176+
177+
protected function checkBin($lines)
178+
{
179+
return $this->checkDir($lines, 'bin/', [
180+
'cli/',
181+
'scripts/',
182+
'console/',
183+
'shell/',
184+
'script/',
185+
]);
186+
}
187+
188+
protected function checkConfig($lines)
189+
{
190+
return $this->checkDir($lines, 'config/', [
191+
'etc/',
192+
'settings/',
193+
'configuration/',
194+
'configs/',
195+
'_config/',
196+
'conf/',
197+
]);
198+
}
199+
200+
protected function checkDocs($lines)
201+
{
202+
return $this->checkDir($lines, 'docs/', [
203+
'manual/',
204+
'documentation/',
205+
'usage/',
206+
'doc/',
207+
'guide/',
208+
'phpdoc/',
209+
]);
210+
}
211+
212+
protected function checkPublic($lines)
213+
{
214+
return $this->checkDir($lines, 'public/', [
215+
'assets/',
216+
'static/',
217+
'html/',
218+
'httpdocs/',
219+
'media/',
220+
'docroot/',
221+
'css/',
222+
'fonts/',
223+
'styles/',
224+
'style/',
225+
'js/',
226+
'javascript/',
227+
'images/',
228+
'site/',
229+
'mysite/',
230+
'img/',
231+
'web/',
232+
'pub/',
233+
'webroot/',
234+
'www/',
235+
'htdocs/',
236+
'asset/',
237+
'public_html/',
238+
'publish/',
239+
'pages/',
240+
]);
241+
}
242+
243+
protected function checkSrc($lines)
244+
{
245+
return $this->checkDir($lines, 'src/', [
246+
'exception/',
247+
'exceptions/',
248+
'src-files/',
249+
'traits/',
250+
'interfaces/',
251+
'common/',
252+
'sources/',
253+
'php/',
254+
'inc/',
255+
'libraries/',
256+
'autoloads/',
257+
'autoload/',
258+
'source/',
259+
'includes/',
260+
'include/',
261+
'lib/',
262+
'libs/',
263+
'library/',
264+
'code/',
265+
'classes/',
266+
'func/',
267+
]);
268+
}
269+
270+
protected function checkTests($lines)
271+
{
272+
return $this->checkDir($lines, 'tests/', [
273+
'test/',
274+
'unit-tests/',
275+
'phpunit/',
276+
'testing/',
277+
]);
278+
}
279+
280+
protected function checkResources($lines)
281+
{
282+
return $this->checkDir($lines, 'resources/', [
283+
'Resources/',
284+
'res/',
285+
'resource/',
286+
'Resource/',
287+
'ressources/',
288+
'Ressources/',
289+
]);
290+
}
291+
}

tests/ComplianceValidatorTest.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
define('ENV', 'test');
4+
require __DIR__ . "/../bin/validate";
5+
6+
$tester = new ComplianceValidatorTest();
7+
// Test all 4 possible states.
8+
$tester->testValidate_WithIncorrectBin_ReturnsIncorrectBin();
9+
$tester->testValidate_WithoutVendor_ReturnsMissingVendor();
10+
11+
echo "Errors: {$tester->numErrors}" . PHP_EOL;
12+
13+
class ComplianceValidatorTest
14+
{
15+
public $numErrors = 0;
16+
17+
public function testValidate_WithIncorrectBin_ReturnsIncorrectBin()
18+
{
19+
$paths = [
20+
'cli/',
21+
'vendor/',
22+
];
23+
24+
$validator = new ComplianceValidator();
25+
$results = $validator->validate($paths);
26+
27+
foreach ($results as $expected => $result) {
28+
if ($expected == "bin/") {
29+
if ($result['state'] != ComplianceValidator::STATE_INCORRECT_PRESENT) {
30+
$this->numErrors++;
31+
echo __FUNCTION__ . ": Expected state of {$result['expected']} to be STATE_INCORRECT_PRESENT" . PHP_EOL;
32+
}
33+
continue;
34+
}
35+
if ($expected == "vendor/") {
36+
if ($result['state'] != ComplianceValidator::STATE_CORRECT_PRESENT) {
37+
$this->numErrors++;
38+
echo __FUNCTION__ . ": Expected state of {$result['expected']} to be STATE_CORRECT_PRESENT" . PHP_EOL;
39+
}
40+
continue;
41+
}
42+
if ($result['state'] != ComplianceValidator::STATE_OPTIONAL_NOT_PRESENT) {
43+
$this->numErrors++;
44+
echo __FUNCTION__ . ": Expected state of {$result['expected']} to be STATE_OPTIONAL_NOT_PRESENT" . PHP_EOL;
45+
continue;
46+
}
47+
}
48+
}
49+
50+
public function testValidate_WithoutVendor_ReturnsMissingVendor()
51+
{
52+
$paths = [
53+
'bin/',
54+
];
55+
56+
$validator = new ComplianceValidator();
57+
$results = $validator->validate($paths);
58+
59+
foreach ($results as $expected => $result) {
60+
if ($expected == "vendor/") {
61+
if ($result['state'] != ComplianceValidator::STATE_REQUIRED_NOT_PRESENT) {
62+
$this->numErrors++;
63+
echo __FUNCTION__ . ": Expected state of {$result['expected']} to be STATE_REQUIRED_NOT_PRESENT" . PHP_EOL;
64+
}
65+
continue;
66+
}
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)