Passed
Push — master ( 87c59f...ee792a )
by Michal
04:02
created

ContextGenerator::formatName()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
c 0
b 0
f 0
rs 8.439
cc 6
eloc 19
nc 13
nop 1
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 14 and the first side effect is on line 5.

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.

Loading history...
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