1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
namespace PhpMyAdmin\SqlParser\Tools; |
4
|
|
|
|
5
|
|
|
require_once __DIR__ . '/../vendor/autoload.php'; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Used for context generation. |
9
|
|
|
* |
10
|
|
|
* @category Contexts |
11
|
|
|
* |
12
|
|
|
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+ |
13
|
|
|
*/ |
14
|
|
|
class ContextGenerator |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* Labels and flags that may be used when defining keywords. |
18
|
|
|
* |
19
|
|
|
* @var array |
20
|
|
|
*/ |
21
|
|
|
public static $LABELS_FLAGS = array( |
22
|
|
|
'(R)' => 2, // reserved |
23
|
|
|
'(D)' => 8, // data type |
24
|
|
|
'(K)' => 16, // keyword |
25
|
|
|
'(F)' => 32, // function name |
26
|
|
|
); |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Documentation links for each context. |
30
|
|
|
* |
31
|
|
|
* @var array |
32
|
|
|
*/ |
33
|
|
|
public static $LINKS = array( |
34
|
|
|
'MySql50000' => 'https://dev.mysql.com/doc/refman/5.0/en/keywords.html', |
35
|
|
|
'MySql50100' => 'https://dev.mysql.com/doc/refman/5.1/en/keywords.html', |
36
|
|
|
'MySql50500' => 'https://dev.mysql.com/doc/refman/5.5/en/keywords.html', |
37
|
|
|
'MySql50600' => 'https://dev.mysql.com/doc/refman/5.6/en/keywords.html', |
38
|
|
|
'MySql50700' => 'https://dev.mysql.com/doc/refman/5.7/en/keywords.html', |
39
|
|
|
'MySql80000' => 'https://dev.mysql.com/doc/refman/8.0/en/keywords.html', |
40
|
|
|
'MariaDb100000' => 'https://mariadb.com/kb/en/the-mariadb-library/reserved-words/', |
41
|
|
|
'MariaDb100100' => 'https://mariadb.com/kb/en/the-mariadb-library/reserved-words/', |
42
|
|
|
'MariaDb100200' => 'https://mariadb.com/kb/en/the-mariadb-library/reserved-words/', |
43
|
|
|
); |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* The template of a context. |
47
|
|
|
* |
48
|
|
|
* Parameters: |
49
|
|
|
* 1 - name |
50
|
|
|
* 2 - class |
51
|
|
|
* 3 - link |
52
|
|
|
* 4 - keywords array |
53
|
|
|
* |
54
|
|
|
* @var string |
55
|
|
|
*/ |
56
|
|
|
const TEMPLATE = |
57
|
|
|
'<?php' . "\n" . |
58
|
|
|
'' . "\n" . |
59
|
|
|
'/**' . "\n" . |
60
|
|
|
' * Context for %1$s.' . "\n" . |
61
|
|
|
' *' . "\n" . |
62
|
|
|
' * This file was auto-generated.' . "\n" . |
63
|
|
|
' *' . "\n" . |
64
|
|
|
' * @see %3$s' . "\n" . |
65
|
|
|
' */' . "\n" . |
66
|
|
|
'' . "\n" . |
67
|
|
|
'namespace PhpMyAdmin\\SqlParser\\Contexts;' . "\n" . |
68
|
|
|
'' . "\n" . |
69
|
|
|
'use PhpMyAdmin\\SqlParser\\Context;' . "\n" . |
70
|
|
|
'' . "\n" . |
71
|
|
|
'/**' . "\n" . |
72
|
|
|
' * Context for %1$s.' . "\n" . |
73
|
|
|
' *' . "\n" . |
74
|
|
|
' * @category Contexts' . "\n" . |
75
|
|
|
' *' . "\n" . |
76
|
|
|
' * @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+' . "\n" . |
77
|
|
|
' */' . "\n" . |
78
|
|
|
'class %2$s extends Context' . "\n" . |
79
|
|
|
'{' . "\n" . |
80
|
|
|
' /**' . "\n" . |
81
|
|
|
' * List of keywords.' . "\n" . |
82
|
|
|
' *' . "\n" . |
83
|
|
|
' * The value associated to each keyword represents its flags.' . "\n" . |
84
|
|
|
' *' . "\n" . |
85
|
|
|
' * @see Token::FLAG_KEYWORD_*' . "\n" . |
86
|
|
|
' *' . "\n" . |
87
|
|
|
' * @var array' . "\n" . |
88
|
|
|
' */' . "\n" . |
89
|
|
|
' public static $KEYWORDS = array(' . "\n" . |
90
|
|
|
'%4$s' . |
91
|
|
|
' );' . "\n" . |
92
|
|
|
'}' . "\n"; |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Sorts an array of words. |
96
|
|
|
* |
97
|
|
|
* @param array $arr |
98
|
|
|
* |
99
|
|
|
* @return array |
100
|
|
|
*/ |
101
|
|
|
public static function sortWords(array &$arr) |
102
|
|
|
{ |
103
|
|
|
ksort($arr); |
104
|
|
|
foreach ($arr as &$wordsByLen) { |
105
|
|
|
ksort($wordsByLen); |
106
|
|
|
foreach ($wordsByLen as &$words) { |
107
|
|
|
sort($words, SORT_STRING); |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
return $arr; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Reads a list of words and sorts it by type, length and keyword. |
116
|
|
|
* |
117
|
|
|
* @param string[] $files |
118
|
|
|
* |
119
|
|
|
* @return array |
120
|
|
|
*/ |
121
|
|
|
public static function readWords(array $files) |
122
|
|
|
{ |
123
|
|
|
$words = array(); |
124
|
|
|
foreach ($files as $file) { |
125
|
|
|
$words = array_merge($words, file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
$types = array(); |
129
|
|
|
|
130
|
|
|
for ($i = 0, $count = count($words); $i != $count; ++$i) { |
131
|
|
|
$type = 1; |
132
|
|
|
$value = trim($words[$i]); |
133
|
|
|
|
134
|
|
|
// Reserved, data types, keys, functions, etc. keywords. |
135
|
|
|
foreach (static::$LABELS_FLAGS as $label => $flags) { |
136
|
|
|
if (strstr($value, $label) !== false) { |
137
|
|
|
$type |= $flags; |
138
|
|
|
$value = trim(str_replace($label, '', $value)); |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
// Composed keyword. |
143
|
|
|
if (strstr($value, ' ') !== false) { |
144
|
|
|
$type |= 2; // Reserved keyword. |
145
|
|
|
$type |= 4; // Composed keyword. |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
$len = strlen($words[$i]); |
149
|
|
|
if ($len === 0) { |
150
|
|
|
continue; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
$value = strtoupper($value); |
154
|
|
|
if (!isset($types[$value])) { |
155
|
|
|
$types[$value] = $type; |
156
|
|
|
} else { |
157
|
|
|
$types[$value] |= $type; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$ret = array(); |
162
|
|
|
foreach ($types as $word => $type) { |
163
|
|
|
$len = strlen($word); |
164
|
|
|
if (!isset($ret[$type])) { |
165
|
|
|
$ret[$type] = array(); |
166
|
|
|
} |
167
|
|
|
if (!isset($ret[$type][$len])) { |
168
|
|
|
$ret[$type][$len] = array(); |
169
|
|
|
} |
170
|
|
|
$ret[$type][$len][] = $word; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
return static::sortWords($ret); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Prints an array of a words in PHP format. |
178
|
|
|
* |
179
|
|
|
* @param array $words the list of words to be formatted |
180
|
|
|
* @param int $spaces the number of spaces that starts every line |
181
|
|
|
* @param int $line the length of a line |
182
|
|
|
* |
183
|
|
|
* @return string |
184
|
|
|
*/ |
185
|
|
|
public static function printWords($words, $spaces = 8, $line = 80) |
186
|
|
|
{ |
187
|
|
|
$typesCount = count($words); |
188
|
|
|
$ret = ''; |
189
|
|
|
$j = 0; |
190
|
|
|
|
191
|
|
|
foreach ($words as $type => $wordsByType) { |
192
|
|
|
foreach ($wordsByType as $len => $wordsByLen) { |
193
|
|
|
$count = round(($line - $spaces) / ($len + 9)); // strlen("'' => 1, ") = 9 |
194
|
|
|
$i = 0; |
195
|
|
|
|
196
|
|
|
foreach ($wordsByLen as $word) { |
197
|
|
|
if ($i == 0) { |
198
|
|
|
$ret .= str_repeat(' ', $spaces); |
199
|
|
|
} |
200
|
|
|
$ret .= sprintf('\'%s\' => %s, ', $word, $type); |
201
|
|
|
if (++$i == $count) { |
202
|
|
|
$ret .= "\n"; |
203
|
|
|
$i = 0; |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
if ($i != 0) { |
208
|
|
|
$ret .= "\n"; |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
if (++$j < $typesCount) { |
213
|
|
|
$ret .= "\n"; |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
// Trim trailing spaces and return. |
218
|
|
|
return str_replace(" \n", "\n", $ret); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Generates a context's class. |
223
|
|
|
* |
224
|
|
|
* @param array $options the options that are used in generating this context |
225
|
|
|
* |
226
|
|
|
* @return string |
227
|
|
|
*/ |
228
|
|
|
public static function generate($options) |
229
|
|
|
{ |
230
|
|
|
if (isset($options['keywords'])) { |
231
|
|
|
$options['keywords'] = static::printWords($options['keywords']); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
return sprintf( |
235
|
|
|
static::TEMPLATE, |
236
|
|
|
$options['name'], |
237
|
|
|
$options['class'], |
238
|
|
|
$options['link'], |
239
|
|
|
$options['keywords'] |
240
|
|
|
); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Formats context name. |
245
|
|
|
* |
246
|
|
|
* @param string $name Name to format. |
247
|
|
|
* |
248
|
|
|
* @return string |
249
|
|
|
*/ |
250
|
|
|
public static function formatName($name) |
251
|
|
|
{ |
252
|
|
|
/* Split name and version */ |
253
|
|
|
$parts = array(); |
254
|
|
|
if (preg_match('/([^[0-9]*)([0-9]*)/', $name, $parts) === false) { |
255
|
|
|
return $name; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/* Format name */ |
259
|
|
|
$base = $parts[1]; |
260
|
|
|
switch ($base) { |
261
|
|
|
case 'MySql': |
262
|
|
|
$base = 'MySQL'; |
263
|
|
|
break; |
264
|
|
|
case 'MariaDb': |
265
|
|
|
$base = 'MariaDB'; |
266
|
|
|
break; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/* Parse version to array */ |
270
|
|
|
$ver_str = $parts[2]; |
271
|
|
|
if (strlen($ver_str) % 2 == 1) { |
272
|
|
|
$ver_str = '0' . $ver_str; |
273
|
|
|
} |
274
|
|
|
$version = array_map('intval', str_split($ver_str, 2)); |
275
|
|
|
/* Remove trailing zero */ |
276
|
|
|
if ($version[count($version) - 1] === 0) { |
277
|
|
|
$version = array_slice($version, 0, count($version) - 1); |
278
|
|
|
} |
279
|
|
|
/* Create name */ |
280
|
|
|
return $base . ' ' . implode('.', $version); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Builds a test. |
285
|
|
|
* |
286
|
|
|
* Reads the input file, generates the data and writes it back. |
287
|
|
|
* |
288
|
|
|
* @param string $input the input file |
289
|
|
|
* @param string $output the output directory |
290
|
|
|
*/ |
291
|
|
|
public static function build($input, $output) |
292
|
|
|
{ |
293
|
|
|
/** |
294
|
|
|
* The directory that contains the input file. |
295
|
|
|
* |
296
|
|
|
* Used to include common files. |
297
|
|
|
* |
298
|
|
|
* @var string |
299
|
|
|
*/ |
300
|
|
|
$directory = dirname($input) . '/'; |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* The name of the file that contains the context. |
304
|
|
|
* |
305
|
|
|
* @var string |
306
|
|
|
*/ |
307
|
|
|
$file = basename($input); |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* The name of the context. |
311
|
|
|
* |
312
|
|
|
* @var string |
313
|
|
|
*/ |
314
|
|
|
$name = substr($file, 0, -4); |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* The name of the class that defines this context. |
318
|
|
|
* |
319
|
|
|
* @var string |
320
|
|
|
*/ |
321
|
|
|
$class = 'Context' . $name; |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* The formatted name of this context. |
325
|
|
|
* |
326
|
|
|
* @var string |
327
|
|
|
*/ |
328
|
|
|
$formattedName = static::formatName($name); |
329
|
|
|
|
330
|
|
|
file_put_contents( |
331
|
|
|
$output . '/' . $class . '.php', |
332
|
|
|
static::generate( |
333
|
|
|
array( |
334
|
|
|
'name' => $formattedName, |
335
|
|
|
'class' => $class, |
336
|
|
|
'link' => static::$LINKS[$name], |
337
|
|
|
'keywords' => static::readWords( |
338
|
|
|
array( |
339
|
|
|
$directory . '_common.txt', |
340
|
|
|
$directory . '_functions' . $file, |
341
|
|
|
$directory . $file, |
342
|
|
|
) |
343
|
|
|
), |
344
|
|
|
) |
345
|
|
|
) |
346
|
|
|
); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Generates recursively all tests preserving the directory structure. |
351
|
|
|
* |
352
|
|
|
* @param string $input the input directory |
353
|
|
|
* @param string $output the output directory |
354
|
|
|
*/ |
355
|
|
|
public static function buildAll($input, $output) |
356
|
|
|
{ |
357
|
|
|
$files = scandir($input); |
358
|
|
|
|
359
|
|
|
foreach ($files as $file) { |
360
|
|
|
// Skipping current and parent directories. |
361
|
|
|
if (($file[0] === '.') || ($file[0] === '_')) { |
362
|
|
|
continue; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
// Building the context. |
366
|
|
|
sprintf("Building context for %s...\n", $file); |
367
|
|
|
static::build($input . '/' . $file, $output); |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
// Test generator. |
373
|
|
|
// |
374
|
|
|
// Example of usage: |
375
|
|
|
// |
376
|
|
|
// php ContextGenerator.php data data |
377
|
|
|
// |
378
|
|
|
// Input data must be in the `data` folder. |
379
|
|
|
// The output will be generated in the same `data` folder. |
380
|
|
|
// |
381
|
|
|
if (count($argv) >= 3) { |
382
|
|
|
// Extracting directories' name from command line and trimming unnecessary |
383
|
|
|
// slashes at the end. |
384
|
|
|
$input = rtrim($argv[1], '/'); |
385
|
|
|
$output = rtrim($argv[2], '/'); |
386
|
|
|
|
387
|
|
|
// Checking if all directories are valid. |
388
|
|
|
if (!is_dir($input)) { |
389
|
|
|
throw new \Exception('The input directory does not exist.'); |
390
|
|
|
} elseif (!is_dir($output)) { |
391
|
|
|
throw new \Exception('The output directory does not exist.'); |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
// Finally, building the tests. |
395
|
|
|
ContextGenerator::buildAll($input, $output); |
396
|
|
|
} |
397
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.