[TASK] Replace validateRstFiles.sh with PHP version
[Packages/TYPO3.CMS.git] / Build / Scripts / validateRstFiles.php
1 #!/usr/bin/env php
2 <?php
3 declare(strict_types=1);
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 require __DIR__ . '/../../vendor/autoload.php';
19
20 if (PHP_SAPI !== 'cli') {
21 die('Script must be called from command line.' . chr(10));
22 }
23
24 use Symfony\Component\Finder\Finder;
25
26 /**
27 * Check ReST files for integrity. If errors are found, they will be
28 * output on stdout and the program will exit with exit code 1.
29 *
30 * Optional arguments: -d <directory>
31 *
32 * By default, the standard path is used. You can override this for
33 * testing purposes.
34 */
35 class validateRstFiles
36 {
37 /**
38 * @var array
39 */
40 protected $messages;
41
42 /**
43 * @var bool
44 */
45 protected $isError;
46
47 /**
48 * @var string
49 */
50 protected $baseDir = 'typo3/sysext/core/Documentation/Changelog';
51
52 public function __construct(string $dir = '')
53 {
54 if ($dir) {
55 $this->baseDir = $dir;
56 }
57 }
58
59 public function validate()
60 {
61 printf('Searching for rst snippets in' . $this->baseDir . chr(10));
62
63 $count = 0;
64 $finder = $this->findFiles();
65 foreach ($finder as $file) {
66 $filename = (string)$file;
67 $this->clearMessages();
68 $fileContent = $file->getContents();
69 $this->validateContent($fileContent);
70 $a = explode(chr(10), trim($fileContent));
71 $lastLine = array_pop($a);
72 $this->validateLastLine($lastLine);
73 $this->validateLastLineForFilename($filename, $lastLine);
74
75 if ($this->isError) {
76 $shortPath = substr($filename, strlen($this->baseDir));
77 $shortPath = ltrim($shortPath, '/\\');
78 $count++;
79 printf(
80 '%-10s | %-12s | %-17s | %s ' . chr(10),
81 $this->messages['include']['title'],
82 $this->messages['reference']['title'],
83 $this->messages['index']['title'],
84 $shortPath
85 );
86 if ($this->messages['include']['message']) {
87 printf($this->messages['include']['message'] . chr(10));
88 }
89 if ($this->messages['reference']['message']) {
90 printf($this->messages['reference']['message'] . chr(10));
91 }
92 if ($this->messages['index']['message']) {
93 printf($this->messages['index']['message'] . chr(10));
94 }
95 }
96 }
97
98 if ($count > 0) {
99 fwrite(STDERR, 'Found ' . $count . ' rst files with errors, check full log for details.' . chr(10));
100 exit(1);
101 }
102 exit(0);
103 }
104
105 public function findFiles(): Finder
106 {
107 $finder = new Finder();
108 $finder
109 ->files()
110 ->in($this->baseDir)
111 ->name('/\.rst$/')
112 ->notName('Index.rst')
113 ->notName('Howto.rst');
114
115 return $finder;
116 }
117
118 protected function clearMessages()
119 {
120 $this->messages = [
121 'include' => [
122 'title' => '',
123 'message' => '',
124 ],
125 'reference' => [
126 'title' => '',
127 'message' => '',
128 ],
129 'index' => [
130 'title' => '',
131 'message' => '',
132 ],
133 ];
134
135 $this->isError = false;
136 }
137
138 protected function validateContent(string $fileContent)
139 {
140 $checkFor = [
141 [
142 'type' => 'include',
143 'regex' => '#^\\.\\. include:: \\.\\./\\.\\./Includes.txt#m',
144 'title' => 'no include',
145 'message' => 'insert \'.. include:: ../../Includes.txt\' in first line of the file',
146 ],
147 [
148 'type' => 'reference',
149 'regex' => '#^See :issue:`[0-9]{4,6}`#m',
150 'title' => 'no reference',
151 'message' => 'insert \'See :issue:`<issuenumber>`\' after headline',
152 ],
153 ];
154
155 foreach ($checkFor as $values) {
156 if (preg_match($values['regex'], $fileContent) !== 1) {
157 $this->messages[$values['type']]['title'] = $values['title'];
158 $this->messages[$values['type']]['message'] = $values['message'];
159 $this->isError = true;
160 }
161 }
162 }
163
164 protected function validateLastLine(string $line)
165 {
166 $checkFor = [
167 [
168 'type' => 'index',
169 'regex' => '#^\.\. index:: (?:(?:FullyScanned|PartiallyScanned|NotScanned|TypoScript|TSConfig|TCA|FlexForm|LocalConfiguration|Fluid|FAL|Database|JavaScript|PHP-API|Frontend|Backend|CLI|RTE|ext:[a-zA-Z_0-9]+)(?:,\\s|$))+#',
170 'title' => 'no or wrong index',
171 'message' => 'insert \'.. index:: <at least one valid keyword>\' at last line of the file. See Build/Scripts/validateRstFiles.php for allowed keywords',
172 ],
173 ];
174
175 foreach ($checkFor as $values) {
176 if (preg_match($values['regex'], $line) !== 1) {
177 $this->messages[$values['type']]['title'] = $values['title'];
178 $this->messages[$values['type']]['message'] = $values['message'];
179 $this->isError = true;
180 }
181 }
182 }
183
184 protected function validateLastLineForFilename(string $path, string $lastLine)
185 {
186 $checkFor = [
187 [
188 'type' => 'index',
189 'regexNotFilename' => '#
190 Changelog[\\\\/]8\\.[0-9]+[\\\\/]|
191 Changelog[\\\\/]8\\.7\\.x[\\\\/]|
192 Changelog[\\\\/]7\\.[0-9]+[\\\\/]|
193 Changelog[\\\\/]7\\.6\\.x[\\\\/]|
194 Changelog[\\\\/](?:master|[0-9]+\\.[0-9]+)[\\\\/]Feature-|
195 Changelog[\\\\/](?:master|[0-9]+\\.[0-9]+)[\\\\/]Important-
196 #x',
197 'regex' => '#^\.\. index:: .*(?:FullyScanned|PartiallyScanned|NotScanned).*#',
198 'title' => 'missing FullyScanned / PartiallyScanned / NotScanned tag',
199 'message' => 'insert \'.. index:: <at least one valid keyword and either FullyScanned, PartiallyScanned or NotScanned>\' at last line of the file. See Build/Scripts/validateRstFiles.php for allowed keywords',
200 ],
201 ];
202
203 foreach ($checkFor as $values) {
204 if (preg_match($values['regexNotFilename'], $path) === 1) {
205 continue;
206 }
207 if (preg_match($values['regex'], $lastLine) !== 1) {
208 $this->messages[$values['type']]['title'] = $values['title'];
209 $this->messages[$values['type']]['message'] = $values['message'];
210 $this->isError = true;
211 }
212 }
213 }
214 }
215
216 $dir = '';
217 $args = getopt('d:');
218 if (isset($args['d'])) {
219 $dir = $args['d'];
220 }
221 $validator = new validateRstFiles($dir);
222 $validator->validate();